using Newtonsoft.Json; using NLog; using Shadowsocks.Controller; using Shadowsocks.Model; using Shadowsocks.Properties; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; namespace Shadowsocks.Util.SystemProxy { public static class Sysproxy { private static Logger logger = LogManager.GetCurrentClassLogger(); private const string _userWininetConfigFile = "user-wininet.json"; private readonly static string[] _lanIP = { "", "localhost", "127.*", "10.*", "172.16.*", "172.17.*", "172.18.*", "172.19.*", "172.20.*", "172.21.*", "172.22.*", "172.23.*", "172.24.*", "172.25.*", "172.26.*", "172.27.*", "172.28.*", "172.29.*", "172.30.*", "172.31.*", "192.168.*" }; private static string _queryStr; // In general, this won't change // format: // // // // private static SysproxyConfig _userSettings = null; enum RET_ERRORS : int { RET_NO_ERROR = 0, INVALID_FORMAT = 1, NO_PERMISSION = 2, SYSCALL_FAILED = 3, NO_MEMORY = 4, INVAILD_OPTION_COUNT = 5, }; static Sysproxy() { try { FileManager.UncompressFile(Utils.GetTempPath("sysproxy.exe"), Environment.Is64BitOperatingSystem ? Resources.sysproxy64_exe : Resources.sysproxy_exe); } catch (IOException e) { logger.LogUsefulException(e); } } public static void SetIEProxy(bool enable, bool global, string proxyServer, string pacURL) { Read(); if (!_userSettings.UserSettingsRecorded) { // record user settings ExecSysproxy("query"); ParseQueryStr(_queryStr); } string arguments; if (enable) { string customBypassString = _userSettings.BypassList ?? ""; List customBypassList = new List(customBypassString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); customBypassList.AddRange(_lanIP); string[] realBypassList = customBypassList.Distinct().ToArray(); string realBypassString = string.Join(";", realBypassList); arguments = global ? $"global {proxyServer} {realBypassString}" : $"pac {pacURL}"; } else { // restore user settings var flags = _userSettings.Flags; var proxy_server = _userSettings.ProxyServer ?? "-"; var bypass_list = _userSettings.BypassList ?? "-"; var pac_url = _userSettings.PacUrl ?? "-"; arguments = $"set {flags} {proxy_server} {bypass_list} {pac_url}"; // have to get new settings _userSettings.UserSettingsRecorded = false; } Save(); ExecSysproxy(arguments); } // set system proxy to 1 (null) (null) (null) public static bool ResetIEProxy() { try { // clear user-wininet.json _userSettings = new SysproxyConfig(); Save(); // clear system setting ExecSysproxy("set 1 - - -"); } catch (Exception) { return false; } return true; } private static void ExecSysproxy(string arguments) { // using event to avoid hanging when redirect standard output/error // ref: https://stackoverflow.com/questions/139593/processstartinfo-hanging-on-waitforexit-why // and http://blog.csdn.net/zhangweixing0/article/details/7356841 using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false)) using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false)) { using (var process = new Process()) { // Configure the process using the StartInfo properties. process.StartInfo.FileName = Utils.GetTempPath("sysproxy.exe"); process.StartInfo.Arguments = arguments; process.StartInfo.WorkingDirectory = Utils.GetTempPath(); process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardOutput = true; // Need to provide encoding info, or output/error strings we got will be wrong. process.StartInfo.StandardOutputEncoding = Encoding.Unicode; process.StartInfo.StandardErrorEncoding = Encoding.Unicode; process.StartInfo.CreateNoWindow = true; StringBuilder output = new StringBuilder(); StringBuilder error = new StringBuilder(); process.OutputDataReceived += (sender, e) => { if (e.Data == null) { outputWaitHandle.Set(); } else { output.AppendLine(e.Data); } }; process.ErrorDataReceived += (sender, e) => { if (e.Data == null) { errorWaitHandle.Set(); } else { error.AppendLine(e.Data); } }; try { process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); process.WaitForExit(); } catch (System.ComponentModel.Win32Exception e) { // log the arguments throw new ProxyException(ProxyExceptionType.FailToRun, process.StartInfo.Arguments, e); } var stderr = error.ToString(); var stdout = output.ToString(); var exitCode = process.ExitCode; if (exitCode != (int)RET_ERRORS.RET_NO_ERROR) { throw new ProxyException(ProxyExceptionType.SysproxyExitError, stderr); } if (arguments == "query") { if (stdout.IsNullOrWhiteSpace() || stdout.IsNullOrEmpty()) { // we cannot get user settings throw new ProxyException(ProxyExceptionType.QueryReturnEmpty); } _queryStr = stdout; } } } } private static void Save() { try { using (StreamWriter sw = new StreamWriter(File.Open(Utils.GetTempPath(_userWininetConfigFile), FileMode.Create))) { string jsonString = JsonConvert.SerializeObject(_userSettings, Formatting.Indented); sw.Write(jsonString); sw.Flush(); } } catch (IOException e) { logger.LogUsefulException(e); } } private static void Read() { try { string configContent = File.ReadAllText(Utils.GetTempPath(_userWininetConfigFile)); _userSettings = JsonConvert.DeserializeObject(configContent); } catch (Exception) { // Suppress all exceptions. finally block will initialize new user config settings. } finally { if (_userSettings == null) _userSettings = new SysproxyConfig(); } } private static void ParseQueryStr(string str) { string[] userSettingsArr = str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); // sometimes sysproxy output in utf16le instead of ascii // manually translate it if (userSettingsArr.Length != 4) { byte[] strByte = Encoding.ASCII.GetBytes(str); str = Encoding.Unicode.GetString(strByte); userSettingsArr = str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); // still fail, throw exception with string hexdump if (userSettingsArr.Length != 4) { throw new ProxyException(ProxyExceptionType.QueryReturnMalformed, BitConverter.ToString(strByte)); } } _userSettings.Flags = userSettingsArr[0]; // handle output from WinINET if (userSettingsArr[1] == "(null)") _userSettings.ProxyServer = null; else _userSettings.ProxyServer = userSettingsArr[1]; if (userSettingsArr[2] == "(null)") _userSettings.BypassList = null; else _userSettings.BypassList = userSettingsArr[2]; if (userSettingsArr[3] == "(null)") _userSettings.PacUrl = null; else _userSettings.PacUrl = userSettingsArr[3]; _userSettings.UserSettingsRecorded = true; } } }