/// <summary> /// Handle dispose of a client/server session /// </summary> /// <param name="tcpClient"></param> /// <param name="clientStream"></param> /// <param name="clientStreamReader"></param> /// <param name="clientStreamWriter"></param> /// <param name="args"></param> private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, IDisposable args) { if (clientStream != null) { clientStream.Close(); clientStream.Dispose(); } if (args != null) { args.Dispose(); } if (clientStreamReader != null) { clientStreamReader.Dispose(); } if (clientStreamWriter != null) { clientStreamWriter.Close(); clientStreamWriter.Dispose(); } }
/// <summary> /// Handle dispose of a client/server session /// </summary> /// <param name="clientStream"></param> /// <param name="clientStreamReader"></param> /// <param name="clientStreamWriter"></param> /// <param name="serverConnection"></param> private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection) { clientStream?.Dispose(); clientStreamReader?.Dispose(); clientStreamWriter?.Dispose(); if (serverConnection != null) { serverConnection.Dispose(); UpdateServerConnectionCount(false); } }
/// <summary> /// This is called when this proxy acts as a reverse proxy (like a real http server) /// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client /// </summary> /// <param name="endPoint"></param> /// <param name="tcpClient"></param> /// <returns></returns> private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient) { var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize); try { if (endPoint.EnableSsl) { var clientHelloInfo = await SslTools.PeekClientHello(clientStream); if (clientHelloInfo != null) { var sslStream = new SslStream(clientStream); clientStream = new CustomBufferedStream(sslStream, BufferSize); string sniHostName = clientHelloInfo.GetServerName() ?? endPoint.GenericCertificateName; string certName = HttpHelper.GetWildCardDomainName(sniHostName); var certificate = await CertificateManager.CreateCertificateAsync(certName); try { //Successfully managed to authenticate the client using the fake certificate await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false); } catch (Exception e) { ExceptionFunc(new Exception($"Could'nt authenticate client '{sniHostName}' with fake certificate.", e)); return; } } //HTTPS server created - we can now decrypt the client's traffic } //Now create the request await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter, endPoint.EnableSsl?endPoint.GenericCertificateName : null, endPoint, null, true); } finally { clientStreamReader.Dispose(); clientStream.Dispose(); } }
/// <summary> /// Handle dispose of a client/server session /// </summary> /// <param name="clientStream"></param> /// <param name="clientStreamReader"></param> /// <param name="clientStreamWriter"></param> /// <param name="serverConnection"></param> private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, TcpConnection serverConnection) { clientStream?.Close(); clientStream?.Dispose(); clientStreamReader?.Dispose(); clientStreamWriter?.Dispose(); if (serverConnection != null) { serverConnection.Dispose(); Interlocked.Decrement(ref serverConnectionCount); } }
/// <summary> /// Handle dispose of a client/server session /// </summary> /// <param name="clientStream"></param> /// <param name="clientStreamReader"></param> /// <param name="clientStreamWriter"></param> /// <param name="args"></param> private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, IDisposable args) { if (clientStream != null) { clientStream.Close(); clientStream.Dispose(); } args?.Dispose(); clientStreamReader?.Dispose(); if (clientStreamWriter == null) { return; } clientStreamWriter.Close(); clientStreamWriter.Dispose(); }
/// <summary> /// 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 /// </summary> /// <param name="endPoint"></param> /// <param name="tcpClient"></param> /// <returns></returns> private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient) { bool disposed = false; var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamWriter = new HttpResponseWriter(clientStream); Uri httpRemoteUri; try { //read the first line HTTP command string httpCmd = await clientStreamReader.ReadLineAsync(); if (string.IsNullOrEmpty(httpCmd)) { return; } string httpMethod; string httpUrl; Version version; Request.ParseRequestLine(httpCmd, out httpMethod, out httpUrl, out version); httpRemoteUri = httpMethod == "CONNECT" ? new Uri("http://" + httpUrl) : new Uri(httpUrl); //filter out excluded host names bool excluded = false; if (endPoint.ExcludedHttpsHostNameRegex != null) { excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host)); } if (endPoint.IncludedHttpsHostNameRegex != null) { excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host)); } ConnectRequest connectRequest = null; //Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request) if (httpMethod == "CONNECT") { connectRequest = new ConnectRequest { RequestUri = httpRemoteUri, OriginalRequestUrl = httpUrl, HttpVersion = version, Method = httpMethod, }; await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.RequestHeaders); var connectArgs = new TunnelConnectSessionEventArgs(endPoint); connectArgs.WebSession.Request = connectRequest; connectArgs.ProxyClient.TcpClient = tcpClient; connectArgs.ProxyClient.ClientStream = clientStream; if (TunnelConnectRequest != null) { await TunnelConnectRequest.InvokeParallelAsync(this, connectArgs, ExceptionFunc); } if (!excluded && await CheckAuthorization(clientStreamWriter, connectArgs) == false) { if (TunnelConnectResponse != null) { await TunnelConnectResponse.InvokeParallelAsync(this, connectArgs, ExceptionFunc); } return; } //write back successfull CONNECT response connectArgs.WebSession.Response = ConnectResponse.CreateSuccessfullConnectResponse(version); await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response); var clientHelloInfo = await SslTools.GetClientHelloInfo(clientStream); bool isClientHello = clientHelloInfo != null; if (isClientHello) { connectRequest.ClientHelloInfo = clientHelloInfo; } if (TunnelConnectResponse != null) { connectArgs.IsHttpsConnect = isClientHello; await TunnelConnectResponse.InvokeParallelAsync(this, connectArgs, ExceptionFunc); } if (!excluded && isClientHello) { httpRemoteUri = new Uri("https://" + httpUrl); connectRequest.RequestUri = httpRemoteUri; SslStream sslStream = null; try { var alpnStream = AlpnEnabled ? (Stream) new ServerHelloAlpnAdderStream(clientStream) : clientStream; sslStream = new SslStream(alpnStream); string certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host); var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false); //Successfully managed to authenticate the client using the fake certificate await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false); //HTTPS server created - we can now decrypt the client's traffic clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStreamReader.Dispose(); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamWriter = new HttpResponseWriter(clientStream); } catch { sslStream?.Dispose(); return; } //Now read the actual HTTPS request line httpCmd = await clientStreamReader.ReadLineAsync(); } //Hostname is excluded or it is not an HTTPS connect else { //create new connection using (var connection = await GetServerConnection(connectArgs, true)) { if (isClientHello) { if (clientStream.Available > 0) { //send the buffered data var data = new byte[clientStream.Available]; await clientStream.ReadAsync(data, 0, data.Length); await connection.Stream.WriteAsync(data, 0, data.Length); await connection.Stream.FlushAsync(); } var serverHelloInfo = await SslTools.GetServerHelloInfo(connection.Stream); ((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo; } await TcpHelper.SendRaw(clientStream, connection.Stream, (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }); UpdateServerConnectionCount(false); } return; } } //Now create the request disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, httpRemoteUri.Scheme == UriSchemeHttps?httpRemoteUri.Host : null, endPoint, connectRequest); } catch (Exception e) { ExceptionFunc(new Exception("Error whilst authorizing request", e)); } finally { if (!disposed) { Dispose(clientStream, clientStreamReader, clientStreamWriter, null); } } }
/// <summary> /// 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 /// </summary> /// <param name="endPoint"></param> /// <param name="tcpClient"></param> /// <returns></returns> private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient) { var disposed = false; var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; Uri httpRemoteUri; try { //read the first line HTTP command var httpCmd = await clientStreamReader.ReadLineAsync(); if (string.IsNullOrEmpty(httpCmd)) { return; } //break up the line into three components (method, remote URL & Http Version) var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3); //Find the request Verb var httpVerb = httpCmdSplit[0].ToUpper(); httpRemoteUri = httpVerb == "CONNECT" ? new Uri("http://" + httpCmdSplit[1]) : new Uri(httpCmdSplit[1]); //parse the HTTP version var version = HttpHeader.Version11; if (httpCmdSplit.Length == 3) { var httpVersion = httpCmdSplit[2].Trim(); if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase)) { version = HttpHeader.Version10; } } //filter out excluded host names bool excluded = false; if (endPoint.ExcludedHttpsHostNameRegex != null) { excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host)); } if (endPoint.IncludedHttpsHostNameRegex != null) { excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host)); } List <HttpHeader> connectRequestHeaders = null; //Client wants to create a secure tcp tunnel (its a HTTPS request) if (httpVerb == "CONNECT" && !excluded && endPoint.RemoteHttpsPorts.Contains(httpRemoteUri.Port)) { httpRemoteUri = new Uri("https://" + httpCmdSplit[1]); connectRequestHeaders = new List <HttpHeader>(); string tmpLine; while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync())) { var header = tmpLine.Split(ProxyConstants.ColonSplit, 2); var newHeader = new HttpHeader(header[0], header[1]); connectRequestHeaders.Add(newHeader); } if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false) { return; } await WriteConnectResponse(clientStreamWriter, version); SslStream sslStream = null; try { sslStream = new SslStream(clientStream); var certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host); var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false); //Successfully managed to authenticate the client using the fake certificate await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false); //HTTPS server created - we can now decrypt the client's traffic clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStreamReader.Dispose(); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; } catch { sslStream?.Dispose(); return; } //Now read the actual HTTPS request line httpCmd = await clientStreamReader.ReadLineAsync(); } //Sorry cannot do a HTTPS request decrypt to port 80 at this time else if (httpVerb == "CONNECT") { //Siphon out CONNECT request headers await clientStreamReader.ReadAndIgnoreAllLinesAsync(); //write back successfull CONNECT response await WriteConnectResponse(clientStreamWriter, version); await TcpHelper.SendRaw(this, httpRemoteUri.Host, httpRemoteUri.Port, null, version, null, false, clientStream, tcpConnectionFactory); return; } //Now create the request disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, httpRemoteUri.Scheme == Uri.UriSchemeHttps?httpRemoteUri.Host : null, endPoint, connectRequestHeaders); } catch (Exception e) { ExceptionFunc(new Exception("Error whilst authorizing request", e)); } finally { if (!disposed) { Dispose(clientStream, clientStreamReader, clientStreamWriter, null); } } }
/// <summary> /// 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 /// </summary> /// <param name="endPoint"></param> /// <param name="tcpClient"></param> /// <returns></returns> private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient) { var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize); try { string connectHostname = null; ConnectRequest connectRequest = null; //Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request) if (await HttpHelper.IsConnectMethod(clientStream) == 1) { //read the first line HTTP command string httpCmd = await clientStreamReader.ReadLineAsync(); if (string.IsNullOrEmpty(httpCmd)) { return; } Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version); var httpRemoteUri = new Uri("http://" + httpUrl); connectHostname = httpRemoteUri.Host; //filter out excluded host names bool excluded = false; if (endPoint.ExcludedHttpsHostNameRegex != null) { excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(connectHostname)); } if (endPoint.IncludedHttpsHostNameRegex != null) { excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(connectHostname)); } if (endPoint.BeforeTunnelConnect != null) { excluded = await endPoint.BeforeTunnelConnect(connectHostname); } connectRequest = new ConnectRequest { RequestUri = httpRemoteUri, OriginalUrl = httpUrl, HttpVersion = version, }; await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.Headers); var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest, ExceptionFunc); connectArgs.ProxyClient.TcpClient = tcpClient; connectArgs.ProxyClient.ClientStream = clientStream; await endPoint.InvokeTunnectConnectRequest(this, connectArgs, ExceptionFunc); if (await CheckAuthorization(clientStreamWriter, connectArgs) == false) { await endPoint.InvokeTunnectConnectResponse(this, connectArgs, ExceptionFunc); return; } //write back successfull CONNECT response var response = ConnectResponse.CreateSuccessfullConnectResponse(version); response.Headers.FixProxyHeaders(); connectArgs.WebSession.Response = response; await clientStreamWriter.WriteResponseAsync(response); var clientHelloInfo = await SslTools.PeekClientHello(clientStream); bool isClientHello = clientHelloInfo != null; if (isClientHello) { connectRequest.ClientHelloInfo = clientHelloInfo; } await endPoint.InvokeTunnectConnectResponse(this, connectArgs, ExceptionFunc, isClientHello); if (!excluded && isClientHello) { connectRequest.RequestUri = new Uri("https://" + httpUrl); SslStream sslStream = null; try { sslStream = new SslStream(clientStream); string certName = HttpHelper.GetWildCardDomainName(connectHostname); var certificate = endPoint.GenericCertificate ?? await CertificateManager.CreateCertificateAsync(certName); //Successfully managed to authenticate the client using the fake certificate await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false); //HTTPS server created - we can now decrypt the client's traffic clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStreamReader.Dispose(); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize); } catch { sslStream?.Dispose(); return; } if (await HttpHelper.IsConnectMethod(clientStream) == -1) { // It can be for example some Google (Cloude Messaging for Chrome) magic excluded = true; } } //Hostname is excluded or it is not an HTTPS connect if (excluded || !isClientHello) { //create new connection using (var connection = await GetServerConnection(connectArgs, true)) { if (isClientHello) { int available = clientStream.Available; if (available > 0) { //send the buffered data var data = BufferPool.GetBuffer(BufferSize); try { // clientStream.Available sbould be at most BufferSize because it is using the same buffer size await clientStream.ReadAsync(data, 0, available); await connection.StreamWriter.WriteAsync(data, 0, available, true); } finally { BufferPool.ReturnBuffer(data); } } var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream); ((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo; } await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize, (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, ExceptionFunc); } return; } } //Now create the request await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter, connectHostname, endPoint, connectRequest); } catch (ProxyHttpException e) { ExceptionFunc(e); } catch (IOException e) { ExceptionFunc(new Exception("Connection was aborted", e)); } catch (SocketException e) { ExceptionFunc(new Exception("Could not connect", e)); } catch (Exception e) { ExceptionFunc(new Exception("Error occured in whilst handling the client", e)); } finally { clientStreamReader.Dispose(); clientStream.Dispose(); } }
public void Dispose() { reader.Dispose(); }