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 11 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  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 PAC_FILE = "pac.txt";
  18. public const string USER_RULE_FILE = "user-rule.txt";
  19. public const string USER_ABP_FILE = "abp.txt";
  20. private string PacSecret { get; set; } = "";
  21. public string PacUrl { get; private set; } = "";
  22. FileSystemWatcher PACFileWatcher;
  23. FileSystemWatcher UserRuleFileWatcher;
  24. private Configuration _config;
  25. public event EventHandler PACFileChanged;
  26. public event EventHandler UserRuleFileChanged;
  27. public PACServer()
  28. {
  29. this.WatchPacFile();
  30. this.WatchUserRuleFile();
  31. }
  32. public void UpdateConfiguration(Configuration config)
  33. {
  34. this._config = config;
  35. if (config.secureLocalPac)
  36. {
  37. var rd = new byte[32];
  38. RNG.GetBytes(rd);
  39. PacSecret = $"&secret={Convert.ToBase64String(rd)}";
  40. }
  41. else
  42. {
  43. PacSecret = "";
  44. }
  45. PacUrl = $"http://127.0.0.1:{config.localPort}/pac?t={GetTimestamp(DateTime.Now)}{PacSecret}";
  46. }
  47. private static string GetTimestamp(DateTime value)
  48. {
  49. return value.ToString("yyyyMMddHHmmssfff");
  50. }
  51. public override bool Handle(byte[] firstPacket, int length, Socket socket, object state)
  52. {
  53. if (socket.ProtocolType != ProtocolType.Tcp)
  54. {
  55. return false;
  56. }
  57. try
  58. {
  59. string request = Encoding.UTF8.GetString(firstPacket, 0, length);
  60. string[] lines = request.Split('\r', '\n');
  61. bool hostMatch = false, pathMatch = false, useSocks = false;
  62. bool secretMatch = PacSecret.IsNullOrEmpty();
  63. foreach (string line in lines)
  64. {
  65. string[] kv = line.Split(new char[] { ':' }, 2);
  66. if (kv.Length == 2)
  67. {
  68. if (kv[0] == "Host")
  69. {
  70. if (kv[1].Trim() == ((IPEndPoint)socket.LocalEndPoint).ToString())
  71. {
  72. hostMatch = true;
  73. }
  74. }
  75. //else if (kv[0] == "User-Agent")
  76. //{
  77. // // we need to drop connections when changing servers
  78. // if (kv[1].IndexOf("Chrome") >= 0)
  79. // {
  80. // useSocks = true;
  81. // }
  82. //}
  83. }
  84. else if (kv.Length == 1)
  85. {
  86. if (line.IndexOf("pac", StringComparison.Ordinal) >= 0)
  87. {
  88. pathMatch = true;
  89. }
  90. if (!secretMatch)
  91. {
  92. if(line.IndexOf(PacSecret, StringComparison.Ordinal) >= 0)
  93. {
  94. secretMatch = true;
  95. }
  96. }
  97. }
  98. }
  99. if (hostMatch && pathMatch)
  100. {
  101. if (!secretMatch)
  102. {
  103. socket.Close(); // Close immediately
  104. }
  105. else
  106. {
  107. SendResponse(firstPacket, length, socket, useSocks);
  108. }
  109. return true;
  110. }
  111. return false;
  112. }
  113. catch (ArgumentException)
  114. {
  115. return false;
  116. }
  117. }
  118. public string TouchPACFile()
  119. {
  120. if (File.Exists(PAC_FILE))
  121. {
  122. return PAC_FILE;
  123. }
  124. else
  125. {
  126. FileManager.UncompressFile(PAC_FILE, Resources.proxy_pac_txt);
  127. return PAC_FILE;
  128. }
  129. }
  130. internal string TouchUserRuleFile()
  131. {
  132. if (File.Exists(USER_RULE_FILE))
  133. {
  134. return USER_RULE_FILE;
  135. }
  136. else
  137. {
  138. File.WriteAllText(USER_RULE_FILE, Resources.user_rule);
  139. return USER_RULE_FILE;
  140. }
  141. }
  142. private string GetPACContent()
  143. {
  144. if (File.Exists(PAC_FILE))
  145. {
  146. return File.ReadAllText(PAC_FILE, Encoding.UTF8);
  147. }
  148. else
  149. {
  150. return Utils.UnGzip(Resources.proxy_pac_txt);
  151. }
  152. }
  153. public void SendResponse(byte[] firstPacket, int length, Socket socket, bool useSocks)
  154. {
  155. try
  156. {
  157. string pac = GetPACContent();
  158. IPEndPoint localEndPoint = (IPEndPoint)socket.LocalEndPoint;
  159. string proxy = GetPACAddress(firstPacket, length, localEndPoint, useSocks);
  160. pac = pac.Replace("__PROXY__", proxy);
  161. string text = String.Format(@"HTTP/1.1 200 OK
  162. Server: Shadowsocks
  163. Content-Type: application/x-ns-proxy-autoconfig
  164. Content-Length: {0}
  165. Connection: Close
  166. ", Encoding.UTF8.GetBytes(pac).Length) + pac;
  167. byte[] response = Encoding.UTF8.GetBytes(text);
  168. socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket);
  169. Utils.ReleaseMemory(true);
  170. }
  171. catch (Exception e)
  172. {
  173. Logging.LogUsefulException(e);
  174. socket.Close();
  175. }
  176. }
  177. private void SendCallback(IAsyncResult ar)
  178. {
  179. Socket conn = (Socket)ar.AsyncState;
  180. try
  181. {
  182. conn.Shutdown(SocketShutdown.Send);
  183. }
  184. catch
  185. { }
  186. }
  187. private void WatchPacFile()
  188. {
  189. if (PACFileWatcher != null)
  190. {
  191. PACFileWatcher.Dispose();
  192. }
  193. PACFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory());
  194. PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
  195. PACFileWatcher.Filter = PAC_FILE;
  196. PACFileWatcher.Changed += PACFileWatcher_Changed;
  197. PACFileWatcher.Created += PACFileWatcher_Changed;
  198. PACFileWatcher.Deleted += PACFileWatcher_Changed;
  199. PACFileWatcher.Renamed += PACFileWatcher_Changed;
  200. PACFileWatcher.EnableRaisingEvents = true;
  201. }
  202. private void WatchUserRuleFile()
  203. {
  204. if (UserRuleFileWatcher != null)
  205. {
  206. UserRuleFileWatcher.Dispose();
  207. }
  208. UserRuleFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory());
  209. UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
  210. UserRuleFileWatcher.Filter = USER_RULE_FILE;
  211. UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed;
  212. UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed;
  213. UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed;
  214. UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed;
  215. UserRuleFileWatcher.EnableRaisingEvents = true;
  216. }
  217. #region FileSystemWatcher.OnChanged()
  218. // FileSystemWatcher Changed event is raised twice
  219. // http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice
  220. // Add a short delay to avoid raise event twice in a short period
  221. private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e)
  222. {
  223. if (PACFileChanged != null)
  224. {
  225. Logging.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}.");
  226. Task.Factory.StartNew(() =>
  227. {
  228. ((FileSystemWatcher)sender).EnableRaisingEvents = false;
  229. System.Threading.Thread.Sleep(10);
  230. PACFileChanged(this, new EventArgs());
  231. ((FileSystemWatcher)sender).EnableRaisingEvents = true;
  232. });
  233. }
  234. }
  235. private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e)
  236. {
  237. if (UserRuleFileChanged != null)
  238. {
  239. Logging.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}.");
  240. Task.Factory.StartNew(()=>
  241. {
  242. ((FileSystemWatcher)sender).EnableRaisingEvents = false;
  243. System.Threading.Thread.Sleep(10);
  244. UserRuleFileChanged(this, new EventArgs());
  245. ((FileSystemWatcher)sender).EnableRaisingEvents = true;
  246. });
  247. }
  248. }
  249. #endregion
  250. private string GetPACAddress(byte[] requestBuf, int length, IPEndPoint localEndPoint, bool useSocks)
  251. {
  252. //try
  253. //{
  254. // string requestString = Encoding.UTF8.GetString(requestBuf);
  255. // if (requestString.IndexOf("AppleWebKit") >= 0)
  256. // {
  257. // string address = "" + localEndPoint.Address + ":" + config.GetCurrentServer().local_port;
  258. // proxy = "SOCKS5 " + address + "; SOCKS " + address + ";";
  259. // }
  260. //}
  261. //catch (Exception e)
  262. //{
  263. // Logging.LogUsefulException(e);
  264. //}
  265. return (useSocks ? "SOCKS5 " : "PROXY ") + localEndPoint.Address + ":" + this._config.localPort + ";";
  266. }
  267. }
  268. }