diff --git a/MainForm.Designer.cs b/MainForm.Designer.cs index 3e7f7ee..61cfffc 100644 --- a/MainForm.Designer.cs +++ b/MainForm.Designer.cs @@ -35,6 +35,7 @@ this.mainTabControl = new System.Windows.Forms.TabControl(); this.tabPage1 = new System.Windows.Forms.TabPage(); this.panel1 = new System.Windows.Forms.Panel(); + this.proxySettings = new System.Windows.Forms.Button(); this.installButton = new System.Windows.Forms.Button(); this.stateLabel = new System.Windows.Forms.Label(); this.label28 = new System.Windows.Forms.Label(); @@ -202,6 +203,7 @@ // // panel1 // + this.panel1.Controls.Add(this.proxySettings); this.panel1.Controls.Add(this.installButton); this.panel1.Controls.Add(this.stateLabel); this.panel1.Controls.Add(this.label28); @@ -219,6 +221,13 @@ resources.ApplyResources(this.panel1, "panel1"); this.panel1.Name = "panel1"; // + // proxySettings + // + resources.ApplyResources(this.proxySettings, "proxySettings"); + this.proxySettings.Name = "proxySettings"; + this.proxySettings.UseVisualStyleBackColor = true; + this.proxySettings.Click += new System.EventHandler(this.button1_Click); + // // installButton // resources.ApplyResources(this.installButton, "installButton"); @@ -1137,6 +1146,7 @@ private System.Windows.Forms.ToolStripMenuItem askConsentToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem privacyBarToolStripMenuItem; private System.Windows.Forms.PictureBox pictureBox2; + private System.Windows.Forms.Button proxySettings; private System.Windows.Forms.ToolStripMenuItem customAppsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem customAppsToolStripMenuItem1; } diff --git a/MainForm.cs b/MainForm.cs index 8dac6b4..23b2902 100644 --- a/MainForm.cs +++ b/MainForm.cs @@ -326,6 +326,7 @@ namespace MeshCentralRouter if (File.Exists(Path.Combine(selfExe.Directory.FullName, @"customization\logo.png"))) { try { pictureBox2.Image = pictureBox6.Image = (Bitmap)Image.FromFile(Path.Combine(selfExe.Directory.FullName, @"customization\logo.png")); showLicense = false; } catch (Exception) { } } if (File.Exists(Path.Combine(selfExe.Directory.FullName, @"customization\bottombanner.png"))) { try { pictureBox3.Image = pictureBox4.Image = pictureBox5.Image = pictureBox7.Image = (Bitmap)Image.FromFile(Path.Combine(selfExe.Directory.FullName, @"customization\bottombanner.png")); showLicense = false; } catch (Exception) { } } licenseLinkLabel.Visible = showLicense; + proxySettings.Visible = true; try { if (File.Exists(Path.Combine(selfExe.Directory.FullName, @"customization\customize.txt"))) @@ -2166,6 +2167,12 @@ namespace MeshCentralRouter cancelAutoClose(); } + private void button1_Click(object sender, EventArgs e) + { + ProxySettings form = new ProxySettings(); + if (form.ShowDialog(this) == DialogResult.OK) { } + } + private void customAppsToolStripMenuItem_Click(object sender, EventArgs e) { CustomAppsForm f = new CustomAppsForm(Settings.GetApplications()); diff --git a/MainForm.resx b/MainForm.resx index f067527..081a548 100644 --- a/MainForm.resx +++ b/MainForm.resx @@ -121,20 +121,59 @@ FlatButtons + + Bottom, Left + + + NoControl + + + + 185, 354 + + + 4, 4, 4, 4 + + + 127, 28 + + + + 106 + + + Manual Proxy settings + + + False + + + proxySettings + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 0 + Bottom, Left NoControl - - 11, 286 + 15, 354 + + + 4, 4, 4, 4 - 95, 23 + 127, 28 - 104 @@ -154,7 +193,7 @@ panel1 - 0 + 1 Top, Left, Right @@ -169,10 +208,13 @@ NoControl - 241, 225 + 321, 277 + + + 4, 0, 4, 0 - 88, 13 + 114, 17 19 @@ -193,7 +235,7 @@ panel1 - 1 + 2 True @@ -202,10 +244,13 @@ NoControl - 238, 176 + 317, 217 + + + 4, 0, 4, 0 - 53, 13 + 69, 17 18 @@ -223,7 +268,7 @@ panel1 - 2 + 3 True @@ -232,10 +277,13 @@ NoControl - 238, 135 + 317, 166 + + + 4, 0, 4, 0 - 55, 13 + 73, 17 17 @@ -253,7 +301,7 @@ panel1 - 3 + 4 True @@ -262,10 +310,13 @@ NoControl - 238, 92 + 317, 113 + + + 4, 0, 4, 0 - 38, 13 + 50, 17 16 @@ -283,19 +334,22 @@ panel1 - 4 + 5 Top, Left, Right - 241, 192 + 321, 236 + + + 4, 4, 4, 4 - 214, 20 + 287, 22 102 @@ -310,7 +364,7 @@ panel1 - 5 + 6 Top, Left, Right @@ -319,10 +373,13 @@ meshcentral.com - 241, 110 + 321, 135 + + + 4, 4, 4, 4 - 214, 21 + 287, 24 100 @@ -337,16 +394,19 @@ panel1 - 6 + 7 Top, Left, Right - 241, 151 + 321, 186 + + + 4, 4, 4, 4 - 214, 20 + 287, 22 101 @@ -361,7 +421,7 @@ panel1 - 7 + 8 Bottom, Right @@ -370,10 +430,13 @@ NoControl - 174, 260 + 235, 323 + + + 4, 0, 4, 0 - 303, 13 + 404, 16 103 @@ -394,7 +457,7 @@ panel1 - 8 + 9 Bottom, Left @@ -406,10 +469,13 @@ NoControl - 3, 260 + 4, 323 + + + 4, 0, 4, 0 - 37, 13 + 47, 17 11 @@ -427,7 +493,7 @@ panel1 - 9 + 10 Bottom, Right @@ -436,10 +502,13 @@ NoControl - 372, 286 + 498, 354 + + + 4, 4, 4, 4 - 95, 23 + 127, 28 105 @@ -457,7 +526,7 @@ panel1 - 10 + 11 Bottom, Left, Right @@ -466,10 +535,13 @@ NoControl - 0, 277 + 0, 343 + + + 4, 4, 4, 4 - 478, 40 + 640, 49 StretchImage @@ -487,7 +559,7 @@ panel1 - 11 + 12 Top, Left, Right @@ -496,10 +568,13 @@ NoControl - 12, 9 + 16, 11 + + + 4, 0, 4, 0 - 454, 56 + 608, 69 6 @@ -517,7 +592,7 @@ panel1 - 12 + 13 Top, Bottom, Left, Right @@ -526,10 +601,13 @@ NoControl - 15, 68 + 20, 84 + + + 4, 4, 4, 4 - 217, 170 + 292, 212 Zoom @@ -547,16 +625,19 @@ panel1 - 13 + 14 Fill - 3, 3 + 4, 4 + + + 4, 4, 4, 4 - 478, 316 + 640, 392 6 @@ -574,13 +655,16 @@ 0 - 4, 25 + 4, 28 + + + 4, 4, 4, 4 - 3, 3, 3, 3 + 4, 4, 4, 4 - 484, 322 + 648, 400 0 @@ -607,10 +691,13 @@ NoControl - 241, 221 + 321, 272 + + + 4, 4, 4, 4 - 131, 17 + 170, 21 204 @@ -637,10 +724,13 @@ NoControl - 241, 177 + 321, 218 + + + 4, 4, 4, 4 - 95, 23 + 127, 28 202 @@ -667,10 +757,13 @@ NoControl - 342, 177 + 456, 218 + + + 4, 4, 4, 4 - 95, 23 + 127, 28 203 @@ -700,10 +793,13 @@ NoControl - 238, 182 + 317, 224 + + + 4, 0, 4, 0 - 55, 13 + 73, 17 21 @@ -733,10 +829,13 @@ NoControl - 238, 135 + 317, 166 + + + 4, 0, 4, 0 - 38, 13 + 48, 17 19 @@ -760,10 +859,13 @@ Top, Left, Right - 241, 151 + 321, 186 + + + 4, 4, 4, 4 - 214, 20 + 287, 22 201 @@ -787,10 +889,13 @@ NoControl - 15, 68 + 20, 84 + + + 4, 4, 4, 4 - 202, 170 + 272, 212 Zoom @@ -817,10 +922,13 @@ NoControl - 270, 286 + 363, 354 + + + 4, 4, 4, 4 - 95, 23 + 127, 28 205 @@ -847,10 +955,13 @@ NoControl - 372, 286 + 498, 354 + + + 4, 4, 4, 4 - 95, 23 + 127, 28 206 @@ -877,10 +988,13 @@ NoControl - 0, 277 + 0, 343 + + + 4, 4, 4, 4 - 478, 40 + 640, 49 StretchImage @@ -907,10 +1021,13 @@ NoControl - 12, 9 + 16, 11 + + + 4, 0, 4, 0 - 454, 56 + 608, 69 6 @@ -934,10 +1051,13 @@ Fill - 3, 3 + 4, 4 + + + 4, 4, 4, 4 - 478, 316 + 640, 392 7 @@ -955,13 +1075,16 @@ 0 - 4, 25 + 4, 28 + + + 4, 4, 4, 4 - 3, 3, 3, 3 + 4, 4, 4, 4 - 484, 322 + 648, 400 1 @@ -988,10 +1111,13 @@ True - 208, 256 + 277, 318 + + + 4, 4, 4, 4 - 145, 17 + 190, 21 302 @@ -1015,7 +1141,10 @@ Top, Bottom, Left, Right - 15, 82 + 20, 101 + + + 4, 4, 4, 4 True @@ -1024,7 +1153,7 @@ Vertical - 450, 162 + 602, 202 25 @@ -1048,10 +1177,13 @@ NoControl - 14, 251 + 19, 313 + + + 4, 4, 4, 4 - 188, 23 + 251, 28 301 @@ -1081,10 +1213,13 @@ NoControl - 8, 6 + 11, 7 + + + 4, 0, 4, 0 - 468, 22 + 627, 27 23 @@ -1114,10 +1249,13 @@ NoControl - 12, 34 + 16, 42 + + + 4, 0, 4, 0 - 454, 45 + 608, 55 22 @@ -1144,10 +1282,13 @@ NoControl - 276, 291 + 371, 362 + + + 4, 4, 4, 4 - 95, 23 + 127, 28 303 @@ -1174,10 +1315,13 @@ NoControl - 378, 291 + 506, 362 + + + 4, 4, 4, 4 - 95, 23 + 127, 28 304 @@ -1204,10 +1348,13 @@ NoControl - 0, 282 + 0, 351 + + + 4, 4, 4, 4 - 484, 40 + 648, 49 StretchImage @@ -1233,8 +1380,11 @@ 0, 0 + + 4, 4, 4, 4 + - 484, 322 + 648, 400 8 @@ -1252,10 +1402,13 @@ 0 - 4, 25 + 4, 28 + + + 4, 4, 4, 4 - 484, 322 + 648, 400 2 @@ -1285,37 +1438,37 @@ 613, 17 - 185, 22 + 218, 26 Show &Group Names - 185, 22 + 218, 26 Show &Offline Devices - 182, 6 + 215, 6 - 185, 22 + 218, 26 Sort by &Name - 185, 22 + 218, 26 Sort by G&roup - 182, 6 + 215, 6 - 185, 22 + 218, 26 S&ettings... @@ -1327,7 +1480,7 @@ &Custom Apps... - 186, 170 + 219, 146 mainContextMenuStrip @@ -1342,10 +1495,13 @@ NoControl - 454, 2 + 608, 2 + + + 4, 0, 4, 0 - 25, 25 + 31, 31 403 @@ -1369,10 +1525,13 @@ Top, Right - 306, 5 + 411, 6 + + + 4, 4, 4, 4 - 146, 20 + 193, 22 402 @@ -1405,10 +1564,13 @@ Bottom, Left - 4, 167 + 5, 206 + + + 4, 4, 4, 4 - 168, 35 + 224, 43 405 @@ -1450,82 +1612,82 @@ Segoe UI, 9pt, style=Bold - 170, 22 + 198, 24 Add &Map... - 170, 22 + 198, 24 Add &Relay Map... - 167, 6 + 195, 6 - 171, 22 + 212, 26 Ask Consent + Bar - 171, 22 + 212, 26 Ask Consent - 171, 22 + 212, 26 Privacy Bar - 170, 22 + 198, 24 Remote Desktop... - 170, 22 + 198, 24 Remote Files... - 170, 22 + 198, 24 HTTP - 170, 22 + 198, 24 HTTPS - 170, 22 + 198, 24 RDP - 170, 22 + 198, 24 SSH - 170, 22 + 198, 24 SCP - 171, 208 + 199, 226 devicesContextMenuStrip @@ -1672,10 +1834,13 @@ - 10, 4 + 13, 5 + + + 4, 4, 4, 4 - 446, 210 + 593, 258 404 @@ -1705,10 +1870,13 @@ NoControl - 2, 88 + 3, 108 + + + 4, 0, 4, 0 - 392, 52 + 526, 64 5 @@ -1744,10 +1912,13 @@ NoControl - 2, 88 + 3, 108 + + + 4, 0, 4, 0 - 392, 52 + 526, 64 4 @@ -1773,8 +1944,11 @@ 0, 0 + + 4, 4, 4, 4 + - 470, 248 + 628, 305 50 @@ -1792,13 +1966,16 @@ 0 - 4, 25 + 4, 28 + + + 4, 4, 4, 4 - 3, 3, 3, 3 + 4, 4, 4, 4 - 470, 250 + 632, 315 0 @@ -1825,10 +2002,13 @@ NoControl - 4, 182 + 5, 221 + + + 4, 4, 4, 4 - 168, 35 + 224, 43 55 @@ -1867,10 +2047,13 @@ NoControl - 6, 79 + 8, 97 + + + 4, 0, 4, 0 - 446, 52 + 594, 64 4 @@ -1898,8 +2081,11 @@ Click "Add" to get started. 0, 0 + + 4, 4, 4, 4 + - 472, 221 + 628, 268 49 @@ -1923,10 +2109,13 @@ Click "Add" to get started. NoControl - 1, 226 + 1, 275 + + + 4, 4, 4, 4 - 20, 20 + 27, 25 54 @@ -1950,10 +2139,13 @@ Click "Add" to get started. NoControl - 240, 226 + 320, 275 + + + 4, 4, 4, 4 - 20, 20 + 27, 25 5 @@ -1980,10 +2172,13 @@ Click "Add" to get started. NoControl - 372, 223 + 496, 272 + + + 4, 4, 4, 4 - 100, 23 + 133, 28 50 @@ -2013,10 +2208,13 @@ Click "Add" to get started. NoControl - 266, 223 + 355, 272 + + + 4, 4, 4, 4 - 100, 23 + 133, 28 52 @@ -2037,13 +2235,16 @@ Click "Add" to get started. 5 - 4, 25 + 4, 28 + + + 4, 4, 4, 4 - 3, 3, 3, 3 + 4, 4, 4, 4 - 472, 253 + 632, 315 1 @@ -2064,10 +2265,13 @@ Click "Add" to get started. 1 - 3, 3 + 4, 4 + + + 4, 4, 4, 4 - 478, 279 + 640, 347 401 @@ -2091,10 +2295,13 @@ Click "Add" to get started. NoControl - 15, 291 + 20, 362 + + + 4, 4, 4, 4 - 109, 23 + 145, 28 406 @@ -2124,10 +2331,13 @@ Click "Add" to get started. NoControl - 276, 291 + 371, 362 + + + 4, 4, 4, 4 - 95, 23 + 127, 28 407 @@ -2154,10 +2364,13 @@ Click "Add" to get started. NoControl - 378, 291 + 506, 362 + + + 4, 4, 4, 4 - 95, 23 + 127, 28 408 @@ -2184,10 +2397,13 @@ Click "Add" to get started. NoControl - 0, 282 + 0, 351 + + + 4, 4, 4, 4 - 484, 40 + 648, 49 StretchImage @@ -2213,8 +2429,11 @@ Click "Add" to get started. 0, 0 + + 4, 4, 4, 4 + - 484, 322 + 648, 400 8 @@ -2232,10 +2451,13 @@ Click "Add" to get started. 0 - 4, 25 + 4, 28 + + + 4, 4, 4, 4 - 484, 322 + 648, 400 4 @@ -2265,7 +2487,7 @@ Click "Add" to get started. 0, 0, 0, 0 - 492, 351 + 656, 432 8 @@ -2289,10 +2511,13 @@ Click "Add" to get started. Fill - 0, 65 + 0, 80 + + + 4, 4, 4, 4 - 492, 351 + 656, 432 9 @@ -2318,8 +2543,11 @@ Click "Add" to get started. 0, 0 + + 4, 4, 4, 4 + - 492, 65 + 656, 80 Zoom @@ -2345,8 +2573,11 @@ Click "Add" to get started. 0, 0 + + 4, 4, 4, 4 + - 492, 416 + 656, 512 8 @@ -2376,22 +2607,22 @@ Click "Add" to get started. 418, 17 - 112, 22 + 123, 24 &Open... - 109, 6 + 120, 6 - 112, 22 + 123, 24 E&xit - 113, 54 + 124, 58 trayIconContextMenuStrip @@ -4103,22 +4334,22 @@ Click "Add" to get started. 905, 17 - 168, 22 + 193, 24 &Open Mappings... - 168, 22 + 193, 24 &Save Mappings... - 165, 6 + 190, 6 - 168, 22 + 193, 24 S&ettings... @@ -4130,7 +4361,7 @@ Click "Add" to get started. &Custom Apps... - 169, 98 + 194, 82 mappingsContextMenuStrip @@ -4160,10 +4391,10 @@ Click "Add" to get started. True - 6, 13 + 8, 16 - 492, 416 + 656, 512 @@ -5859,6 +6090,9 @@ Click "Add" to get started. AADAPwAAwD8AAMA/AADAPwAA + + 4, 4, 4, 4 + MeshCentral Router diff --git a/MeshCentralRouter.csproj b/MeshCentralRouter.csproj index 4be9387..463f0db 100644 --- a/MeshCentralRouter.csproj +++ b/MeshCentralRouter.csproj @@ -18,6 +18,10 @@ 3.5 + false + + + publish\ true Disk @@ -30,12 +34,8 @@ true 0 1.0.0.%2a - false false true - - - true @@ -124,6 +124,11 @@ FileDialogMsgForm.cs + + Form + + + ProxySettings.cs Component @@ -310,6 +315,9 @@ FileDialogMsgForm.cs + + ProxySettings.cs + KVMViewerExtra.cs diff --git a/MeshCentralServer.cs b/MeshCentralServer.cs index a2363e7..2027dfe 100644 --- a/MeshCentralServer.cs +++ b/MeshCentralServer.cs @@ -97,31 +97,6 @@ namespace MeshCentralRouter try { return Registry.GetValue(@"HKEY_CURRENT_USER\SOFTWARE\OpenSource\MeshRouter", name, "").ToString(); } catch (Exception) { return ""; } } - public static string GetProxyForUrlUsingPac(string DestinationUrl, string PacUri) - { - IntPtr WinHttpSession = Win32Api.WinHttpOpen("User", Win32Api.WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, IntPtr.Zero, IntPtr.Zero, 0); - - Win32Api.WINHTTP_AUTOPROXY_OPTIONS ProxyOptions = new Win32Api.WINHTTP_AUTOPROXY_OPTIONS(); - Win32Api.WINHTTP_PROXY_INFO ProxyInfo = new Win32Api.WINHTTP_PROXY_INFO(); - - ProxyOptions.dwFlags = Win32Api.WINHTTP_AUTOPROXY_CONFIG_URL; - ProxyOptions.dwAutoDetectFlags = (Win32Api.WINHTTP_AUTO_DETECT_TYPE_DHCP | Win32Api.WINHTTP_AUTO_DETECT_TYPE_DNS_A); - ProxyOptions.lpszAutoConfigUrl = PacUri; - - // Get Proxy - bool IsSuccess = Win32Api.WinHttpGetProxyForUrl(WinHttpSession, DestinationUrl, ref ProxyOptions, ref ProxyInfo); - Win32Api.WinHttpCloseHandle(WinHttpSession); - - if (IsSuccess) - { - return ProxyInfo.lpszProxy; - } - else - { - Console.WriteLine("Error: {0}", Win32Api.GetLastError()); - return null; - } - } // Parse the URL query parameters and returns a collection public static NameValueCollection GetQueryStringParameters() @@ -759,8 +734,424 @@ namespace MeshCentralRouter public string Base64Encode(string plainText) { - var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); - return System.Convert.ToBase64String(plainTextBytes); + private MeshCentralServer parent = null; + private TcpClient wsclient = null; + private SslStream wsstream = null; + private NetworkStream wsrawstream = null; + private int state = 0; + private Uri url = null; + private byte[] readBuffer = new Byte[500]; + private int readBufferLen = 0; + private int accopcodes = 0; + private bool accmask = false; + private int acclen = 0; + private bool proxyInUse = false; + private Uri proxyUri = null; + private string user = null; + private string pass = null; + private string token = null; + public bool xdebug = false; + public bool xtlsdump = false; + public bool xignoreCert = false; + + public void Dispose() { + try { wsstream.Close(); } catch (Exception) { } + try { wsstream.Dispose(); } catch (Exception) { } + wsstream = null; + wsclient = null; + state = -1; + parent.changeState(0); + parent.wshash = null; + } + + public void Debug(string msg) { if (xdebug) { try { File.AppendAllText("debug.log", "Debug-" + msg + "\r\n"); } catch (Exception) { } } } + public void TlsDump(string direction, byte[] data, int offset, int len) { if (xtlsdump) { try { File.AppendAllText("debug.log", direction + ": " + BitConverter.ToString(data, offset, len).Replace("-", string.Empty) + "\r\n"); } catch (Exception) { } } } + + public bool Start(MeshCentralServer parent, Uri url, string user, string pass, string token, string fingerprint) + { + if (state != 0) return false; + parent.changeState(1); + state = 1; + this.parent = parent; + this.url = url; + this.user = user; + this.pass = pass; + this.token = token; + this.proxyUri = null; + + this.proxyUri = Win32Api.GetProxy(url); + + if (proxyUri != null) + { + // Proxy in use + proxyInUse = true; + wsclient = new TcpClient(); + // This may log proxy password to debug log + Debug("Connecting with proxy in use: " + proxyUri.ToString()); + wsclient.BeginConnect(proxyUri.Host, proxyUri.Port, new AsyncCallback(OnConnectSink), this); + } + else + { + // No proxy in use + proxyInUse = false; + wsclient = new TcpClient(); + Debug("Connecting without proxy"); + wsclient.BeginConnect(url.Host, url.Port, new AsyncCallback(OnConnectSink), this); + } + return true; + } + + private void OnConnectSink(IAsyncResult ar) + { + if (wsclient == null) return; + + // Accept the connection + try + { + wsclient.EndConnect(ar); + } catch (Exception ex) { + Debug("Websocket TCP failed to connect: " + ex.ToString()); + Dispose(); + return; + } + + if (proxyInUse == true) + { + // Send proxy connection request + wsrawstream = wsclient.GetStream(); + string userCreds = proxyUri.UserInfo; + string basicAuth = ""; + if (userCreds.Length > 0) + { + // Base64 encode for basic authentication + userCreds = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(userCreds)); + basicAuth = "\r\nProxy-Authorization: Basic " + userCreds; + } + + byte[] proxyRequestBuf = UTF8Encoding.UTF8.GetBytes("CONNECT " + url.Host + ":" + url.Port + " HTTP/1.1\r\nHost: " + url.Host + ":" + url.Port + basicAuth + "\r\n\r\n"); + TlsDump("OutRaw", proxyRequestBuf, 0, proxyRequestBuf.Length); + try { wsrawstream.Write(proxyRequestBuf, 0, proxyRequestBuf.Length); } catch (Exception ex) { Debug(ex.ToString()); } + wsrawstream.BeginRead(readBuffer, readBufferLen, readBuffer.Length - readBufferLen, new AsyncCallback(OnProxyResponseSink), this); + } + else + { + // Start TLS connection + Debug("Websocket TCP connected, doing TLS..."); + wsstream = new SslStream(wsclient.GetStream(), false, VerifyServerCertificate, null); + wsstream.BeginAuthenticateAsClient(url.Host, null, System.Security.Authentication.SslProtocols.Tls12, false, new AsyncCallback(OnTlsSetupSink), this); + } + } + + private void OnProxyResponseSink(IAsyncResult ar) + { + if (wsrawstream == null) return; + + int len = 0; + try { len = wsrawstream.EndRead(ar); } catch (Exception) { } + if (len == 0) + { + // Disconnect + Debug("Websocket proxy disconnected, length = 0."); + Dispose(); + return; + } + + TlsDump("InRaw", readBuffer, 0, readBufferLen); + + readBufferLen += len; + string proxyResponse = UTF8Encoding.UTF8.GetString(readBuffer, 0, readBufferLen); + if (proxyResponse.IndexOf("\r\n\r\n") >= 0) + { + // We get a full proxy response, we should get something like "HTTP/1.1 200 Connection established\r\n\r\n" + if (proxyResponse.StartsWith("HTTP/1.1 200 ")) + { + // All good, start TLS setup. + readBufferLen = 0; + Debug("Websocket TCP connected, doing TLS..."); + wsstream = new SslStream(wsrawstream, false, VerifyServerCertificate, null); + wsstream.BeginAuthenticateAsClient(url.Host, null, System.Security.Authentication.SslProtocols.Tls12, false, new AsyncCallback(OnTlsSetupSink), this); + } + else + { + // Invalid response + Debug("Proxy connection failed: " + proxyResponse); + Dispose(); + } + } else { + if (readBufferLen == readBuffer.Length) + { + // Buffer overflow + Debug("Proxy connection failed"); + Dispose(); + } + else + { + // Read more proxy data + wsrawstream.BeginRead(readBuffer, readBufferLen, readBuffer.Length - readBufferLen, new AsyncCallback(OnProxyResponseSink), this); + } + } + } + + public string Base64Encode(string plainText) + { + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return System.Convert.ToBase64String(plainTextBytes); + } + + public string Base64Decode(string base64EncodedData) + { + var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData); + return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); + } + + private void OnTlsSetupSink(IAsyncResult ar) + { + if (wsstream == null) return; + + // Accept the connection + try + { + wsstream.EndAuthenticateAsClient(ar); + } + catch (Exception ex) + { + // Disconnect + if (ex.InnerException != null) { + MessageBox.Show(ex.Message + ", Inner: " + ex.InnerException.ToString(), "MeshCentral Router"); + } else { + MessageBox.Show(ex.Message, "MeshCentral Router"); + } + Debug("Websocket TLS failed: " + ex.ToString()); + Dispose(); + return; + } + + // Fetch remote certificate + parent.wshash = wsstream.RemoteCertificate.GetCertHashString(); + + // Setup extra headers if needed + string extraHeaders = ""; + if (user != null && pass != null && token != null) { extraHeaders = "x-meshauth: " + Base64Encode(user) + "," + Base64Encode(pass) + "," + Base64Encode(token) + "\r\n"; } + else if (user != null && pass != null) { extraHeaders = "x-meshauth: " + Base64Encode(user) + "," + Base64Encode(pass) + "\r\n"; } + + // Send the HTTP headers + Debug("Websocket TLS setup, sending HTTP header..."); + string header = "GET " + url.PathAndQuery + " HTTP/1.1\r\nHost: " + url.Host + "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n" + extraHeaders + "\r\n"; + try { wsstream.Write(UTF8Encoding.UTF8.GetBytes(header)); } catch (Exception ex) { Debug(ex.ToString()); } + + // Start receiving data + wsstream.BeginRead(readBuffer, readBufferLen, readBuffer.Length - readBufferLen, new AsyncCallback(OnTlsDataSink), this); + } + + private void OnTlsDataSink(IAsyncResult ar) + { + if (wsstream == null) return; + + int len = 0; + try { len = wsstream.EndRead(ar); } catch (Exception) { } + if (len == 0) + { + // Disconnect + Debug("Websocket disconnected, length = 0."); + Dispose(); + return; + } + //parent.Debug("#" + counter + ": Websocket got new data: " + len); + readBufferLen += len; + TlsDump("In", readBuffer, 0, len); + + // Consume all of the data + int consumed = 0; + int ptr = 0; + do + { + consumed = ProcessBuffer(readBuffer, ptr, readBufferLen - ptr); + if (consumed < 0) { Dispose(); return; } // Error, close the connection + ptr += consumed; + } while ((consumed > 0) && ((readBufferLen - consumed) > 0)); + + // Move the data forward + if ((ptr > 0) && (readBufferLen - ptr) > 0) { + //Console.Write("MOVE FORWARD\r\n"); + Array.Copy(readBuffer, ptr, readBuffer, 0, (readBufferLen - ptr)); + } + readBufferLen = (readBufferLen - ptr); + + // If the buffer is too small, double the size here. + if (readBuffer.Length - readBufferLen == 0) + { + Debug("Increasing the read buffer size from " + readBuffer.Length + " to " + (readBuffer.Length * 2) + "."); + byte[] readBuffer2 = new byte[readBuffer.Length * 2]; + Array.Copy(readBuffer, 0, readBuffer2, 0, readBuffer.Length); + readBuffer = readBuffer2; + } + + // Receive more data + try { wsstream.BeginRead(readBuffer, readBufferLen, readBuffer.Length - readBufferLen, new AsyncCallback(OnTlsDataSink), this); } catch (Exception) { } + } + + private int ProcessBuffer(byte[] buffer, int offset, int len) + { + string ss = UTF8Encoding.UTF8.GetString(buffer, offset, len); + + if (state == 1) + { + // Look for the end of the http header + string header = UTF8Encoding.UTF8.GetString(buffer, offset, len); + int i = header.IndexOf("\r\n\r\n"); + if (i == -1) return 0; + Dictionary parsedHeader = ParseHttpHeader(header.Substring(0, i)); + if ((parsedHeader == null) || (parsedHeader["_Path"] != "101")) { Debug("Websocket bad header."); return -1; } // Bad header, close the connection + Debug("Websocket got setup upgrade header."); + state = 2; + return len; // TODO: Technically we need to return the header length before UTF8 convert. + } else if (state == 2) { + // Parse a websocket fragment header + if (len < 2) return 0; + int headsize = 2; + accopcodes = buffer[offset]; + accmask = ((buffer[offset + 1] & 0x80) != 0); + acclen = (buffer[offset + 1] & 0x7F); + + if ((accopcodes & 0x0F) == 8) + { + // Close the websocket + Debug("Websocket got closed fragment."); + return -1; + } + + if (acclen == 126) + { + if (len < 4) return 0; + headsize = 4; + acclen = (buffer[offset + 2] << 8) + (buffer[offset + 3]); + } + else if (acclen == 127) + { + if (len < 10) return 0; + headsize = 10; + acclen = (buffer[offset + 6] << 24) + (buffer[offset + 7] << 16) + (buffer[offset + 8] << 8) + (buffer[offset + 9]); + Debug("Websocket receive large fragment: " + acclen); + } + if (accmask == true) + { + // TODO: Do unmasking here. + headsize += 4; + } + //parent.Debug("#" + counter + ": Websocket frag header - FIN: " + ((accopcodes & 0x80) != 0) + ", OP: " + (accopcodes & 0x0F) + ", LEN: " + acclen + ", MASK: " + accmask); + state = 3; + return headsize; + } + else if (state == 3) + { + // Parse a websocket fragment data + if (len < acclen) return 0; + //Console.Write("WSREAD: " + acclen + "\r\n"); + ProcessWsBuffer(buffer, offset, acclen, accopcodes); + state = 2; + return acclen; + } + return 0; + } + + private void ProcessWsBuffer(byte[] data, int offset, int len, int op) + { + Debug("Websocket got data."); + //try { parent.processServerData(UTF8Encoding.UTF8.GetString(data, offset, len)); } catch (Exception ex) { } + parent.processServerData(UTF8Encoding.UTF8.GetString(data, offset, len)); + } + + private Dictionary ParseHttpHeader(string header) + { + string[] lines = header.Replace("\r\n", "\r").Split('\r'); + if (lines.Length < 2) { return null; } + string[] directive = lines[0].Split(' '); + Dictionary values = new Dictionary(); + values["_Action"] = directive[0]; + values["_Path"] = directive[1]; + values["_Protocol"] = directive[2]; + for (int i = 1; i < lines.Length; i++) + { + var j = lines[i].IndexOf(":"); + values[lines[i].Substring(0, j).ToLower()] = lines[i].Substring(j + 1).Trim(); + } + return values; + } + + // Return a modified base64 SHA384 hash string of the certificate public key + public static string GetMeshKeyHash(X509Certificate cert) + { + return ByteArrayToHexString(new SHA384Managed().ComputeHash(cert.GetPublicKey())); + } + + // Return a modified base64 SHA384 hash string of the certificate + public static string GetMeshCertHash(X509Certificate cert) + { + return ByteArrayToHexString(new SHA384Managed().ComputeHash(cert.GetRawCertData())); + } + + public static string ByteArrayToHexString(byte[] Bytes) + { + StringBuilder Result = new StringBuilder(Bytes.Length * 2); + string HexAlphabet = "0123456789ABCDEF"; + foreach (byte B in Bytes) { Result.Append(HexAlphabet[(int)(B >> 4)]); Result.Append(HexAlphabet[(int)(B & 0xF)]); } + return Result.ToString(); + } + + private bool VerifyServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + parent.certHash = GetMeshKeyHash(certificate); + Debug("Verify cert: " + parent.certHash); + if (xignoreCert) return true; + if (chain.Build(new X509Certificate2(certificate)) == true) return true; + + // Check that the remote certificate is the expected one + if ((parent.okCertHash != null) && (parent.okCertHash == certificate.GetCertHashString())) return true; + + // Check that the remote certificate is the expected one + if ((parent.okCertHash2 != null) && ((parent.okCertHash2 == GetMeshKeyHash(certificate)) || (parent.okCertHash2 == GetMeshCertHash(certificate)))) { return true; } + + parent.certHash = null; + parent.disconnectMsg = "cert"; + parent.disconnectCert = new X509Certificate2(certificate); + return false; + } + + public void WriteStringWebSocket(string data) + { + // Convert the string into a buffer with 4 byte of header space. + int len = UTF8Encoding.UTF8.GetByteCount(data); + byte[] buf = new byte[4 + len]; + UTF8Encoding.UTF8.GetBytes(data, 0, data.Length, buf, 4); + len = buf.Length - 4; + + // Check that everything is ok + if ((state < 2) || (len < 1) || (len > 65535)) { Dispose(); return; } + + //Console.Write("Length: " + len + "\r\n"); + //System.Threading.Thread.Sleep(0); + + if (len < 126) + { + // Small fragment + buf[2] = 130; // Fragment op code (129 = text, 130 = binary) + buf[3] = (byte)(len & 0x7F); + //try { wsstream.BeginWrite(buf, 2, len + 2, new AsyncCallback(WriteWebSocketAsyncDone), args); } catch (Exception) { Dispose(); return; } + TlsDump("Out", buf, 2, len + 2); + try { wsstream.Write(buf, 2, len + 2); } catch (Exception ex) { Debug(ex.ToString()); } + } + else + { + // Large fragment + buf[0] = 130; // Fragment op code (129 = text, 130 = binary) + buf[1] = 126; + buf[2] = (byte)((len >> 8) & 0xFF); + buf[3] = (byte)(len & 0xFF); + //try { wsstream.BeginWrite(buf, 0, len + 4, new AsyncCallback(WriteWebSocketAsyncDone), args); } catch (Exception) { Dispose(); return; } + TlsDump("Out", buf, 0, len + 4); + try { wsstream.Write(buf, 0, len + 4); } catch (Exception ex) { Debug(ex.ToString()); } + } + } } public string Base64Decode(string base64EncodedData) diff --git a/ProxySettings.Designer.cs b/ProxySettings.Designer.cs new file mode 100644 index 0000000..49a0bf5 --- /dev/null +++ b/ProxySettings.Designer.cs @@ -0,0 +1,194 @@ +namespace MeshCentralRouter +{ + partial class ProxySettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.SaveProxyConfig = new System.Windows.Forms.Button(); + this.manualHttpProxyHost = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.manualHttpProxyPort = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.manualHttpProxyUsername = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.manualHttpProxyPassword = new System.Windows.Forms.TextBox(); + this.label5 = new System.Windows.Forms.Label(); + this.useManualProxySettings = new System.Windows.Forms.CheckBox(); + this.cancel = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // SaveProxyConfig + // + this.SaveProxyConfig.Location = new System.Drawing.Point(124, 404); + this.SaveProxyConfig.Name = "SaveProxyConfig"; + this.SaveProxyConfig.Size = new System.Drawing.Size(75, 23); + this.SaveProxyConfig.TabIndex = 1; + this.SaveProxyConfig.Text = "Ok"; + this.SaveProxyConfig.UseVisualStyleBackColor = true; + this.SaveProxyConfig.Click += new System.EventHandler(this.SaveProxyConfig_Click); + // + // manualHttpProxyHost + // + this.manualHttpProxyHost.Location = new System.Drawing.Point(104, 141); + this.manualHttpProxyHost.Name = "manualHttpProxyHost"; + this.manualHttpProxyHost.Size = new System.Drawing.Size(203, 22); + this.manualHttpProxyHost.TabIndex = 2; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(75, 114); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(103, 17); + this.label1.TabIndex = 3; + this.label1.Text = "Http proxy host"; + this.label1.Click += new System.EventHandler(this.label1_Click); + // + // manualHttpProxyPort + // + this.manualHttpProxyPort.Location = new System.Drawing.Point(104, 208); + this.manualHttpProxyPort.Name = "manualHttpProxyPort"; + this.manualHttpProxyPort.Size = new System.Drawing.Size(203, 22); + this.manualHttpProxyPort.TabIndex = 4; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(75, 181); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(101, 17); + this.label2.TabIndex = 5; + this.label2.Text = "Http proxy port"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(75, 257); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(139, 17); + this.label3.TabIndex = 7; + this.label3.Text = "Http proxy username"; + // + // manualHttpProxyUsername + // + this.manualHttpProxyUsername.Location = new System.Drawing.Point(104, 286); + this.manualHttpProxyUsername.Name = "manualHttpProxyUsername"; + this.manualHttpProxyUsername.Size = new System.Drawing.Size(203, 22); + this.manualHttpProxyUsername.TabIndex = 6; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(78, 332); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(136, 17); + this.label4.TabIndex = 9; + this.label4.Text = "Http proxy password"; + // + // manualHttpProxyPassword + // + this.manualHttpProxyPassword.Location = new System.Drawing.Point(104, 362); + this.manualHttpProxyPassword.Name = "manualHttpProxyPassword"; + this.manualHttpProxyPassword.PasswordChar = '*'; + this.manualHttpProxyPassword.Size = new System.Drawing.Size(203, 22); + this.manualHttpProxyPassword.TabIndex = 8; + // + // label5 + // + this.label5.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label5.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.label5.Location = new System.Drawing.Point(13, 9); + this.label5.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(396, 69); + this.label5.TabIndex = 10; + this.label5.Text = "Manually configure an HTTP proxy server to use. Username and password are optiona" + + "l. Only basic auth is supported."; + this.label5.UseMnemonic = false; + // + // useManualProxySettings + // + this.useManualProxySettings.AutoSize = true; + this.useManualProxySettings.Location = new System.Drawing.Point(43, 81); + this.useManualProxySettings.Name = "useManualProxySettings"; + this.useManualProxySettings.Size = new System.Drawing.Size(194, 21); + this.useManualProxySettings.TabIndex = 11; + this.useManualProxySettings.Text = "use manual proxy settings"; + this.useManualProxySettings.UseVisualStyleBackColor = true; + this.useManualProxySettings.CheckedChanged += new System.EventHandler(this.useManualProxySettings_CheckedChanged); + // + // cancel + // + this.cancel.Location = new System.Drawing.Point(232, 404); + this.cancel.Name = "cancel"; + this.cancel.Size = new System.Drawing.Size(75, 23); + this.cancel.TabIndex = 12; + this.cancel.Text = "Cancel"; + this.cancel.UseVisualStyleBackColor = true; + this.cancel.Click += new System.EventHandler(this.cancel_Click); + // + // ProxySettings + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(482, 450); + this.Controls.Add(this.cancel); + this.Controls.Add(this.useManualProxySettings); + this.Controls.Add(this.label5); + this.Controls.Add(this.label4); + this.Controls.Add(this.manualHttpProxyPassword); + this.Controls.Add(this.label3); + this.Controls.Add(this.manualHttpProxyUsername); + this.Controls.Add(this.label2); + this.Controls.Add(this.manualHttpProxyPort); + this.Controls.Add(this.label1); + this.Controls.Add(this.manualHttpProxyHost); + this.Controls.Add(this.SaveProxyConfig); + this.Name = "ProxySettings"; + this.Text = "Proxy Settings"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.Button SaveProxyConfig; + private System.Windows.Forms.TextBox manualHttpProxyHost; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox manualHttpProxyPort; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TextBox manualHttpProxyUsername; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.TextBox manualHttpProxyPassword; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.CheckBox useManualProxySettings; + private System.Windows.Forms.Button cancel; + } +} \ No newline at end of file diff --git a/ProxySettings.cs b/ProxySettings.cs new file mode 100644 index 0000000..28d010b --- /dev/null +++ b/ProxySettings.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace MeshCentralRouter +{ + public partial class ProxySettings : Form + { + public ProxySettings() + { + InitializeComponent(); + useManualProxySettings.Checked = Settings.GetRegValue("Use_Manual_Http_proxy", false); + manualHttpProxyHost.Text = Settings.GetRegValue("Manual_Http_proxy_host", ""); + manualHttpProxyPort.Text = Settings.GetRegValue("Manual_Http_proxy_port", ""); + manualHttpProxyUsername.Text = Settings.GetRegValue("Manual_Http_proxy_username", ""); + manualHttpProxyPassword.Text = Settings.GetRegValue("Manual_Http_proxy_password", ""); + checkbox_refresh_form(); + } + + private void label1_Click(object sender, EventArgs e) + { + + } + + private void SaveProxyConfig_Click(object sender, EventArgs e) + { + Settings.SetRegValue("Use_Manual_Http_proxy", useManualProxySettings.Checked); + Settings.SetRegValue("Manual_Http_proxy_host", manualHttpProxyHost.Text); + Settings.SetRegValue("Manual_Http_proxy_port", manualHttpProxyPort.Text); + Settings.SetRegValue("Manual_Http_proxy_username", manualHttpProxyUsername.Text); + Settings.SetRegValue("Manual_Http_proxy_password", manualHttpProxyPassword.Text); + DialogResult = DialogResult.OK; + } + + private void checkbox_refresh_form() + { + if (useManualProxySettings.Checked) + { + manualHttpProxyHost.ReadOnly = false; + manualHttpProxyPort.ReadOnly = false; + manualHttpProxyUsername.ReadOnly = false; + manualHttpProxyPassword.ReadOnly = false; + } + else + { + manualHttpProxyHost.ReadOnly = true; + manualHttpProxyPort.ReadOnly = true; + manualHttpProxyUsername.ReadOnly = true; + manualHttpProxyPassword.ReadOnly = true; + } + } + + private void useManualProxySettings_CheckedChanged(object sender, EventArgs e) + { + checkbox_refresh_form(); + } + + private void cancel_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + } + } +} diff --git a/ProxySettings.resx b/ProxySettings.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/ProxySettings.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/WebSocketClient.cs b/WebSocketClient.cs index b1bfe2f..2969a33 100644 --- a/WebSocketClient.cs +++ b/WebSocketClient.cs @@ -49,6 +49,7 @@ namespace MeshCentralRouter private bool accmask = false; private int acclen = 0; private bool proxyInUse = false; + private Uri proxyUri = null; private string tlsCertFingerprint = null; private string tlsCertFingerprint2 = null; //private ConnectionErrors lastError = ConnectionErrors.NoError; @@ -160,6 +161,13 @@ namespace MeshCentralRouter private async Task ConnectAsync(Uri url) { + if (state != ConnectionStates.Disconnected) return false; + SetState(ConnectionStates.Connecting); + this.url = url; + if (tlsCertFingerprint != null) { this.tlsCertFingerprint = tlsCertFingerprint.ToUpper(); } + Log("Websocket Start, URL=" + ((url == null) ? "(NULL)" : url.ToString())); + + proxyUri = Win32Api.GetProxy(url); if (CTS != null) CTS.Dispose(); CTS = new CancellationTokenSource(); try { await ws.ConnectAsync(url, CTS.Token); } catch (Exception) { SetState(0); return; } @@ -188,7 +196,6 @@ namespace MeshCentralRouter this.url = url; if (tlsCertFingerprint != null) { this.tlsCertFingerprint = tlsCertFingerprint.ToUpper(); } if (tlsCertFingerprint2 != null) { this.tlsCertFingerprint2 = tlsCertFingerprint2.ToUpper(); } - if (nativeWebSocketFirst) { try { ws = new ClientWebSocket(); } catch (Exception) { } } if (ws != null) { @@ -279,7 +286,17 @@ namespace MeshCentralRouter { // Send proxy connection request wsrawstream = wsclient.GetStream(); - byte[] proxyRequestBuf = UTF8Encoding.UTF8.GetBytes("CONNECT " + url.Host + ":" + url.Port + " HTTP/1.1\r\nHost: " + url.Host + ":" + url.Port + "\r\n\r\n"); + + string userCreds = proxyUri.UserInfo; + string basicAuth = ""; + if (userCreds.Length > 0) + { + // Base64 encode for basic authentication + userCreds = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(userCreds)); + basicAuth = "\r\nProxy-Authorization: Basic " + userCreds; + } + + byte[] proxyRequestBuf = UTF8Encoding.UTF8.GetBytes("CONNECT " + url.Host + ":" + url.Port + " HTTP/1.1\r\nHost: " + url.Host + ":" + url.Port + basicAuth + "\r\n\r\n"); wsrawstream.Write(proxyRequestBuf, 0, proxyRequestBuf.Length); wsrawstream.BeginRead(readBuffer, readBufferLen, readBuffer.Length - readBufferLen, new AsyncCallback(OnProxyResponseSink), this); } diff --git a/Win32Api.cs b/Win32Api.cs index 6e2643b..e2f8776 100644 --- a/Win32Api.cs +++ b/Win32Api.cs @@ -78,6 +78,24 @@ namespace MeshCentralRouter public static Uri GetProxy(Uri url) { + // Manual http proxy with optional basic auth. Other auth type would be difficult to support as proxy authentication is done manually within the websocketclient + if(Settings.GetRegValue("Use_Manual_Http_proxy", false)) + { + string proxyStr = ""; + string proxyUserName = Settings.GetRegValue("Manual_Http_proxy_username", ""); + string proxyPassword = Settings.GetRegValue("Manual_Http_proxy_password", ""); + + if (proxyUserName.Length > 0 && proxyPassword.Length > 0) + { + proxyStr = "http://" + proxyUserName + ":" + proxyPassword + "@" + Settings.GetRegValue("Manual_Http_proxy_host", "") + ":" + Settings.GetRegValue("Manual_Http_proxy_port", ""); + } + else + { + proxyStr = "http://" + Settings.GetRegValue("Manual_Http_proxy_host", "") + ":" + Settings.GetRegValue("Manual_Http_proxy_port", ""); + + } + return new Uri(proxyStr); + } // Check if we need to use a HTTP proxy (Auto-proxy way) try { @@ -86,7 +104,10 @@ namespace MeshCentralRouter if ((x != null) && (x.GetType() == typeof(string))) { string proxyStr = GetProxyForUrlUsingPac("http" + ((url.Port == 80) ? "" : "s") + "://" + url.Host + ":" + url.Port, x.ToString()); - return new Uri("http://" + proxyStr); + if (proxyStr != null) + { + return new Uri("http://" + proxyStr); + } } } catch (Exception) { }