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

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