You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

PACServer.cs 7.3 kB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
5 years ago
10 years ago
10 years ago
10 years ago
6 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
6 years ago
10 years ago
10 years ago
6 years ago
10 years ago
10 years ago
6 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
6 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. using Shadowsocks.Encryption;
  2. using Shadowsocks.Model;
  3. using Shadowsocks.Util;
  4. using System;
  5. using System.Net;
  6. using System.Net.Sockets;
  7. using System.Text;
  8. using System.Web;
  9. using NLog;
  10. namespace Shadowsocks.Controller
  11. {
  12. public class PACServer : Listener.Service
  13. {
  14. private static Logger logger = LogManager.GetCurrentClassLogger();
  15. public const string RESOURCE_NAME = "pac";
  16. private string PacSecret
  17. {
  18. get
  19. {
  20. if (string.IsNullOrEmpty(_cachedPacSecret))
  21. {
  22. var rd = new byte[32];
  23. RNG.GetBytes(rd);
  24. _cachedPacSecret = HttpServerUtility.UrlTokenEncode(rd);
  25. }
  26. return _cachedPacSecret;
  27. }
  28. }
  29. private string _cachedPacSecret = "";
  30. public string PacUrl { get; private set; } = "";
  31. private Configuration _config;
  32. private PACDaemon _pacDaemon;
  33. public PACServer(PACDaemon pacDaemon)
  34. {
  35. _pacDaemon = pacDaemon;
  36. }
  37. public void UpdatePACURL(Configuration config)
  38. {
  39. _config = config;
  40. string usedSecret = _config.secureLocalPac ? $"&secret={PacSecret}" : "";
  41. string contentHash = GetHash(_pacDaemon.GetPACContent());
  42. PacUrl = $"http://{config.localHost}:{config.localPort}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}";
  43. logger.Debug("Set PAC URL:" + PacUrl);
  44. }
  45. private static string GetHash(string content)
  46. {
  47. return HttpServerUtility.UrlTokenEncode(MbedTLS.MD5(Encoding.ASCII.GetBytes(content)));
  48. }
  49. public override bool Handle(byte[] firstPacket, int length, Socket socket, object state)
  50. {
  51. if (socket.ProtocolType != ProtocolType.Tcp)
  52. {
  53. return false;
  54. }
  55. try
  56. {
  57. /*
  58. * RFC 7230
  59. *
  60. GET /hello.txt HTTP/1.1
  61. User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
  62. Host: www.example.com
  63. Accept-Language: en, mi
  64. */
  65. string request = Encoding.UTF8.GetString(firstPacket, 0, length);
  66. string[] lines = request.Split('\r', '\n');
  67. bool hostMatch = false, pathMatch = false, useSocks = false;
  68. bool secretMatch = !_config.secureLocalPac;
  69. if (lines.Length < 2) // need at lease RequestLine + Host
  70. {
  71. return false;
  72. }
  73. // parse request line
  74. string requestLine = lines[0];
  75. // GET /pac?t=yyyyMMddHHmmssfff&secret=foobar HTTP/1.1
  76. string[] requestItems = requestLine.Split(' ');
  77. if (requestItems.Length == 3 && requestItems[0] == "GET")
  78. {
  79. int index = requestItems[1].IndexOf('?');
  80. if (index < 0)
  81. {
  82. index = requestItems[1].Length;
  83. }
  84. string resourceString = requestItems[1].Substring(0, index).Remove(0, 1);
  85. if (string.Equals(resourceString, RESOURCE_NAME, StringComparison.OrdinalIgnoreCase))
  86. {
  87. pathMatch = true;
  88. if (!secretMatch)
  89. {
  90. string queryString = requestItems[1].Substring(index);
  91. if (queryString.Contains(PacSecret))
  92. {
  93. secretMatch = true;
  94. }
  95. }
  96. }
  97. }
  98. // parse request header
  99. for (int i = 1; i < lines.Length; i++)
  100. {
  101. if (string.IsNullOrEmpty(lines[i]))
  102. continue;
  103. string[] kv = lines[i].Split(new char[] { ':' }, 2);
  104. if (kv.Length == 2)
  105. {
  106. if (kv[0] == "Host")
  107. {
  108. if (kv[1].Trim() == ((IPEndPoint)socket.LocalEndPoint).ToString())
  109. {
  110. hostMatch = true;
  111. }
  112. }
  113. //else if (kv[0] == "User-Agent")
  114. //{
  115. // // we need to drop connections when changing servers
  116. // if (kv[1].IndexOf("Chrome") >= 0)
  117. // {
  118. // useSocks = true;
  119. // }
  120. //}
  121. }
  122. }
  123. if (hostMatch && pathMatch)
  124. {
  125. if (!secretMatch)
  126. {
  127. socket.Close(); // Close immediately
  128. }
  129. else
  130. {
  131. SendResponse(socket, useSocks);
  132. }
  133. return true;
  134. }
  135. return false;
  136. }
  137. catch (ArgumentException)
  138. {
  139. return false;
  140. }
  141. }
  142. public void SendResponse(Socket socket, bool useSocks)
  143. {
  144. try
  145. {
  146. IPEndPoint localEndPoint = (IPEndPoint)socket.LocalEndPoint;
  147. string proxy = GetPACAddress(localEndPoint, useSocks);
  148. string pacContent = $"var __PROXY__ = '{proxy}';\n" + _pacDaemon.GetPACContent();
  149. string responseHead =
  150. $@"HTTP/1.1 200 OK
  151. Server: ShadowsocksWindows/{UpdateChecker.Version}
  152. Content-Type: application/x-ns-proxy-autoconfig
  153. Content-Length: { Encoding.UTF8.GetBytes(pacContent).Length}
  154. Connection: Close
  155. ";
  156. byte[] response = Encoding.UTF8.GetBytes(responseHead + pacContent);
  157. socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket);
  158. Utils.ReleaseMemory(true);
  159. }
  160. catch (Exception e)
  161. {
  162. logger.LogUsefulException(e);
  163. socket.Close();
  164. }
  165. }
  166. private void SendCallback(IAsyncResult ar)
  167. {
  168. Socket conn = (Socket)ar.AsyncState;
  169. try
  170. {
  171. conn.Shutdown(SocketShutdown.Send);
  172. }
  173. catch
  174. { }
  175. }
  176. private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks)
  177. {
  178. return localEndPoint.AddressFamily == AddressFamily.InterNetworkV6
  179. ? $"{(useSocks ? "SOCKS5" : "PROXY")} [{localEndPoint.Address}]:{_config.localPort};"
  180. : $"{(useSocks ? "SOCKS5" : "PROXY")} {localEndPoint.Address}:{_config.localPort};";
  181. }
  182. }
  183. }