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  package davmail.http;
20  
21  import jcifs.ntlmssp.NtlmFlags;
22  import jcifs.ntlmssp.Type1Message;
23  import jcifs.ntlmssp.Type2Message;
24  import jcifs.ntlmssp.Type3Message;
25  import org.apache.commons.codec.binary.Base64;
26  import org.apache.commons.httpclient.Credentials;
27  import org.apache.commons.httpclient.HttpMethod;
28  import org.apache.commons.httpclient.NTCredentials;
29  import org.apache.commons.httpclient.auth.*;
30  import org.apache.commons.httpclient.util.EncodingUtil;
31  
32  import java.io.IOException;
33  
34  /**
35   * NTLMv2 scheme implementation.
36   */
37  public class NTLMv2Scheme implements AuthScheme {
38      private static final int UNINITIATED = 0;
39      private static final int INITIATED = 1;
40      private static final int TYPE1_MSG_GENERATED = 2;
41      private static final int TYPE2_MSG_RECEIVED = 3;
42      private static final int TYPE3_MSG_GENERATED = 4;
43      private static final int FAILED = Integer.MAX_VALUE;
44  
45      private Type2Message type2Message;
46      /**
47       * Authentication process state
48       */
49      private int state;
50  
51      /**
52       * Processes the NTLM challenge.
53       *
54       * @param challenge the challenge string
55       * @throws MalformedChallengeException is thrown if the authentication challenge
56       *                                     is malformed
57       */
58      public void processChallenge(final String challenge) throws MalformedChallengeException {
59          String authScheme = AuthChallengeParser.extractScheme(challenge);
60          if (!authScheme.equalsIgnoreCase(getSchemeName())) {
61              throw new MalformedChallengeException("Invalid NTLM challenge: " + challenge);
62          }
63          int spaceIndex = challenge.indexOf(' ');
64          if (spaceIndex != -1) {
65              try {
66                  type2Message = new Type2Message(Base64.decodeBase64(EncodingUtil.getBytes(
67                          challenge.substring(spaceIndex).trim(), "ASCII")));
68              } catch (IOException e) {
69                  throw new MalformedChallengeException("Invalid NTLM challenge: " + challenge, e);
70              }
71              this.state = TYPE2_MSG_RECEIVED;
72          } else {
73              this.type2Message = null;
74              if (this.state == UNINITIATED) {
75                  this.state = INITIATED;
76              } else {
77                  this.state = FAILED;
78              }
79          }
80      }
81  
82  
83      /**
84       * Returns textual designation of the NTLM authentication scheme.
85       *
86       * @return <code>ntlm</code>
87       */
88      public String getSchemeName() {
89          return "ntlm";
90      }
91  
92      /**
93       * Not used with NTLM.
94       *
95       * @return null
96       */
97      public String getParameter(String s) {
98          return null;
99      }
100 
101     /**
102      * Not used with NTLM.
103      *
104      * @return null
105      */
106     public String getRealm() {
107         return null;
108     }
109 
110     /**
111      * Deprecated.
112      */
113     @Deprecated
114     public String getID() {
115         throw new UnsupportedOperationException();
116     }
117 
118     /**
119      * NTLM is connection based.
120      *
121      * @return true
122      */
123     public boolean isConnectionBased() {
124         return true;
125     }
126 
127     /**
128      * Tests if the NTLM authentication process has been completed.
129      *
130      * @return <tt>true</tt> if authorization has been processed
131      */
132     public boolean isComplete() {
133         return state == TYPE3_MSG_GENERATED || state == FAILED;
134     }
135 
136     /**
137      * Not implemented.
138      *
139      * @param credentials user credentials
140      * @param method      method name
141      * @param uri         URI
142      * @return an NTLM authorization string
143      */
144     @Deprecated
145     public String authenticate(final Credentials credentials, String method, String uri) {
146         throw new UnsupportedOperationException();
147     }
148 
149     /**
150      * Produces NTLM authorization string for the given set of
151      * {@link Credentials}.
152      *
153      * @param credentials The set of credentials to be used for authentication
154      * @param httpMethod  The method being authenticated
155      * @return an NTLM authorization string
156      * @throws InvalidCredentialsException if authentication credentials
157      *                                     are not valid or not applicable for this authentication scheme
158      * @throws AuthenticationException     if authorization string cannot
159      *                                     be generated due to an authentication failure
160      */
161     public String authenticate(Credentials credentials, HttpMethod httpMethod) throws AuthenticationException {
162         if (this.state == UNINITIATED) {
163             throw new IllegalStateException("NTLM authentication process has not been initiated");
164         }
165 
166         NTCredentials ntcredentials;
167         try {
168             ntcredentials = (NTCredentials) credentials;
169         } catch (ClassCastException e) {
170             throw new InvalidCredentialsException(
171                     "Credentials cannot be used for NTLM authentication: "
172                             + credentials.getClass().getName());
173         }
174         String response;
175         if (this.state == INITIATED || this.state == FAILED) {
176             int flags = NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 | NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
177                     NtlmFlags.NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED | NtlmFlags.NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED |
178                     NtlmFlags.NTLMSSP_NEGOTIATE_NTLM | NtlmFlags.NTLMSSP_REQUEST_TARGET |
179                     NtlmFlags.NTLMSSP_NEGOTIATE_OEM | NtlmFlags.NTLMSSP_NEGOTIATE_UNICODE |
180                     NtlmFlags.NTLMSSP_NEGOTIATE_56 | NtlmFlags.NTLMSSP_NEGOTIATE_128;
181             Type1Message type1Message = new Type1Message(flags, ntcredentials.getDomain(), ntcredentials.getHost());
182             response = EncodingUtil.getAsciiString(Base64.encodeBase64(type1Message.toByteArray()));
183             this.state = TYPE1_MSG_GENERATED;
184         } else {
185             int flags = NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 | NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
186                     NtlmFlags.NTLMSSP_NEGOTIATE_NTLM | NtlmFlags.NTLMSSP_NEGOTIATE_UNICODE;
187             Type3Message type3Message = new Type3Message(type2Message, ntcredentials.getPassword(),
188                     ntcredentials.getDomain(), ntcredentials.getUserName(), ntcredentials.getHost(), flags);
189             response = EncodingUtil.getAsciiString(Base64.encodeBase64(type3Message.toByteArray()));
190             this.state = TYPE3_MSG_GENERATED;
191         }
192         return "NTLM " + response;
193     }
194 
195 
196 }