using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; using Shadowsocks.Model; using Shadowsocks.Properties; using Shadowsocks.Util; using Shadowsocks.Util.ProcessManagement; namespace Shadowsocks.Controller { class PrivoxyRunner { private static int Uid; private static string UniqueConfigFile; private static Job PrivoxyJob; private Process _process; private int _runningPort; static PrivoxyRunner() { try { Uid = Application.StartupPath.GetHashCode(); // Currently we use ss's StartupPath to identify different Privoxy instance. UniqueConfigFile = $"privoxy_{Uid}.conf"; PrivoxyJob = new Job(); FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe); FileManager.UncompressFile(Utils.GetTempPath("mgwz.dll"), Resources.mgwz_dll); } catch (IOException e) { Logging.LogUsefulException(e); } } public int RunningPort => _runningPort; public void Start(Configuration configuration) { if (_process == null) { Process[] existingPrivoxy = Process.GetProcessesByName("ss_privoxy"); foreach (Process p in existingPrivoxy.Where(IsChildProcess)) { KillProcess(p); } string privoxyConfig = Resources.privoxy_conf; _runningPort = this.GetFreePort(); privoxyConfig = privoxyConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString()); privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_PORT__", _runningPort.ToString()); privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1"); FileManager.ByteArrayToFile(Utils.GetTempPath(UniqueConfigFile), Encoding.UTF8.GetBytes(privoxyConfig)); _process = new Process(); // Configure the process using the StartInfo properties. _process.StartInfo.FileName = "ss_privoxy.exe"; _process.StartInfo.Arguments = UniqueConfigFile; _process.StartInfo.WorkingDirectory = Utils.GetTempPath(); _process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; _process.StartInfo.UseShellExecute = true; _process.StartInfo.CreateNoWindow = true; _process.Start(); /* * Add this process to job obj associated with this ss process, so that * when ss exit unexpectedly, this process will be forced killed by system. */ PrivoxyJob.AddProcess(_process.Handle); } RefreshTrayArea(); } public void Stop() { if (_process != null) { KillProcess(_process); _process.Dispose(); _process = null; } RefreshTrayArea(); } private static void KillProcess(Process p) { try { p.CloseMainWindow(); p.WaitForExit(100); if (!p.HasExited) { p.Kill(); p.WaitForExit(); } } catch (Exception e) { Logging.LogUsefulException(e); } } /* * We won't like to kill other ss instances' ss_privoxy.exe. * This function will check whether the given process is created * by this process by checking the module path or command line. * * Since it's required to put ss in different dirs to run muti instances, * different instance will create their unique "privoxy_UID.conf" where * UID is hash of ss's location. */ private static bool IsChildProcess(Process process) { try { if (Utils.IsPortableMode()) { /* * Under PortableMode, we could identify it by the path of ss_privoxy.exe. */ var path = process.MainModule.FileName; return Utils.GetTempPath("ss_privoxy.exe").Equals(path); } else { var cmd = process.GetCommandLine(); return cmd.Contains(UniqueConfigFile); } } catch (Exception ex) { /* * Sometimes Process.GetProcessesByName will return some processes that * are already dead, and that will cause exceptions here. * We could simply ignore those exceptions. */ Logging.LogUsefulException(ex); return false; } } private int GetFreePort() { int defaultPort = 8123; try { // TCP stack please do me a favor TcpListener l = new TcpListener(IPAddress.Loopback, 0); l.Start(); var port = ((IPEndPoint)l.LocalEndpoint).Port; l.Stop(); return port; } catch (Exception e) { // in case access denied Logging.LogUsefulException(e); return defaultPort; } } [StructLayout(LayoutKind.Sequential)] public struct RECT { public int left; public int top; public int right; public int bottom; } [DllImport("user32.dll")] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll")] public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); [DllImport("user32.dll")] public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam); public void RefreshTrayArea() { IntPtr systemTrayContainerHandle = FindWindow("Shell_TrayWnd", null); IntPtr systemTrayHandle = FindWindowEx(systemTrayContainerHandle, IntPtr.Zero, "TrayNotifyWnd", null); IntPtr sysPagerHandle = FindWindowEx(systemTrayHandle, IntPtr.Zero, "SysPager", null); IntPtr notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "Notification Area"); if (notificationAreaHandle == IntPtr.Zero) { notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "User Promoted Notification Area"); IntPtr notifyIconOverflowWindowHandle = FindWindow("NotifyIconOverflowWindow", null); IntPtr overflowNotificationAreaHandle = FindWindowEx(notifyIconOverflowWindowHandle, IntPtr.Zero, "ToolbarWindow32", "Overflow Notification Area"); RefreshTrayArea(overflowNotificationAreaHandle); } RefreshTrayArea(notificationAreaHandle); } private static void RefreshTrayArea(IntPtr windowHandle) { const uint wmMousemove = 0x0200; RECT rect; GetClientRect(windowHandle, out rect); for (var x = 0; x < rect.right; x += 5) for (var y = 0; y < rect.bottom; y += 5) SendMessage(windowHandle, wmMousemove, 0, (y << 16) + x); } } }