1 /*
2  * ====================================================================
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  * ====================================================================
20  *
21  * This software consists of voluntary contributions made by many
22  * individuals on behalf of the Apache Software Foundation.  For more
23  * information on the Apache Software Foundation, please see
24  * <http://www.apache.org/>.
25  *
26  */
27 package ch.boye.httpclientandroidlib.impl.auth;
28 
29 import ch.boye.httpclientandroidlib.Header;
30 import ch.boye.httpclientandroidlib.HttpRequest;
31 import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
32 import ch.boye.httpclientandroidlib.auth.AUTH;
33 import ch.boye.httpclientandroidlib.auth.AuthenticationException;
34 import ch.boye.httpclientandroidlib.auth.Credentials;
35 import ch.boye.httpclientandroidlib.auth.InvalidCredentialsException;
36 import ch.boye.httpclientandroidlib.auth.MalformedChallengeException;
37 import ch.boye.httpclientandroidlib.auth.NTCredentials;
38 import ch.boye.httpclientandroidlib.message.BufferedHeader;
39 import ch.boye.httpclientandroidlib.util.Args;
40 import ch.boye.httpclientandroidlib.util.CharArrayBuffer;
41 
42 /**
43  * NTLM is a proprietary authentication scheme developed by Microsoft
44  * and optimized for Windows platforms.
45  *
46  * @since 4.0
47  */
48 @NotThreadSafe
49 public class NTLMScheme extends AuthSchemeBase {
50 
51     enum State {
52         UNINITIATED,
53         CHALLENGE_RECEIVED,
54         MSG_TYPE1_GENERATED,
55         MSG_TYPE2_RECEVIED,
56         MSG_TYPE3_GENERATED,
57         FAILED,
58     }
59 
60     private final NTLMEngine engine;
61 
62     private State state;
63     private String challenge;
64 
NTLMScheme(final NTLMEngine engine)65     public NTLMScheme(final NTLMEngine engine) {
66         super();
67         Args.notNull(engine, "NTLM engine");
68         this.engine = engine;
69         this.state = State.UNINITIATED;
70         this.challenge = null;
71     }
72 
73     /**
74      * @since 4.3
75      */
NTLMScheme()76     public NTLMScheme() {
77         this(new NTLMEngineImpl());
78     }
79 
getSchemeName()80     public String getSchemeName() {
81         return "ntlm";
82     }
83 
getParameter(final String name)84     public String getParameter(final String name) {
85         // String parameters not supported
86         return null;
87     }
88 
getRealm()89     public String getRealm() {
90         // NTLM does not support the concept of an authentication realm
91         return null;
92     }
93 
isConnectionBased()94     public boolean isConnectionBased() {
95         return true;
96     }
97 
98     @Override
parseChallenge( final CharArrayBuffer buffer, final int beginIndex, final int endIndex)99     protected void parseChallenge(
100             final CharArrayBuffer buffer,
101             final int beginIndex, final int endIndex) throws MalformedChallengeException {
102         this.challenge = buffer.substringTrimmed(beginIndex, endIndex);
103         if (this.challenge.length() == 0) {
104             if (this.state == State.UNINITIATED) {
105                 this.state = State.CHALLENGE_RECEIVED;
106             } else {
107                 this.state = State.FAILED;
108             }
109         } else {
110             if (this.state.compareTo(State.MSG_TYPE1_GENERATED) < 0) {
111                 this.state = State.FAILED;
112                 throw new MalformedChallengeException("Out of sequence NTLM response message");
113             } else if (this.state == State.MSG_TYPE1_GENERATED) {
114                 this.state = State.MSG_TYPE2_RECEVIED;
115             }
116         }
117     }
118 
authenticate( final Credentials credentials, final HttpRequest request)119     public Header authenticate(
120             final Credentials credentials,
121             final HttpRequest request) throws AuthenticationException {
122         NTCredentials ntcredentials = null;
123         try {
124             ntcredentials = (NTCredentials) credentials;
125         } catch (final ClassCastException e) {
126             throw new InvalidCredentialsException(
127              "Credentials cannot be used for NTLM authentication: "
128               + credentials.getClass().getName());
129         }
130         String response = null;
131         if (this.state == State.FAILED) {
132             throw new AuthenticationException("NTLM authentication failed");
133         } else if (this.state == State.CHALLENGE_RECEIVED) {
134             response = this.engine.generateType1Msg(
135                     ntcredentials.getDomain(),
136                     ntcredentials.getWorkstation());
137             this.state = State.MSG_TYPE1_GENERATED;
138         } else if (this.state == State.MSG_TYPE2_RECEVIED) {
139             response = this.engine.generateType3Msg(
140                     ntcredentials.getUserName(),
141                     ntcredentials.getPassword(),
142                     ntcredentials.getDomain(),
143                     ntcredentials.getWorkstation(),
144                     this.challenge);
145             this.state = State.MSG_TYPE3_GENERATED;
146         } else {
147             throw new AuthenticationException("Unexpected state: " + this.state);
148         }
149         final CharArrayBuffer buffer = new CharArrayBuffer(32);
150         if (isProxy()) {
151             buffer.append(AUTH.PROXY_AUTH_RESP);
152         } else {
153             buffer.append(AUTH.WWW_AUTH_RESP);
154         }
155         buffer.append(": NTLM ");
156         buffer.append(response);
157         return new BufferedHeader(buffer);
158     }
159 
isComplete()160     public boolean isComplete() {
161         return this.state == State.MSG_TYPE3_GENERATED || this.state == State.FAILED;
162     }
163 
164 }
165