You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@mina.apache.org by "Antonio Silvestre (JIRA)" <ji...@apache.org> on 2016/05/18 13:41:12 UTC

[jira] [Comment Edited] (DIRMINA-844) Http Proxy Authentication failed to complete (see description for exact point of failure)

    [ https://issues.apache.org/jira/browse/DIRMINA-844?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15288962#comment-15288962 ] 

Antonio Silvestre edited comment on DIRMINA-844 at 5/18/16 1:40 PM:
--------------------------------------------------------------------

I had got the same issue. The problem is an error design into the handshake process with an http authentication required proxy. When we try to connect to the proxy for the first time, we don't send any authentication header, so we receive a 407 code response, and from the "Proxy-Authenticate" header we get the required authentication method and set the corresponding AuthHandler class.

The first error is in class "org.apache.mina.proxy.handlers.http.AbstractHttpLogicHandler", method "", line 377:
{code:title=AbstractHttpLogicHandler.java|borderStyle=solid}
    protected HttpProxyResponse decodeResponse(final String response) throws Exception {
        LOGGER.debug("  parseResponse()");

        // Break response into lines
        String[] responseLines = response.split(HttpProxyConstants.CRLF);

        // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
        // BUG FIX : Trimed to prevent failures with some proxies that add 
        // extra space chars like "Microsoft-IIS/5.0" ...
        String[] statusLine = responseLines[0].trim().split(" ", 2);

        if (statusLine.length < 2) {
            throw new Exception("Invalid response status line (" + statusLine + "). Response: " + response);
        }

        // Status code is 3 digits
        if (!statusLine[1].matches("^\\d\\d\\d")) {
            throw new Exception("Invalid response code (" + statusLine[1] + "). Response: " + response);
        }

        Map<String, List<String>> headers = new HashMap<String, List<String>>();

        for (int i = 1; i < responseLines.length; i++) {
            String[] args = responseLines[i].split(":\\s?", 2);
            StringUtilities.addValueToHeader(headers, args[0], args[1], false);
        }

        return new HttpProxyResponse(statusLine[0], statusLine[1], headers);
    }
{code}

The value "statusLine[1]" is expected to be a string starting with the http returned code (in this case 407) followed by a space and the corresponding message added by the server (as we're splitting the full status line into only 2 strings). Here you are an example taken from my company proxy:

{{407 Proxy Authentication Required}}

the regular expression to match {noformat}"^\\d\\d\\d" is expecting only the ciphers, not the text, so I've changed this regular expression to add any followed text: "^\\d\\d\\d.*".{noformat} The HttpProxyResponse constructor gets the substring from the first 3 chars and parses it to the integer return code, so the remaining text doesn't affect. Depending of how do you handle this exception, you may not see the error trace.

After correcting this error, instead of throwing the exception the code goes on and it tries to reconnect to the proxy, creating a new session and rebuilding the http request to add the corresponding authentication headers to it, but here there's another problem... The process never launch the second http request, as the "operationComplete" callback defined into the "reconnect" method, line 342 is never called:

{code:title=AbstractHttpLogicHandler.java|borderStyle=solid}
    private void reconnect(final NextFilter nextFilter, final HttpProxyRequest request) {
        LOGGER.debug("Reconnecting to proxy ...");

        final ProxyIoSession proxyIoSession = getProxyIoSession();

        // Fires reconnection
        proxyIoSession.getConnector().connect(new IoSessionInitializer<ConnectFuture>() {
            public void initializeSession(final IoSession session, ConnectFuture future) {
                LOGGER.debug("Initializing new session: {}", session);
                session.setAttribute(ProxyIoSession.PROXY_SESSION, proxyIoSession);
                proxyIoSession.setSession(session);
                LOGGER.debug("  setting up proxyIoSession: {}", proxyIoSession);
                future.addListener(new IoFutureListener<ConnectFuture>() {
                    public void operationComplete(ConnectFuture future) {
                        // Reconnection is done so we send the
                        // request to the proxy
                        proxyIoSession.setReconnectionNeeded(false);
                        writeRequest0(nextFilter, request);
                    }
                });
            }
        });
    }
{code}

The operation is considered "completed" when the handshake phase is completed, but we're into this phase now, and we need to execute the request to complete it, so here we have a deadlock. As we haven't completed the handshake phase, we never call the "operationComplete" method and of course we never do the second http request with the right headers. To solve this problem we need to remove the custom IoFutureListener and put the inner code into the "initializeSession" callback directly.

I'm going to make a pull request with this changes. They have been tested and they're working.


was (Author: asilvestrer):
I had got the same issue. The problem is an error design into the handshake process with an http authentication required proxy. When we try to connect to the proxy for the first time, we don't send any authentication header, so we receive a 407 code response, and from the "Proxy-Authenticate" header we get the required authentication method and set the corresponding AuthHandler class.

The first error is in class "org.apache.mina.proxy.handlers.http.AbstractHttpLogicHandler", method "", line 377:
{code:title=AbstractHttpLogicHandler.java|borderStyle=solid}
    protected HttpProxyResponse decodeResponse(final String response) throws Exception {
        LOGGER.debug("  parseResponse()");

        // Break response into lines
        String[] responseLines = response.split(HttpProxyConstants.CRLF);

        // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
        // BUG FIX : Trimed to prevent failures with some proxies that add 
        // extra space chars like "Microsoft-IIS/5.0" ...
        String[] statusLine = responseLines[0].trim().split(" ", 2);

        if (statusLine.length < 2) {
            throw new Exception("Invalid response status line (" + statusLine + "). Response: " + response);
        }

        // Status code is 3 digits
        if (!statusLine[1].matches("^\\d\\d\\d")) {
            throw new Exception("Invalid response code (" + statusLine[1] + "). Response: " + response);
        }

        Map<String, List<String>> headers = new HashMap<String, List<String>>();

        for (int i = 1; i < responseLines.length; i++) {
            String[] args = responseLines[i].split(":\\s?", 2);
            StringUtilities.addValueToHeader(headers, args[0], args[1], false);
        }

        return new HttpProxyResponse(statusLine[0], statusLine[1], headers);
    }
{code}

The value "statusLine[1]" is expected to be a string starting with the http returned code (in this case 407) followed by a space and the corresponding message added by the server (as we're splitting the full status line into only 2 strings). Here you are an example taken from my company proxy:

{{407 Proxy Authentication Required}}

the regular expression to match "^\\d\\d\\d" is expecting only the ciphers, not the text, so I've changed this regular expression to add any followed text: "^\\d\\d\\d.*". The HttpProxyResponse constructor gets the substring from the first 3 chars and parses it to the integer return code, so the remaining text doesn't affect. Depending of how do you handle this exception, you may not see the error trace.

After correcting this error, instead of throwing the exception the code goes on and it tries to reconnect to the proxy, creating a new session and rebuilding the http request to add the corresponding authentication headers to it, but here there's another problem... The process never launch the second http request, as the "operationComplete" callback defined into the "reconnect" method, line 342 is never called:

{code:title=AbstractHttpLogicHandler.java|borderStyle=solid}
    private void reconnect(final NextFilter nextFilter, final HttpProxyRequest request) {
        LOGGER.debug("Reconnecting to proxy ...");

        final ProxyIoSession proxyIoSession = getProxyIoSession();

        // Fires reconnection
        proxyIoSession.getConnector().connect(new IoSessionInitializer<ConnectFuture>() {
            public void initializeSession(final IoSession session, ConnectFuture future) {
                LOGGER.debug("Initializing new session: {}", session);
                session.setAttribute(ProxyIoSession.PROXY_SESSION, proxyIoSession);
                proxyIoSession.setSession(session);
                LOGGER.debug("  setting up proxyIoSession: {}", proxyIoSession);
                future.addListener(new IoFutureListener<ConnectFuture>() {
                    public void operationComplete(ConnectFuture future) {
                        // Reconnection is done so we send the
                        // request to the proxy
                        proxyIoSession.setReconnectionNeeded(false);
                        writeRequest0(nextFilter, request);
                    }
                });
            }
        });
    }
{code}

The operation is considered "completed" when the handshake phase is completed, but we're into this phase now, and we need to execute the request to complete it, so here we have a deadlock. As we haven't completed the handshake phase, we never call the "operationComplete" method and of course we never do the second http request with the right headers. To solve this problem we need to remove the custom IoFutureListener and put the inner code into the "initializeSession" callback directly.

I'm going to make a pull request with this changes. They have been tested and they're working.

> Http Proxy Authentication failed to complete (see description for exact point of failure)
> -----------------------------------------------------------------------------------------
>
>                 Key: DIRMINA-844
>                 URL: https://issues.apache.org/jira/browse/DIRMINA-844
>             Project: MINA
>          Issue Type: Bug
>          Components: Core
>    Affects Versions: 2.0.3
>         Environment: Mac OS X 10.6.8 
> JDK 1.6
> squid/2.6
> Eclipse
>            Reporter: Parijat Bansal
>             Fix For: 2.0.8
>
>
> When trying to connect through an Squid Http Proxy using ProxyConnector (requiring basic authentication). While debugging issue, I walked through MINA code. Following are my observations:
> 1. First attempt made by MINA do not send any authentication details even though property set, seems like its to get first response mentioning authentication method. 
> 2. So a second connection is initiated from ProxyConnector and this time request contains the authentication header. 
> ISSUE: My client kept waiting at this point and it seems that second connection never got completed. I used eclipse debugger and found that AbstractPollingIoConnector.connect0 method is getting called with proper destination (As I used a NioSocketConnector to construct ProxyConnector) but after that no progress.
> Also  at Line#343 following were the content of request variable (I wonder if this could be of help )
> request	AbstractPollingIoConnector$ConnectionRequest  (id=82)	
> 	deadline	1310717009666	
> 	firstListener	null	
> 	handle	SocketChannelImpl  (id=88)	
> 	lock	AbstractPollingIoConnector$ConnectionRequest  (id=82)	
> 	otherListeners	null	
> 	ready	false	
> 	result	null	
> 	session	null	
> 	sessionInitializer	ProxyIoSessionInitializer<T>  (id=104)	
> 	this$0	NioSocketConnector  (id=40)	
> 	waiters	0	



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)