001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027package org.apache.http.impl.auth;
028
029import org.apache.http.Header;
030import org.apache.http.HttpRequest;
031import org.apache.http.auth.AUTH;
032import org.apache.http.auth.AuthenticationException;
033import org.apache.http.auth.Credentials;
034import org.apache.http.auth.InvalidCredentialsException;
035import org.apache.http.auth.MalformedChallengeException;
036import org.apache.http.auth.NTCredentials;
037import org.apache.http.message.BufferedHeader;
038import org.apache.http.util.Args;
039import org.apache.http.util.CharArrayBuffer;
040
041/**
042 * NTLM is a proprietary authentication scheme developed by Microsoft
043 * and optimized for Windows platforms.
044 *
045 * @since 4.0
046 */
047public class NTLMScheme extends AuthSchemeBase {
048
049    enum State {
050        UNINITIATED,
051        CHALLENGE_RECEIVED,
052        MSG_TYPE1_GENERATED,
053        MSG_TYPE2_RECEVIED,
054        MSG_TYPE3_GENERATED,
055        FAILED,
056    }
057
058    private final NTLMEngine engine;
059
060    private State state;
061    private String challenge;
062
063    public NTLMScheme(final NTLMEngine engine) {
064        super();
065        Args.notNull(engine, "NTLM engine");
066        this.engine = engine;
067        this.state = State.UNINITIATED;
068        this.challenge = null;
069    }
070
071    /**
072     * @since 4.3
073     */
074    public NTLMScheme() {
075        this(new NTLMEngineImpl());
076    }
077
078    @Override
079    public String getSchemeName() {
080        return "ntlm";
081    }
082
083    @Override
084    public String getParameter(final String name) {
085        // String parameters not supported
086        return null;
087    }
088
089    @Override
090    public String getRealm() {
091        // NTLM does not support the concept of an authentication realm
092        return null;
093    }
094
095    @Override
096    public boolean isConnectionBased() {
097        return true;
098    }
099
100    @Override
101    protected void parseChallenge(
102            final CharArrayBuffer buffer,
103            final int beginIndex, final int endIndex) throws MalformedChallengeException {
104        this.challenge = buffer.substringTrimmed(beginIndex, endIndex);
105        if (this.challenge.isEmpty()) {
106            if (this.state == State.UNINITIATED) {
107                this.state = State.CHALLENGE_RECEIVED;
108            } else {
109                this.state = State.FAILED;
110            }
111        } else {
112            if (this.state.compareTo(State.MSG_TYPE1_GENERATED) < 0) {
113                this.state = State.FAILED;
114                throw new MalformedChallengeException("Out of sequence NTLM response message");
115            } else if (this.state == State.MSG_TYPE1_GENERATED) {
116                this.state = State.MSG_TYPE2_RECEVIED;
117            }
118        }
119    }
120
121    @Override
122    public Header authenticate(
123            final Credentials credentials,
124            final HttpRequest request) throws AuthenticationException {
125        NTCredentials ntcredentials = null;
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 = null;
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            response = this.engine.generateType3Msg(
143                    ntcredentials.getUserName(),
144                    ntcredentials.getPassword(),
145                    ntcredentials.getDomain(),
146                    ntcredentials.getWorkstation(),
147                    this.challenge);
148            this.state = State.MSG_TYPE3_GENERATED;
149        } else {
150            throw new AuthenticationException("Unexpected state: " + this.state);
151        }
152        final CharArrayBuffer buffer = new CharArrayBuffer(32);
153        if (isProxy()) {
154            buffer.append(AUTH.PROXY_AUTH_RESP);
155        } else {
156            buffer.append(AUTH.WWW_AUTH_RESP);
157        }
158        buffer.append(": NTLM ");
159        buffer.append(response);
160        return new BufferedHeader(buffer);
161    }
162
163    @Override
164    public boolean isComplete() {
165        return this.state == State.MSG_TYPE3_GENERATED || this.state == State.FAILED;
166    }
167
168}