using System; using System.Collections.Generic; using System.IO.Pipelines; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Shadowsocks.Protocol.Socks5 { public class Socks5Service : IStreamService { public Socks5Service() { } public Socks5Service(Dictionary passwords) { enablePassword = true; this.passwords = passwords; } private readonly bool enablePassword; private readonly Dictionary passwords = new Dictionary(); public static int ReadTimeout = 120000; public async Task IsMyClient(IDuplexPipe pipe) { var result = await pipe.Input.ReadAsync(); pipe.Input.AdvanceTo(result.Buffer.Start); var buffer = result.Buffer; if (buffer.Length < 3) return false; if (buffer.First.Span[0] != 5) return false; if (buffer.First.Span[1] == 0) return false; // ver 5, has auth method return true; } public async Task Handle(IDuplexPipe pipe) { var pmp = new ProtocolMessagePipe(pipe); var hs = await pmp.ReadAsync(); var selected = Socks5Message.AuthNoAcceptable; if (enablePassword) { foreach (var a in Util.GetArray(hs.Auth)) { if (a == Socks5Message.AuthUserPass) { selected = Socks5Message.AuthUserPass; break; } if (a == Socks5Message.AuthNone) { selected = Socks5Message.AuthNone; } } } else { if (Util.GetArray(hs.Auth).Any(a => a == Socks5Message.AuthNone)) { selected = Socks5Message.AuthNone; } } await pmp.WriteAsync(new Socks5MethodSelectionMessage() { SelectedAuth = selected, }); switch (selected) { case Socks5Message.AuthNoAcceptable: default: await pipe.Output.CompleteAsync(); return null; case Socks5Message.AuthNone: break; case Socks5Message.AuthUserPass: var token = await pmp.ReadAsync(); var user = Encoding.UTF8.GetString(token.User.Span); var password = Encoding.UTF8.GetString(token.Password.Span); var ar = new Socks5UserPasswordResponseMessage(); var success = passwords.TryGetValue(user, out var expectPassword) && expectPassword == password; ar.Success = success; await pmp.WriteAsync(ar); if (!success) { await pipe.Output.CompleteAsync(); return null; } break; } var req = await pmp.ReadAsync(); var resp = new Socks5ReplyMessage(); switch (req.Command) { case Socks5Message.CmdBind: case Socks5Message.CmdUdpAssociation: // not support yet resp.Reply = Socks5Message.ReplyCommandNotSupport; break; case Socks5Message.CmdConnect: Console.WriteLine(req.EndPoint); // TODO: route and dial outbound resp.Reply = Socks5Message.ReplySucceed; break; } // TODO: write response, hand out connection await pmp.WriteAsync(resp); if (req.Command != Socks5Message.CmdConnect) return null; return pipe; } } }