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.ntlm;
21  
22  import java.io.IOException;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.apache.mina.core.filterchain.IoFilter.NextFilter;
28  import org.apache.mina.proxy.ProxyAuthException;
29  import org.apache.mina.proxy.handlers.http.AbstractAuthLogicHandler;
30  import org.apache.mina.proxy.handlers.http.HttpProxyConstants;
31  import org.apache.mina.proxy.handlers.http.HttpProxyRequest;
32  import org.apache.mina.proxy.handlers.http.HttpProxyResponse;
33  import org.apache.mina.proxy.session.ProxyIoSession;
34  import org.apache.mina.proxy.utils.StringUtilities;
35  import org.apache.mina.util.Base64;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * HttpNTLMAuthLogicHandler.java - HTTP NTLM authentication mechanism logic handler.
41   * 
42   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
43   * @since MINA 2.0.0-M3
44   */
45  public class HttpNTLMAuthLogicHandler extends AbstractAuthLogicHandler {
46  
47      private static final Logger LOGGER = LoggerFactory.getLogger(HttpNTLMAuthLogicHandler.class);
48  
49      /**
50       * The challenge provided by the server.
51       */
52      private byte[] challengePacket = null;
53  
54      /**
55       * Build an HttpNTLMAuthLogicHandler
56       * 
57       * @param proxyIoSession The original session
58       * @throws ProxyAuthException If we get an error during the proxy authentication
59       */
60      public HttpNTLMAuthLogicHandler(final ProxyIoSession proxyIoSession) throws ProxyAuthException {
61          super(proxyIoSession);
62  
63          ((HttpProxyRequest) request).checkRequiredProperties(HttpProxyConstants.USER_PROPERTY,
64                  HttpProxyConstants.PWD_PROPERTY, HttpProxyConstants.DOMAIN_PROPERTY,
65                  HttpProxyConstants.WORKSTATION_PROPERTY);
66      }
67  
68      /**
69       * {@inheritDoc}
70       */
71      @Override
72      public void doHandshake(NextFilter nextFilter) throws ProxyAuthException {
73          if (LOGGER.isDebugEnabled()) {
74              LOGGER.debug(" doHandshake()");
75          }
76  
77          if (step > 0 && challengePacket == null) {
78              throw new IllegalStateException("NTLM Challenge packet not received");
79          }
80  
81          HttpProxyRequest./../../../../org/apache/mina/proxy/handlers/http/HttpProxyRequest.html#HttpProxyRequest">HttpProxyRequest req = (HttpProxyRequest) request;
82          Map<String, List<String>> headers = req.getHeaders() != null ? req.getHeaders()
83                  : new HashMap<String, List<String>>();
84  
85          String domain = req.getProperties().get(HttpProxyConstants.DOMAIN_PROPERTY);
86          String workstation = req.getProperties().get(HttpProxyConstants.WORKSTATION_PROPERTY);
87  
88          if (step > 0) {
89              if (LOGGER.isDebugEnabled()) {
90                  LOGGER.debug("  sending NTLM challenge response");
91              }
92  
93              byte[] challenge = NTLMUtilities.extractChallengeFromType2Message(challengePacket);
94              int serverFlags = NTLMUtilities.extractFlagsFromType2Message(challengePacket);
95  
96              String username = req.getProperties().get(HttpProxyConstants.USER_PROPERTY);
97              String password = req.getProperties().get(HttpProxyConstants.PWD_PROPERTY);
98  
99              byte[] authenticationPacket = NTLMUtilities.createType3Message(username, password, challenge, domain,
100                     workstation, serverFlags, null);
101 
102             StringUtilities.addValueToHeader(headers, "Proxy-Authorization",
103                     "NTLM " + new String(Base64.encodeBase64(authenticationPacket)), true);
104 
105         } else {
106             if (LOGGER.isDebugEnabled()) {
107                 LOGGER.debug("  sending NTLM negotiation packet");
108             }
109 
110             byte[] negotiationPacket = NTLMUtilities.createType1Message(workstation, domain, null, null);
111             StringUtilities.addValueToHeader(headers, "Proxy-Authorization",
112                     "NTLM " + new String(Base64.encodeBase64(negotiationPacket)), true);
113         }
114 
115         addKeepAliveHeaders(headers);
116         req.setHeaders(headers);
117 
118         writeRequest(nextFilter, req);
119         step++;
120     }
121 
122     /**
123      * @return the value of the NTLM Proxy-Authenticate header.
124      * 
125      * @param response the proxy response
126      */
127     private String getNTLMHeader(final HttpProxyResponse response) {
128         List<String> values = response.getHeaders().get("Proxy-Authenticate");
129 
130         for (String s : values) {
131             if (s.startsWith("NTLM")) {
132                 return s;
133             }
134         }
135 
136         return null;
137     }
138 
139     /**
140      * {@inheritDoc}
141      */
142     @Override
143     public void handleResponse(final HttpProxyResponse response) throws ProxyAuthException {
144         if (step == 0) {
145             String challengeResponse = getNTLMHeader(response);
146             step = 1;
147 
148             if (challengeResponse == null || challengeResponse.length() < 5) {
149                 // Nothing to handle at this step. 
150                 // Just need to send a reply type 1 message in doHandshake().
151                 return;
152             }
153 
154             // else there was no step 0 so continue to step 1.
155         }
156 
157         if (step == 1) {
158             // Header should look like :
159             // Proxy-Authenticate: NTLM still_some_more_stuff
160             String challengeResponse = getNTLMHeader(response);
161 
162             if (challengeResponse == null || challengeResponse.length() < 5) {
163                 throw new ProxyAuthException("Unexpected error while reading server challenge !");
164             }
165 
166             try {
167                 challengePacket = Base64.decodeBase64(challengeResponse.substring(5).getBytes(
168                         proxyIoSession.getCharsetName()));
169             } catch (IOException e) {
170                 throw new ProxyAuthException("Unable to decode the base64 encoded NTLM challenge", e);
171             }
172             step = 2;
173         } else {
174             throw new ProxyAuthException("Received unexpected response code (" + response.getStatusLine() + ").");
175         }
176     }
177 }