Commit 30501546 authored by ilushka85's avatar ilushka85

Create proper support for Upstream proxy supporting username, password, and ssl fixes

parent d3baf3f0
......@@ -69,8 +69,8 @@ Setup HTTP proxy:
};
proxyServer.AddEndPoint(transparentEndPoint);
//proxyServer.ExternalHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.ExternalHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
......
......@@ -39,6 +39,7 @@ namespace Titanium.Web.Proxy.Http
/// <param name="Connection"></param>
internal void SetConnection(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ServerConnection = Connection;
}
......@@ -59,13 +60,31 @@ namespace Titanium.Web.Proxy.Http
StringBuilder requestLines = new StringBuilder();
//prepare the request & headers
requestLines.AppendLine(string.Join(" ", new string[3]
{
if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps == true))
{
requestLines.AppendLine(string.Join(" ", new string[3]
{
this.Request.Method,
this.Request.RequestUri.AbsoluteUri,
string.Format("HTTP/{0}.{1}",this.Request.HttpVersion.Major, this.Request.HttpVersion.Minor)
}));
}
else
{
requestLines.AppendLine(string.Join(" ", new string[3]
{
this.Request.Method,
this.Request.RequestUri.PathAndQuery,
string.Format("HTTP/{0}.{1}",this.Request.HttpVersion.Major, this.Request.HttpVersion.Minor)
}));
}));
}
//Send Authentication to Upstream proxy if needed
if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false && ServerConnection.UpStreamHttpProxy.UserName != null && ServerConnection.UpStreamHttpProxy.UserName != "" && ServerConnection.UpStreamHttpProxy.Password != null)
{
requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(ServerConnection.UpStreamHttpProxy.UserName + ":" + ServerConnection.UpStreamHttpProxy.Password)));
}
//write request headers
foreach (var headerItem in this.Request.RequestHeaders)
{
......@@ -87,6 +106,9 @@ namespace Titanium.Web.Proxy.Http
string request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request);
var test = Encoding.UTF8.GetString(requestBytes);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync();
......
......@@ -5,6 +5,8 @@
/// </summary>
public class ExternalProxy
{
public string UserName { get; set; }
public string Password { get; set; }
public string HostName { get; set; }
public int Port { get; set; }
}
......
......@@ -2,6 +2,7 @@
using System.IO;
using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network
{
......@@ -10,11 +11,20 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
public class TcpConnection : IDisposable
{
internal ExternalProxy UpStreamHttpProxy { get; set; }
internal ExternalProxy UpStreamHttpsProxy { get; set; }
internal string HostName { get; set; }
internal int port { get; set; }
internal bool IsHttps { get; set; }
/// <summary>
/// Http version
/// </summary>
internal Version Version { get; set; }
internal TcpClient TcpClient { get; set; }
/// <summary>
......@@ -27,6 +37,16 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
internal Stream Stream { get; set; }
/// <summary>
/// Last time this connection was used
/// </summary>
internal DateTime LastAccess { get; set; }
internal TcpConnection()
{
LastAccess = DateTime.Now;
}
public void Dispose()
{
Stream.Close();
......
......@@ -7,6 +7,7 @@ using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
using System.Linq;
namespace Titanium.Web.Proxy.Network
{
......@@ -34,7 +35,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="clientStream"></param>
/// <returns></returns>
internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, Version httpVersion,
string remoteHostName, int remotePort, Version httpVersion,
bool isHttps, SslProtocols supportedSslProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
......@@ -55,9 +56,15 @@ namespace Titanium.Web.Proxy.Network
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true))
{
await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", remoteHostName, remotePort, httpVersion));
await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} HTTP/{2}", remoteHostName, remotePort,httpVersion));
await writer.WriteLineAsync(string.Format("Host: {0}:{1}", remoteHostName, remotePort));
await writer.WriteLineAsync("Connection: Keep-Alive");
if (externalHttpsProxy.UserName != null && externalHttpsProxy.UserName != "" && externalHttpsProxy.Password != null)
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
}
await writer.WriteLineAsync();
await writer.FlushAsync();
writer.Close();
......@@ -67,7 +74,8 @@ namespace Titanium.Web.Proxy.Network
{
var result = await reader.ReadLineAsync();
if (!result.ToLower().Contains("200 connection established"))
if (!new string[] { "200 OK", "connection established" }.Any(s=> result.ToLower().Contains(s.ToLower())))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
......@@ -119,15 +127,18 @@ namespace Titanium.Web.Proxy.Network
stream.ReadTimeout = connectionTimeOutSeconds * 1000;
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
return new TcpConnection()
{
UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy,
HostName = remoteHostName,
port = remotePort,
IsHttps = isHttps,
TcpClient = client,
StreamReader = new CustomBinaryReader(stream),
Stream = stream
Stream = stream,
Version = httpVersion
};
}
}
......
......@@ -9,6 +9,7 @@ using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using System.Linq;
using System.Security.Authentication;
using System.Collections.Concurrent;
namespace Titanium.Web.Proxy
{
......@@ -17,15 +18,11 @@ namespace Titanium.Web.Proxy
/// </summary>
public partial class ProxyServer : IDisposable
{
/// <summary>
/// Does the root certificate used by this proxy is trusted by the machine?
/// </summary>
private bool certTrusted { get; set; }
public ConcurrentQueue<ExternalProxy> CustomUpStreamHttpProxies{ get; set; }
public Func<ConcurrentQueue<ExternalProxy>, ExternalProxy> GetCustomUpStreamHttpProxyFunc = null;
public ConcurrentQueue<ExternalProxy> CustomUpStreamHttpsProxies { get; set; }
public Func<ConcurrentQueue<ExternalProxy>, ExternalProxy> GetCustomUpStreamHttpsProxyFunc = null;
/// <summary>
/// Is the proxy currently running
/// </summary>
private bool proxyRunning { get; set; }
/// <summary>
/// Manages certificates used by this proxy
......@@ -44,6 +41,16 @@ namespace Titanium.Web.Proxy
private FireFoxProxySettingsManager firefoxProxySettingsManager { get; set; }
/// <summary>
/// Does the root certificate used by this proxy is trusted by the machine?
/// </summary>
private bool certTrusted { get; set; }
/// <summary>
/// Is the proxy currently running
/// </summary>
private bool proxyRunning { get; set; }
/// <summary>
/// Buffer size used throughout this proxy
/// </summary>
......@@ -88,12 +95,12 @@ namespace Titanium.Web.Proxy
/// <summary>
/// External proxy for Http
/// </summary>
public ExternalProxy ExternalHttpProxy { get; set; }
public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary>
/// External proxy for Https
/// External proxy for Http
/// </summary>
public ExternalProxy ExternalHttpsProxy { get; set; }
public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
......@@ -390,9 +397,16 @@ namespace Titanium.Web.Proxy
}
else
{
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
}
ExternalProxy externalHttpProxy = null;
if (GetCustomUpStreamHttpProxyFunc != null)
externalHttpProxy = GetCustomUpStreamHttpProxyFunc(CustomUpStreamHttpProxies);
ExternalProxy externalHttpsProxy = null;
if (GetCustomUpStreamHttpsProxyFunc != null)
externalHttpsProxy = GetCustomUpStreamHttpsProxyFunc(CustomUpStreamHttpsProxies);
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient, externalHttpProxy, externalHttpsProxy);
}
}
finally
{
......
......@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy
{
//This is called when client is aware of proxy
//So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client)
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
{
Stream clientStream = client.GetStream();
......@@ -137,11 +137,12 @@ namespace Titanium.Web.Proxy
return;
}
//Now create the request
await HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null);
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, customUpStreamHttpProxy,customUpStreamHttpsProxy);
}
catch
catch(Exception ex)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
......@@ -213,7 +214,7 @@ namespace Titanium.Web.Proxy
/// <param name="httpsHostName"></param>
/// <returns></returns>
private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName)
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
{
TcpConnection connection = null;
......@@ -332,11 +333,12 @@ namespace Titanium.Web.Proxy
args.IsHttps, SupportedSslProtocols,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate),
ExternalHttpProxy, ExternalHttpsProxy, clientStream);
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, clientStream);
}
args.WebSession.Request.RequestLocked = true;
//If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest)
{
......@@ -344,6 +346,7 @@ namespace Titanium.Web.Proxy
break;
}
//if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue
if (args.WebSession.Request.ExpectContinue)
......@@ -418,7 +421,7 @@ namespace Titanium.Web.Proxy
httpCmd = await clientStreamReader.ReadLineAsync();
}
catch
catch(Exception e)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment