/// <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;
        }
Пример #2
0
        /// <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)));
        }
Пример #3
0
        /// <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)));
        }
Пример #4
0
        /// <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)));
        }
Пример #5
0
        /// <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);
                }
            }
        }
Пример #9
0
        /// <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)));
        }
Пример #10
0
        /// <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);
            }
        }