View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.proxy.handlers.http;
21  
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.mina.core.filterchain.IoFilter.NextFilter;
27  import org.apache.mina.proxy.ProxyAuthException;
28  import org.apache.mina.proxy.session.ProxyIoSession;
29  import org.apache.mina.proxy.utils.StringUtilities;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /**
34   * HttpSmartProxyHandler.java - HTTP proxy handler that automatically handles forwarding a request 
35   * to the appropriate authentication mechanism logic handler.
36   * 
37   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
38   * @since MINA 2.0.0-M3
39   */
40  public class HttpSmartProxyHandler extends AbstractHttpLogicHandler {
41      private final static Logger logger = LoggerFactory.getLogger(HttpSmartProxyHandler.class);
42  
43      /**
44       * Has the HTTP proxy request been sent ?
45       */
46      private boolean requestSent = false;
47  
48      /**
49       * The automatically selected http authentication logic handler. 
50       */
51      private AbstractAuthLogicHandler authHandler;
52  
53      public HttpSmartProxyHandler(final ProxyIoSession proxyIoSession) {
54          super(proxyIoSession);
55      }
56  
57      /**
58       * Performs the handshake processing.
59       * 
60       * @param nextFilter the next filter
61       */
62      public void doHandshake(final NextFilter nextFilter) throws ProxyAuthException {
63          logger.debug(" doHandshake()");
64  
65          if (authHandler != null) {
66              authHandler.doHandshake(nextFilter);
67          } else {
68              if (requestSent) {
69                  // Safety check
70                  throw new ProxyAuthException("Authentication request already sent");
71              }
72  
73              logger.debug("  sending HTTP request");
74  
75              // Compute request headers
76              HttpProxyRequest req = (HttpProxyRequest) getProxyIoSession().getRequest();
77              Map<String, List<String>> headers = req.getHeaders() != null ? req.getHeaders()
78                      : new HashMap<String, List<String>>();
79  
80              AbstractAuthLogicHandler.addKeepAliveHeaders(headers);
81              req.setHeaders(headers);
82  
83              // Write request to the proxy
84              writeRequest(nextFilter, req);
85              requestSent = true;
86          }
87      }
88  
89      /**
90       * Automatic selection of the authentication algorithm. If <code>preferedOrder</code> is set then
91       * algorithms are selected from the list order otherwise the algorithm tries to select the most 
92       * secured algorithm available first.
93       * 
94       * @param response the proxy response
95       */
96      private void autoSelectAuthHandler(final HttpProxyResponse response) throws ProxyAuthException {
97          // Get the Proxy-Authenticate header
98          List<String> values = response.getHeaders().get("Proxy-Authenticate");
99          ProxyIoSession proxyIoSession = getProxyIoSession();
100 
101         if (values == null || values.size() == 0) {
102             authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession);
103 
104         } else if (getProxyIoSession().getPreferedOrder() == null) {
105             // No preference order set for auth mechanisms
106             int method = -1;
107 
108             // Test which auth mechanism to use. First found is the first used
109             // that's why we test in a decreasing security quality order.
110             for (String proxyAuthHeader : values) {
111                 proxyAuthHeader = proxyAuthHeader.toLowerCase();
112 
113                 if (proxyAuthHeader.contains("ntlm")) {
114                     method = HttpAuthenticationMethods.NTLM.getId();
115                     break;
116                 } else if (proxyAuthHeader.contains("digest") && method != HttpAuthenticationMethods.NTLM.getId()) {
117                     method = HttpAuthenticationMethods.DIGEST.getId();
118                 } else if (proxyAuthHeader.contains("basic") && method == -1) {
119                     method = HttpAuthenticationMethods.BASIC.getId();
120                 }
121             }
122 
123             if (method != -1) {
124                 try {
125                     authHandler = HttpAuthenticationMethods.getNewHandler(method, proxyIoSession);
126                 } catch (Exception ex) {
127                     logger.debug("Following exception occured:", ex);
128                 }
129             }
130 
131             if (authHandler == null) {
132                 authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession);
133             }
134 
135         } else {
136             for (HttpAuthenticationMethods method : proxyIoSession.getPreferedOrder()) {
137                 if (authHandler != null) {
138                     break;
139                 }
140 
141                 if (method == HttpAuthenticationMethods.NO_AUTH) {
142                     authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession);
143                     break;
144                 }
145 
146                 for (String proxyAuthHeader : values) {
147                     proxyAuthHeader = proxyAuthHeader.toLowerCase();
148 
149                     try {
150                         // test which auth mechanism to use
151                         if (proxyAuthHeader.contains("basic") && method == HttpAuthenticationMethods.BASIC) {
152                             authHandler = HttpAuthenticationMethods.BASIC.getNewHandler(proxyIoSession);
153                             break;
154                         } else if (proxyAuthHeader.contains("digest") && method == HttpAuthenticationMethods.DIGEST) {
155                             authHandler = HttpAuthenticationMethods.DIGEST.getNewHandler(proxyIoSession);
156                             break;
157                         } else if (proxyAuthHeader.contains("ntlm") && method == HttpAuthenticationMethods.NTLM) {
158                             authHandler = HttpAuthenticationMethods.NTLM.getNewHandler(proxyIoSession);
159                             break;
160                         }
161                     } catch (Exception ex) {
162                         logger.debug("Following exception occured:", ex);
163                     }
164                 }
165             }
166 
167         }
168 
169         if (authHandler == null) {
170             throw new ProxyAuthException("Unknown authentication mechanism(s): " + values);
171         }
172     }
173 
174     /**
175      * Handle a HTTP response from the proxy server.
176      * 
177      * @param response The proxy response.
178      */
179     @Override
180     public void handleResponse(final HttpProxyResponse response) throws ProxyAuthException {
181         if (!isHandshakeComplete()
182                 && ("close".equalsIgnoreCase(StringUtilities.getSingleValuedHeader(response.getHeaders(),
183                         "Proxy-Connection")) || "close".equalsIgnoreCase(StringUtilities.getSingleValuedHeader(
184                         response.getHeaders(), "Connection")))) {
185             getProxyIoSession().setReconnectionNeeded(true);
186         }
187 
188         if (response.getStatusCode() == 407) {
189             if (authHandler == null) {
190                 autoSelectAuthHandler(response);
191             }
192             authHandler.handleResponse(response);
193         } else {
194             throw new ProxyAuthException("Error: unexpected response code " + response.getStatusLine()
195                     + " received from proxy.");
196         }
197     }
198 }