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