/// <summary> /// Start this proxy server /// </summary> public void Start() { if (proxyRunning) { throw new Exception("Proxy is already running."); } if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null) { GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy; GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy; } foreach (var endPoint in ProxyEndPoints) { Listen(endPoint); } CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); if (!RunTime.IsRunningOnMono) { //clear orphaned windows auth states every 2 minutes WinAuthEndPoint.ClearIdleStates(2); } proxyRunning = true; }
/// <summary> /// Get the initial client token for server /// using credentials of user running the proxy server process /// </summary> /// <param name="serverHostname"></param> /// <param name="authScheme"></param> /// <param name="requestId"></param> /// <returns></returns> public static string GetInitialAuthToken(string serverHostname, string authScheme, Guid requestId) { var tokenBytes = WinAuthEndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, requestId); return(string.Concat(" ", Convert.ToBase64String(tokenBytes))); }
/// <summary> /// Get the final token given the server challenge token /// </summary> /// <param name="serverHostname"></param> /// <param name="serverToken"></param> /// <param name="requestId"></param> /// <returns></returns> public static string GetFinalAuthToken(string serverHostname, string serverToken, Guid requestId) { var tokenBytes = WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname, Convert.FromBase64String(serverToken), requestId); return(string.Concat(" ", Convert.ToBase64String(tokenBytes))); }
/// <summary> /// Get the final token given the server challenge token /// </summary> /// <param name="serverHostname"></param> /// <param name="serverToken"></param> /// <param name="data"></param> /// <returns></returns> internal static string GetFinalAuthToken(string serverHostname, string serverToken, InternalDataStore data) { var tokenBytes = WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname, Convert.FromBase64String(serverToken), data); return(string.Concat(" ", Convert.ToBase64String(tokenBytes))); }
/// <summary> /// Start this proxy server /// </summary> public void Start() { if (ProxyRunning) { throw new Exception("Proxy is already running."); } //clear any system proxy settings which is pointing to our own endpoint (causing a cycle) //due to non gracious proxy shutdown before or something else if (systemProxySettingsManager != null && RunTime.IsWindows) { var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry(); if (proxyInfo.Proxies != null) { var protocolToRemove = ProxyProtocolType.None; foreach (var proxy in proxyInfo.Proxies.Values) { if ((proxy.HostName == "127.0.0.1" || proxy.HostName.Equals("localhost", StringComparison.OrdinalIgnoreCase)) && ProxyEndPoints.Any(x => x.Port == proxy.Port)) { protocolToRemove |= proxy.ProtocolType; } } if (protocolToRemove != ProxyProtocolType.None) { //do not restore to any of listening address when we quit systemProxySettingsManager.RemoveProxy(protocolToRemove, false); } } } if (ForwardToUpstreamGateway && GetCustomUpStreamProxyFunc == null && systemProxySettingsManager != null) { // Use WinHttp to handle PAC/WAPD scripts. systemProxyResolver = new WinHttpWebProxyFinder(); systemProxyResolver.LoadFromIE(); GetCustomUpStreamProxyFunc = GetSystemUpStreamProxy; } ProxyRunning = true; foreach (var endPoint in ProxyEndPoints) { Listen(endPoint); } CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); if (RunTime.IsWindows && !RunTime.IsRunningOnMono) { //clear orphaned windows auth states every 2 minutes WinAuthEndPoint.ClearIdleStates(2); } }
/// <summary> /// Called asynchronously when a request was successful and we received the response. /// </summary> /// <param name="args">The session event arguments.</param> /// <returns> The task.</returns> protected async Task handleHttpSessionResponse(SessionEventArgs args) { var cancellationToken = args.CancellationTokenSource.Token; // read response & headers from server await args.HttpClient.ReceiveResponse(cancellationToken); // Server may send expect-continue even if not asked for it in request. // According to spec "the client can simply discard this interim response." if (args.HttpClient.Response.StatusCode == (int)HttpStatusCode.Continue) { await args.ClearResponse(cancellationToken); await args.HttpClient.ReceiveResponse(cancellationToken); } args.TimeLine["Response Received"] = DateTime.Now; var response = args.HttpClient.Response; args.ReRequest = false; // check for windows authentication if (args.EnableWinAuth) { if (response.StatusCode == (int)HttpStatusCode.Unauthorized) { await handle401UnAuthorized(args); } else { WinAuthEndPoint.AuthenticatedResponse(args.HttpClient.Data); } } // save original values so that if user changes them // we can still use original values when syphoning out data from attached tcp connection. response.SetOriginalHeaders(); // if user requested call back then do it if (!response.Locked) { await onBeforeResponse(args); } // it may changed in the user event response = args.HttpClient.Response; var clientStream = args.ClientStream; // user set custom response by ignoring original response from server. if (response.Locked) { // write custom user response with body and return. await clientStream.WriteResponseAsync(response, cancellationToken); if (args.HttpClient.HasConnection && !args.HttpClient.CloseServerConnection) { // syphon out the original response body from server connection // so that connection will be good to be reused. await args.SyphonOutBodyAsync(false, cancellationToken); } return; } // if user requested to send request again // likely after making modifications from User Response Handler if (args.ReRequest) { if (args.HttpClient.HasConnection) { await tcpConnectionFactory.Release(args.HttpClient.Connection); } // clear current response await args.ClearResponse(cancellationToken); await handleHttpSessionRequest(args, null, args.ClientConnection.NegotiatedApplicationProtocol, cancellationToken, args.CancellationTokenSource); return; } response.Locked = true; if (!args.IsTransparent) { response.Headers.FixProxyHeaders(); } await clientStream.WriteResponseAsync(response, cancellationToken); if (response.OriginalHasBody) { if (response.IsBodySent) { // syphon out body await args.SyphonOutBodyAsync(false, cancellationToken); } else { // Copy body if exists var serverStream = args.HttpClient.Connection.Stream; await serverStream.CopyBodyAsync(response, false, clientStream, TransformationMode.None, args.OnDataReceived, cancellationToken); } } args.TimeLine["Response Sent"] = DateTime.Now; }
/// <summary> /// Handle windows NTLM/Kerberos authentication. /// Note: NTLM/Kerberos cannot do a man in middle operation /// we do for HTTPS requests. /// As such we will be sending local credentials of current /// User to server to authenticate requests. /// To disable this set ProxyServer.EnableWinAuth to false. /// </summary> private async Task handle401UnAuthorized(SessionEventArgs args) { string headerName = null; HttpHeader authHeader = null; var response = args.HttpClient.Response; // check in non-unique headers first var header = response.Headers.NonUniqueHeaders.FirstOrDefault(x => authHeaderNames.Contains(x.Key)); if (!header.Equals(new KeyValuePair <string, List <HttpHeader> >())) { headerName = header.Key; } if (headerName != null) { authHeader = response.Headers.NonUniqueHeaders[headerName] .FirstOrDefault( x => authSchemes.Any(y => x.Value.StartsWith(y, StringComparison.OrdinalIgnoreCase))); } // check in unique headers if (authHeader == null) { headerName = null; // check in non-unique headers first var uHeader = response.Headers.Headers.FirstOrDefault(x => authHeaderNames.Contains(x.Key)); if (!uHeader.Equals(new KeyValuePair <string, HttpHeader>())) { headerName = uHeader.Key; } if (headerName != null) { authHeader = authSchemes.Any(x => response.Headers.Headers[headerName].Value .StartsWith(x, StringComparison.OrdinalIgnoreCase)) ? response.Headers.Headers[headerName] : null; } } if (authHeader != null) { string scheme = authSchemes.Contains(authHeader.Value) ? authHeader.Value : null; var expectedAuthState = scheme == null ? State.WinAuthState.INITIAL_TOKEN : State.WinAuthState.UNAUTHORIZED; if (!WinAuthEndPoint.ValidateWinAuthState(args.HttpClient.Data, expectedAuthState)) { // Invalid state, create proper error message to client await rewriteUnauthorizedResponse(args); return; } var request = args.HttpClient.Request; // clear any existing headers to avoid confusing bad servers request.Headers.RemoveHeader(KnownHeaders.Authorization); // initial value will match exactly any of the schemes if (scheme != null) { string clientToken = WinAuthHandler.GetInitialAuthToken(request.Host, scheme, args.HttpClient.Data); string auth = string.Concat(scheme, clientToken); // replace existing authorization header if any request.Headers.SetOrAddHeaderValue(KnownHeaders.Authorization, auth); // don't need to send body for Authorization request if (request.HasBody) { request.ContentLength = 0; } } else { // challenge value will start with any of the scheme selected scheme = authSchemes.First(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) && authHeader.Value.Length > x.Length + 1); string serverToken = authHeader.Value.Substring(scheme.Length + 1); string clientToken = WinAuthHandler.GetFinalAuthToken(request.Host, serverToken, args.HttpClient.Data); string auth = string.Concat(scheme, clientToken); // there will be an existing header from initial client request request.Headers.SetOrAddHeaderValue(KnownHeaders.Authorization, auth); // send body for final auth request if (request.OriginalHasBody) { request.ContentLength = request.Body.Length; } args.HttpClient.Connection.IsWinAuthenticated = true; } // Need to revisit this. // Should we cache all Set-Cookie headers from server during auth process // and send it to client after auth? // Let ResponseHandler send the updated request args.ReRequest = true; } }
/// <summary> /// Called asynchronously when a request was successfull and we received the response. /// </summary> /// <param name="args">The session event arguments.</param> /// <returns> The task.</returns> private async Task handleHttpSessionResponse(SessionEventArgs args) { var cancellationToken = args.CancellationTokenSource.Token; // read response & headers from server await args.WebSession.ReceiveResponse(cancellationToken); var response = args.WebSession.Response; args.ReRequest = false; // check for windows authentication if (isWindowsAuthenticationEnabledAndSupported) { if (response.StatusCode == (int)HttpStatusCode.Unauthorized) { await handle401UnAuthorized(args); } else { WinAuthEndPoint.AuthenticatedResponse(args.WebSession.Data); } } //save original values so that if user changes them //we can still use original values when syphoning out data from attached tcp connection. response.SetOriginalHeaders(); // if user requested call back then do it if (!response.Locked) { await invokeBeforeResponse(args); } // it may changed in the user event response = args.WebSession.Response; var clientStreamWriter = args.ProxyClient.ClientStreamWriter; //user set custom response by ignoring original response from server. if (response.Locked) { //write custom user response with body and return. await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); if (args.WebSession.ServerConnection != null && !args.WebSession.CloseServerConnection) { // syphon out the original response body from server connection // so that connection will be good to be reused. await args.SyphonOutBodyAsync(false, cancellationToken); } return; } // if user requested to send request again // likely after making modifications from User Response Handler if (args.ReRequest) { // clear current response await args.ClearResponse(cancellationToken); await handleHttpSessionRequestInternal(args.WebSession.ServerConnection, args); return; } response.Locked = true; // Write back to client 100-conitinue response if that's what server returned if (response.Is100Continue) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue", cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken); } else if (response.ExpectationFailed) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken); } if (!args.IsTransparent) { response.Headers.FixProxyHeaders(); } if (response.IsBodyRead) { await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); } else { // Write back response status to client await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription, cancellationToken); await clientStreamWriter.WriteHeadersAsync(response.Headers, cancellationToken : cancellationToken); // Write body if exists if (response.HasBody) { await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None, cancellationToken); } } }
/// <summary> /// Get the initial client token for server /// using credentials of user running the proxy server process /// </summary> /// <param name="serverHostname"></param> /// <param name="authScheme"></param> /// <param name="data"></param> /// <returns></returns> public static string GetInitialAuthToken(string serverHostname, string authScheme, InternalDataStore data) { var tokenBytes = WinAuthEndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, data); return(string.Concat(" ", Convert.ToBase64String(tokenBytes))); }
/// <summary> /// Called asynchronously when a request was successfully and we received the response. /// </summary> /// <param name="args">The session event arguments.</param> /// <returns> The task.</returns> private async Task HandleHttpSessionResponse(SessionEventArgs args) { try { var cancellationToken = args.CancellationTokenSource.Token; // read response & headers from server await args.WebSession.ReceiveResponse(cancellationToken); var response = args.WebSession.Response; args.ReRequest = false; // check for windows authentication if (isWindowsAuthenticationEnabledAndSupported) { if (response.StatusCode == (int)HttpStatusCode.Unauthorized) { await Handle401UnAuthorized(args); } else { WinAuthEndPoint.AuthenticatedResponse(args.WebSession.Data); } } response.OriginalHasBody = response.HasBody; // if user requested call back then do it if (!response.Locked) { await InvokeBeforeResponse(args); } // it may changed in the user event response = args.WebSession.Response; var clientStreamWriter = args.ProxyClient.ClientStreamWriter; if (response.TerminateResponse || response.Locked) { await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); if (!response.TerminateResponse) { // syphon out the response body from server before setting the new body await args.SyphonOutBodyAsync(false, cancellationToken); } else { args.WebSession.ServerConnection.Dispose(); args.WebSession.ServerConnection = null; } return; } // if user requested to send request again // likely after making modifications from User Response Handler if (args.ReRequest) { // clear current response await args.ClearResponse(cancellationToken); await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args); return; } response.Locked = true; // Write back to client 100-conitinue response if that's what server returned if (response.Is100Continue) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue", cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken); } else if (response.ExpectationFailed) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken); } if (!args.IsTransparent) { response.Headers.FixProxyHeaders(); } if (response.IsBodyRead) { await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); } else { // Write back response status to client await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription, cancellationToken); await clientStreamWriter.WriteHeadersAsync(response.Headers, cancellationToken : cancellationToken); // Write body if exists if (response.HasBody) { await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None, cancellationToken); } } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session response", e, args); } }