From fcbb9fb3affbcf4ae25fede7c61c0e29bdd31283 Mon Sep 17 00:00:00 2001 From: demouse021 <107237756+demouse021@users.noreply.github.com> Date: Tue, 14 Jun 2022 11:09:35 +0530 Subject: [PATCH] create cs file --- PACServer.cs | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 PACServer.cs diff --git a/PACServer.cs b/PACServer.cs new file mode 100644 index 000000000..b77a633eb --- /dev/null +++ b/PACServer.cs @@ -0,0 +1,204 @@ +using Shadowsocks.Net; +using Shadowsocks.Utilities; +using Shadowsocks.Net.Crypto; +using Splat; +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Reflection; + +namespace Shadowsocks.PAC +{ + public class PACServer : StreamService, IEnableLogger + { + public const string RESOURCE_NAME = "pac"; + + private string PacSecret + { + get + { + if (string.IsNullOrEmpty(_cachedPacSecret)) + { + _cachedPacSecret = Base64Url.Encode(RNG.GetBytes(32)); + } + return _cachedPacSecret; + } + } + private string _cachedPacSecret = ""; + private bool _PACServerEnableSecret; + public string PacUrl { get; private set; } = ""; + + private PACDaemon _pacDaemon; + + public PACServer(PACDaemon pacDaemon, bool PACServerEnableSecret) + { + _pacDaemon = pacDaemon; + _PACServerEnableSecret = PACServerEnableSecret; + } + + public void UpdatePACURL(EndPoint localEndPoint) + { + string usedSecret = _PACServerEnableSecret ? $"&secret={PacSecret}" : ""; + string contentHash = GetHash(_pacDaemon.GetPACContent()); + PacUrl = $"http://{localEndPoint}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}"; + this.Log().Debug("Setting PAC URL: {PacUrl}"); + } + + private static string GetHash(string content) + { + + return Base64Url.Encode(CryptoUtils.MD5(Encoding.ASCII.GetBytes(content))); + } + + public override bool Handle(CachedNetworkStream stream, object state) + { + byte[] fp = new byte[256]; + int len = stream.ReadFirstBlock(fp); + return Handle(fp, len, stream.Socket, state); + } + + [Obsolete] + public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) + { + if (socket.ProtocolType != ProtocolType.Tcp) + { + return false; + } + + try + { + /* + * RFC 7230 + * + GET /hello.txt HTTP/1.1 + User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 + Host: www.example.com + Accept-Language: en, mi + */ + + string request = Encoding.UTF8.GetString(firstPacket, 0, length); + string[] lines = request.Split('\r', '\n'); + bool hostMatch = false, pathMatch = false, useSocks = false; + bool secretMatch = !_PACServerEnableSecret; + + if (lines.Length < 2) // need at lease RequestLine + Host + { + return false; + } + + // parse request line + string requestLine = lines[0]; + // GET /pac?t=yyyyMMddHHmmssfff&secret=foobar HTTP/1.1 + string[] requestItems = requestLine.Split(' '); + if (requestItems.Length == 3 && requestItems[0] == "GET") + { + int index = requestItems[1].IndexOf('?'); + if (index < 0) + { + index = requestItems[1].Length; + } + string resourceString = requestItems[1].Substring(0, index).Remove(0, 1); + if (string.Equals(resourceString, RESOURCE_NAME, StringComparison.OrdinalIgnoreCase)) + { + pathMatch = true; + if (!secretMatch) + { + string queryString = requestItems[1].Substring(index); + if (queryString.Contains(PacSecret)) + { + secretMatch = true; + } + } + } + } + + // parse request header + for (int i = 1; i < lines.Length; i++) + { + if (string.IsNullOrEmpty(lines[i])) + continue; + + string[] kv = lines[i].Split(new char[] { ':' }, 2); + if (kv.Length == 2) + { + if (kv[0] == "Host") + { + if (kv[1].Trim() == (socket.LocalEndPoint as IPEndPoint)?.ToString()) + { + hostMatch = true; + } + } + //else if (kv[0] == "User-Agent") + //{ + // // we need to drop connections when changing servers + // if (kv[1].IndexOf("Chrome") >= 0) + // { + // useSocks = true; + // } + //} + } + } + + if (hostMatch && pathMatch) + { + if (!secretMatch) + { + socket.Close(); // Close immediately + } + else + { + SendResponse(socket, useSocks); + } + return true; + } + return false; + } + catch (ArgumentException) + { + return false; + } + } + + public void SendResponse(Socket socket, bool useSocks) + { + try + { + IPEndPoint localEndPoint = socket.LocalEndPoint as IPEndPoint ?? throw new ArgumentException("Invalid socket local endpoint.", nameof(socket)); + + string proxy = GetPACAddress(localEndPoint, useSocks); + + string pacContent = $"var __PROXY__ = '{proxy}';\n" + _pacDaemon.GetPACContent(); + string responseHead = +$@"HTTP/1.1 200 OK +Server: ShadowsocksPAC/{Assembly.GetExecutingAssembly().GetName().Version} +Content-Type: application/x-ns-proxy-autoconfig +Content-Length: { Encoding.UTF8.GetBytes(pacContent).Length} +Connection: Close + +"; + byte[] response = Encoding.UTF8.GetBytes(responseHead + pacContent); + socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket); + } + catch (Exception e) + { + this.Log().Error(e, ""); + socket.Close(); + } + } + + private void SendCallback(IAsyncResult ar) + { + Socket? conn = ar.AsyncState as Socket; + try + { + conn?.Shutdown(SocketShutdown.Send); + } + catch + { + } + } + + private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks) => $"{(useSocks ? "SOCKS5" : "PROXY")} {localEndPoint};"; + } +}