View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2010  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  
20  package davmail.http;
21  
22  import org.apache.http.Header;
23  import org.apache.http.HttpRequest;
24  import org.apache.http.auth.*;
25  import org.apache.http.conn.ManagedHttpClientConnection;
26  import org.apache.http.impl.auth.AuthSchemeBase;
27  import org.apache.http.impl.auth.NTLMEngineException;
28  import org.apache.http.message.BufferedHeader;
29  import org.apache.http.protocol.ExecutionContext;
30  import org.apache.http.protocol.HttpContext;
31  import org.apache.http.util.Args;
32  import org.apache.http.util.CharArrayBuffer;
33  
34  import javax.net.ssl.SSLPeerUnverifiedException;
35  import java.security.cert.Certificate;
36  
37  /**
38   * Duplicate of NTLMScheme from HttpClient to implement channel binding.
39   */
40  public class DavMailNTLMScheme extends AuthSchemeBase {
41      enum State {
42          UNINITIATED,
43          CHALLENGE_RECEIVED,
44          MSG_TYPE1_GENERATED,
45          MSG_TYPE2_RECEVIED,
46          MSG_TYPE3_GENERATED,
47          FAILED,
48      }
49  
50      private State state;
51      private final DavMailNTLMEngineImpl engine;
52      private HttpContext httpContext;
53  
54      private String challenge;
55  
56      public DavMailNTLMScheme() {
57          this.engine = new DavMailNTLMEngineImpl();
58          this.state = State.UNINITIATED;
59          this.challenge = null;
60      }
61  
62      @Override
63      protected void parseChallenge(CharArrayBuffer buffer, int beginIndex, int endIndex) throws MalformedChallengeException {
64          this.challenge = buffer.substringTrimmed(beginIndex, endIndex);
65          if (this.challenge.isEmpty()) {
66              if (this.state == State.UNINITIATED) {
67                  this.state = State.CHALLENGE_RECEIVED;
68              } else {
69                  this.state = State.FAILED;
70              }
71          } else {
72              if (this.state.compareTo(State.MSG_TYPE1_GENERATED) < 0) {
73                  this.state = State.FAILED;
74                  throw new MalformedChallengeException("Out of sequence NTLM response message");
75              } else if (this.state == State.MSG_TYPE1_GENERATED) {
76                  this.state = State.MSG_TYPE2_RECEVIED;
77              }
78          }
79      }
80  
81      @Override
82      public String getSchemeName() {
83          return "ntlm";
84      }
85  
86      @Override
87      public String getParameter(String name) {
88          // String parameters not supported
89          return null;
90      }
91  
92      @Override
93      public String getRealm() {
94          // NTLM does not support the concept of an authentication realm
95          return null;
96      }
97  
98      @Override
99      public boolean isConnectionBased() {
100         return true;
101     }
102 
103     @Override
104     public boolean isComplete() {
105         return this.state == State.MSG_TYPE3_GENERATED || this.state == State.FAILED;
106     }
107 
108     @Override
109     public Header authenticate(
110             final Credentials credentials,
111             final HttpRequest request,
112             final HttpContext httpContext) throws AuthenticationException {
113         this.httpContext = httpContext;
114         return authenticate(credentials, request);
115     }
116 
117 
118     @Override
119     public Header authenticate(Credentials credentials, HttpRequest request) throws AuthenticationException {
120         NTCredentials ntcredentials = null;
121         try {
122             ntcredentials = (NTCredentials) credentials;
123         } catch (final ClassCastException e) {
124             throw new InvalidCredentialsException(
125                     "Credentials cannot be used for NTLM authentication: "
126                             + credentials.getClass().getName());
127         }
128         String response = null;
129         if (this.state == State.FAILED) {
130             throw new AuthenticationException("NTLM authentication failed");
131         } else if (this.state == State.CHALLENGE_RECEIVED) {
132             response = this.engine.generateType1Msg(
133                     ntcredentials.getDomain(),
134                     ntcredentials.getWorkstation());
135             this.state = State.MSG_TYPE1_GENERATED;
136         } else if (this.state == State.MSG_TYPE2_RECEVIED) {
137             // retrieve certificate from connection and pass it to NTLM engine
138             ManagedHttpClientConnection routedConnection = (ManagedHttpClientConnection) httpContext.getAttribute(ExecutionContext.HTTP_CONNECTION);
139             try {
140                 Certificate[] certificates = routedConnection.getSSLSession().getPeerCertificates();
141                 this.engine.setPeerServerCertificate(certificates[0]);
142             } catch (SSLPeerUnverifiedException e) {
143                 throw new NTLMEngineException(e.getMessage(), e);
144             }
145             response = this.engine.generateType3Msg(
146                     ntcredentials.getUserName(),
147                     ntcredentials.getPassword(),
148                     ntcredentials.getDomain(),
149                     ntcredentials.getWorkstation(),
150                     this.challenge);
151             this.state = State.MSG_TYPE3_GENERATED;
152         } else {
153             throw new AuthenticationException("Unexpected state: " + this.state);
154         }
155         final CharArrayBuffer buffer = new CharArrayBuffer(32);
156         if (isProxy()) {
157             buffer.append(AUTH.PROXY_AUTH_RESP);
158         } else {
159             buffer.append(AUTH.WWW_AUTH_RESP);
160         }
161         buffer.append(": NTLM ");
162         buffer.append(response);
163         return new BufferedHeader(buffer);
164     }
165 }