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.AUTH;
25  import org.apache.http.auth.AuthenticationException;
26  import org.apache.http.auth.Credentials;
27  import org.apache.http.auth.InvalidCredentialsException;
28  import org.apache.http.auth.MalformedChallengeException;
29  import org.apache.http.auth.NTCredentials;
30  import org.apache.http.conn.ManagedHttpClientConnection;
31  import org.apache.http.impl.auth.AuthSchemeBase;
32  import org.apache.http.impl.auth.NTLMEngineException;
33  import org.apache.http.message.BufferedHeader;
34  import org.apache.http.protocol.HttpContext;
35  import org.apache.http.protocol.HttpCoreContext;
36  import org.apache.http.util.CharArrayBuffer;
37  
38  import javax.net.ssl.SSLPeerUnverifiedException;
39  import java.security.cert.Certificate;
40  
41  /**
42   * Duplicate of NTLMScheme from HttpClient to implement channel binding.
43   */
44  public class DavMailNTLMScheme extends AuthSchemeBase {
45      enum State {
46          UNINITIATED,
47          CHALLENGE_RECEIVED,
48          MSG_TYPE1_GENERATED,
49          MSG_TYPE2_RECEVIED,
50          MSG_TYPE3_GENERATED,
51          FAILED,
52      }
53  
54      private State state;
55      private final DavMailNTLMEngineImpl engine;
56      private HttpContext httpContext;
57  
58      private String challenge;
59  
60      public DavMailNTLMScheme() {
61          this.engine = new DavMailNTLMEngineImpl();
62          this.state = State.UNINITIATED;
63          this.challenge = null;
64      }
65  
66      @Override
67      protected void parseChallenge(CharArrayBuffer buffer, int beginIndex, int endIndex) throws MalformedChallengeException {
68          this.challenge = buffer.substringTrimmed(beginIndex, endIndex);
69          if (this.challenge.isEmpty()) {
70              if (this.state == State.UNINITIATED) {
71                  this.state = State.CHALLENGE_RECEIVED;
72              } else {
73                  this.state = State.FAILED;
74              }
75          } else {
76              if (this.state.compareTo(State.MSG_TYPE1_GENERATED) < 0) {
77                  this.state = State.FAILED;
78                  throw new MalformedChallengeException("Out of sequence NTLM response message");
79              } else if (this.state == State.MSG_TYPE1_GENERATED) {
80                  this.state = State.MSG_TYPE2_RECEVIED;
81              }
82          }
83      }
84  
85      @Override
86      public String getSchemeName() {
87          return "ntlm";
88      }
89  
90      @Override
91      public String getParameter(String name) {
92          // String parameters not supported
93          return null;
94      }
95  
96      @Override
97      public String getRealm() {
98          // NTLM does not support the concept of an authentication realm
99          return null;
100     }
101 
102     @Override
103     public boolean isConnectionBased() {
104         return true;
105     }
106 
107     @Override
108     public boolean isComplete() {
109         return this.state == State.MSG_TYPE3_GENERATED || this.state == State.FAILED;
110     }
111 
112     @Override
113     public Header authenticate(
114             final Credentials credentials,
115             final HttpRequest request,
116             final HttpContext httpContext) throws AuthenticationException {
117         this.httpContext = httpContext;
118         return authenticate(credentials, request);
119     }
120 
121 
122     @Override
123     @SuppressWarnings("deprecation")
124     public Header authenticate(Credentials credentials, HttpRequest request) throws AuthenticationException {
125         NTCredentials ntcredentials;
126         try {
127             ntcredentials = (NTCredentials) credentials;
128         } catch (final ClassCastException e) {
129             throw new InvalidCredentialsException(
130                     "Credentials cannot be used for NTLM authentication: "
131                             + credentials.getClass().getName());
132         }
133         String response;
134         if (this.state == State.FAILED) {
135             throw new AuthenticationException("NTLM authentication failed");
136         } else if (this.state == State.CHALLENGE_RECEIVED) {
137             response = this.engine.generateType1Msg(
138                     ntcredentials.getDomain(),
139                     ntcredentials.getWorkstation());
140             this.state = State.MSG_TYPE1_GENERATED;
141         } else if (this.state == State.MSG_TYPE2_RECEVIED) {
142             // retrieve certificate from connection and pass it to NTLM engine
143             ManagedHttpClientConnection routedConnection = (ManagedHttpClientConnection) httpContext.getAttribute(HttpCoreContext.HTTP_CONNECTION);
144             try {
145                 Certificate[] certificates = routedConnection.getSSLSession().getPeerCertificates();
146                 this.engine.setPeerServerCertificate(certificates[0]);
147             } catch (SSLPeerUnverifiedException e) {
148                 throw new NTLMEngineException(e.getMessage(), e);
149             }
150             response = this.engine.generateType3Msg(
151                     ntcredentials.getUserName(),
152                     ntcredentials.getPassword(),
153                     ntcredentials.getDomain(),
154                     ntcredentials.getWorkstation(),
155                     this.challenge);
156             this.state = State.MSG_TYPE3_GENERATED;
157         } else {
158             throw new AuthenticationException("Unexpected state: " + this.state);
159         }
160         final CharArrayBuffer buffer = new CharArrayBuffer(32);
161         if (isProxy()) {
162             buffer.append(AUTH.PROXY_AUTH_RESP);
163         } else {
164             buffer.append(AUTH.WWW_AUTH_RESP);
165         }
166         buffer.append(": NTLM ");
167         buffer.append(response);
168         return new BufferedHeader(buffer);
169     }
170 }