private static void Dispose(TcpClient client, Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, SessionEventArgs args) { if (args != null) args.Dispose(); if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); }
private static void HandleClient(TcpClient client) { Stream clientStream = client.GetStream(); CustomBinaryReader clientStreamReader = new CustomBinaryReader(clientStream, Encoding.ASCII); StreamWriter clientStreamWriter = new StreamWriter(clientStream); string tunnelHostName = null; int tunnelPort = 0; try { string securehost = null; List<string> requestLines = new List<string>(); string tmpLine; while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } //read the first line HTTP command String httpCmd = requestLines.Count > 0 ? requestLines[0] : null; if (String.IsNullOrEmpty(httpCmd)) { throw new EndOfStreamException(); } //break up the line into three components (method, remote URL & Http Version) String[] splitBuffer = httpCmd.Split(spaceSplit, 3); String method = splitBuffer[0]; String remoteUri = splitBuffer[1]; Version version; string RequestVersion; if (splitBuffer[2] == "HTTP/1.1") { version = new Version(1, 1); RequestVersion = "HTTP/1.1"; } else { version = new Version(1, 0); RequestVersion = "HTTP/1.0"; } //Client wants to create a secure tcp tunnel (its a HTTPS request) if (splitBuffer[0].ToUpper() == "CONNECT") { //Browser wants to create a secure tunnel //instead = we are going to perform a man in the middle "attack" //the user's browser should warn them of the certification errors, //to avoid that we need to install our root certficate in users machine as Certificate Authority. remoteUri = "https://" + splitBuffer[1]; tunnelHostName = splitBuffer[1].Split(':')[0]; int.TryParse(splitBuffer[1].Split(':')[1], out tunnelPort); if (tunnelPort == 0) tunnelPort = 443; requestLines.Clear(); clientStreamWriter.WriteLine(RequestVersion + " 200 Connection established"); clientStreamWriter.WriteLine(String.Format("Timestamp: {0}", DateTime.Now.ToString())); clientStreamWriter.WriteLine(String.Format("connection:close")); clientStreamWriter.WriteLine(); clientStreamWriter.Flush(); //If port is not 443 its not a HTTP request, so just relay if (tunnelPort != 443) { TcpHelper.SendRaw(tunnelHostName, tunnelPort, clientStreamReader.BaseStream); if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); return; } //Create the fake certificate signed using our fake certificate authority Monitor.Enter(certificateAccessLock); var certificate = ProxyServer.CertManager.CreateCertificate(tunnelHostName); Monitor.Exit(certificateAccessLock); SslStream sslStream = null; //Pinned certificate clients cannot be proxied //Example dropbox.com uses certificate pinning //So just relay the request after identifying it by first failure if (!pinnedCertificateClients.Contains(tunnelHostName)) { try { sslStream = new SslStream(clientStream, true); //Successfully managed to authenticate the client using the fake certificate sslStream.AuthenticateAsServer(certificate, false, SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, false); clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII); clientStreamWriter = new StreamWriter(sslStream); //HTTPS server created - we can now decrypt the client's traffic clientStream = sslStream; } catch { //if authentication failed it could be because client uses pinned certificates //So add the hostname to this list so that next time we can relay without touching it (tunnel the request) if (pinnedCertificateClients.Contains(tunnelHostName) == false) { pinnedCertificateClients.Add(tunnelHostName); } if (sslStream != null) sslStream.Dispose(); throw; } } else { //Hostname was a previously failed request due to certificate pinning, just relay (tunnel the request) TcpHelper.SendRaw(tunnelHostName, tunnelPort, clientStreamReader.BaseStream); if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); return; } while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } //read the new http command. httpCmd = requestLines.Count > 0 ? requestLines[0] : null; if (String.IsNullOrEmpty(httpCmd)) { throw new EndOfStreamException(); } securehost = remoteUri; } //Now create the request Task.Factory.StartNew(() => HandleHttpSessionRequest(client, httpCmd, clientStream, tunnelHostName, requestLines, clientStreamReader, clientStreamWriter, securehost)); } catch { if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); } }
private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, string tunnelHostName, List<string> requestLines, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string securehost) { if (httpCmd == null) { if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); return; } var args = new SessionEventArgs(BUFFER_SIZE); args.Client = client; args.tunnelHostName = tunnelHostName; args.securehost = securehost; try { //break up the line into three components (method, remote URL & Http Version) var splitBuffer = httpCmd.Split(spaceSplit, 3); if (splitBuffer.Length != 3) { TcpHelper.SendRaw(httpCmd, tunnelHostName, requestLines, args.IsSSLRequest, clientStreamReader.BaseStream); if (args != null) args.Dispose(); if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); return; } var method = splitBuffer[0]; var remoteUri = splitBuffer[1]; Version version; if (splitBuffer[2] == "HTTP/1.1") { version = new Version(1, 1); } else { version = new Version(1, 0); } if (securehost != null) { remoteUri = securehost + remoteUri; args.IsSSLRequest = true; } //construct the web request that we are going to issue on behalf of the client. args.ProxyRequest = (HttpWebRequest)HttpWebRequest.Create(remoteUri.Trim()); args.ProxyRequest.Proxy = null; args.ProxyRequest.UseDefaultCredentials = true; args.ProxyRequest.Method = method; args.ProxyRequest.ProtocolVersion = version; args.ClientStream = clientStream; args.ClientStreamReader = clientStreamReader; args.ClientStreamWriter = clientStreamWriter; for (int i = 1; i < requestLines.Count; i++) { var rawHeader = requestLines[i]; String[] header = rawHeader.ToLower().Trim().Split(colonSpaceSplit, 2, StringSplitOptions.None); //if request was upgrade to web-socket protocol then relay the request without proxying if ((header[0] == "upgrade") && (header[1] == "websocket")) { TcpHelper.SendRaw(httpCmd, tunnelHostName, requestLines, args.IsSSLRequest, clientStreamReader.BaseStream); if (args != null) args.Dispose(); if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); return; } } SetClientRequestHeaders(requestLines, args.ProxyRequest); int contentLen = (int)args.ProxyRequest.ContentLength; args.ProxyRequest.AllowAutoRedirect = false; args.ProxyRequest.AutomaticDecompression = DecompressionMethods.None; //If requested interception if (BeforeRequest != null) { args.RequestHostname = args.ProxyRequest.RequestUri.Host; args.RequestURL = args.ProxyRequest.RequestUri.OriginalString; args.RequestLength = contentLen; args.RequestHttpVersion = version; args.ClientPort = ((IPEndPoint)client.Client.RemoteEndPoint).Port; args.ClientIpAddress = ((IPEndPoint)client.Client.RemoteEndPoint).Address; args.RequestIsAlive = args.ProxyRequest.KeepAlive; BeforeRequest(null, args); } string tmpLine; if (args.CancelRequest) { if (args != null) args.Dispose(); if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); return; } args.ProxyRequest.ConnectionGroupName = args.RequestHostname; args.ProxyRequest.AllowWriteStreamBuffering = true; //If request was modified by user if (args.RequestWasModified) { ASCIIEncoding encoding = new ASCIIEncoding(); byte[] requestBytes = encoding.GetBytes(args.RequestHtmlBody); args.ProxyRequest.ContentLength = requestBytes.Length; Stream newStream = args.ProxyRequest.GetRequestStream(); newStream.Write(requestBytes, 0, requestBytes.Length); args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args); } else { //If its a post/put request, then read the client html body and send it to server if (method.ToUpper() == "POST" || method.ToUpper() == "PUT") { SendClientRequestBody(args); //Http request body sent, now wait asynchronously for response args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args); } else { //otherwise wait for response asynchronously args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args); } } //Now read the next request (if keep-Alive is enabled, otherwise exit this thread) //If client is pipeling the request, this will be immediately hit before response for previous request was made tmpLine = null; requestLines.Clear(); while (!String.IsNullOrEmpty(tmpLine = args.ClientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } httpCmd = requestLines.Count() > 0 ? requestLines[0] : null; TcpClient Client = args.Client; //Http request body sent, now wait for next request Task.Factory.StartNew(() => HandleHttpSessionRequest(Client, httpCmd, args.ClientStream, args.tunnelHostName, requestLines, args.ClientStreamReader, args.ClientStreamWriter, args.securehost)); } catch { if (args != null) args.Dispose(); if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); } }