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