001/*
002 * (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *
019 * $Id: JOOoConvertPluginImpl.java 18651 2007-05-13 20:28:53Z sfermigier $
020 */
021
022package org.nuxeo.ecm.platform.ui.web.auth.ntlm;
023
024import java.io.IOException;
025import java.util.List;
026import java.util.Map;
027
028import javax.servlet.ServletException;
029import javax.servlet.http.HttpServletRequest;
030import javax.servlet.http.HttpServletResponse;
031import javax.servlet.http.HttpSession;
032
033import jcifs.Config;
034import jcifs.UniAddress;
035import jcifs.http.NtlmSsp;
036
037import jcifs.smb.NtlmChallenge;
038import jcifs.smb.NtlmPasswordAuthentication;
039import jcifs.smb.SmbAuthException;
040import jcifs.smb.SmbSession;
041import org.apache.commons.logging.Log;
042import org.apache.commons.logging.LogFactory;
043import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo;
044import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPlugin;
045
046import static jcifs.smb.NtStatus.NT_STATUS_ACCESS_VIOLATION;
047
048public class NTLMAuthenticator implements NuxeoAuthenticationPlugin {
049
050    private static final String JCIFS_PREFIX = "jcifs.";
051
052    public static final String JCIFS_NETBIOS_CACHE_POLICY = "jcifs.netbios.cachePolicy";
053
054    public static final String JCIFS_SMB_CLIENT_SO_TIMEOUT = "jcifs.smb.client.soTimeout";
055
056    public static final String JCIFS_HTTP_LOAD_BALANCE = "jcifs.http.loadBalance";
057
058    public static final String JCIFS_HTTP_DOMAIN_CONTROLLER = "jcifs.http.domainController";
059
060    public static final String JCIFS_SMB_CLIENT_DOMAIN = "jcifs.smb.client.domain";
061
062    public static final boolean FORCE_SESSION_CREATION = true;
063
064    public static final String NTLM_HTTP_AUTH_SESSION_KEY = "NtlmHttpAuth";
065
066    public static final String NTLM_HTTP_CHAL_SESSION_KEY = "NtlmHttpChal";
067
068    protected static String defaultDomain;
069
070    protected static String domainController;
071
072    protected static boolean loadBalance;
073
074    private static final Log log = LogFactory.getLog(NTLMAuthenticator.class);
075
076    public List<String> getUnAuthenticatedURLPrefix() {
077        return null;
078    }
079
080    public Boolean handleLoginPrompt(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String baseURL) {
081
082        log.debug("Handle NTLM login prompt");
083        NtlmPasswordAuthentication ntlm = null;
084        HttpSession httpSession = httpRequest.getSession(FORCE_SESSION_CREATION);
085
086        if (httpSession != null) {
087            ntlm = (NtlmPasswordAuthentication) httpSession.getAttribute(NTLM_HTTP_AUTH_SESSION_KEY);
088        }
089
090        if (httpSession == null || ntlm == null) {
091            log.debug("Sending NTLM Challenge/Response request to browser");
092            httpResponse.setHeader("WWW-Authenticate", "NTLM");
093            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
094            httpResponse.setContentLength(0);
095            try {
096                httpResponse.flushBuffer();
097            } catch (IOException e) {
098                log.error("Error while flushing buffer:" + e.getMessage());
099                e.printStackTrace();
100            }
101            return true;
102        } else {
103            log.debug("No NTLM Prompt done since NTLM Auth was found :" + ntlm.getUsername());
104            return false;
105        }
106    }
107
108    public UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest httpRequest,
109            HttpServletResponse httpResponse) {
110
111        log.debug("NTML handleRetrieveIdentity");
112        NtlmPasswordAuthentication ntlm;
113
114        try {
115            ntlm = negotiate(httpRequest, httpResponse, false);
116        } catch (IOException | ServletException e) {
117            log.error("NTLM negotiation failed : " + e.getMessage(), e);
118            return null;
119        }
120
121        if (ntlm == null) {
122            log.debug("Negotiation returned a null NTLM token");
123            return null;
124        } else {
125            log.debug("Negotiation succeed and returned a NTLM token, creating UserIdentificationInfo");
126            String userName = ntlm.getUsername();
127            log.debug("ntlm.getUsername() = " + userName);
128            if (userName.startsWith(ntlm.getDomain())) {
129                userName = userName.replace(ntlm.getDomain() + "/", "");
130            }
131            log.debug("userName = " + userName);
132            String password = ntlm.getPassword();
133            if (password == null || "".equals(password)) {
134                // we don't get the NTLM password, so we have to trust NTLM auth
135                UserIdentificationInfo userInfo = new UserIdentificationInfo(ntlm.getUsername(), "ITrustNTLM");
136                userInfo.setLoginPluginName("Trusting_LM");
137                return userInfo;
138            } else {
139                return new UserIdentificationInfo(ntlm.getUsername(), ntlm.getPassword());
140            }
141        }
142    }
143
144    public void initPlugin(Map<String, String> parameters) {
145
146        Config.setProperty(JCIFS_SMB_CLIENT_SO_TIMEOUT, "300000");
147        Config.setProperty(JCIFS_NETBIOS_CACHE_POLICY, "1200");
148
149        // init CIFS from parameters
150        for (String name : parameters.keySet()) {
151            if (name.startsWith(JCIFS_PREFIX)) {
152                Config.setProperty(name, parameters.get(name));
153            }
154        }
155
156        // get params from CIFS config
157        defaultDomain = Config.getProperty(JCIFS_SMB_CLIENT_DOMAIN);
158        domainController = Config.getProperty(JCIFS_HTTP_DOMAIN_CONTROLLER);
159        if (domainController == null) {
160            domainController = defaultDomain;
161            loadBalance = Config.getBoolean(JCIFS_HTTP_LOAD_BALANCE, true);
162        }
163    }
164
165    public Boolean needLoginPrompt(HttpServletRequest httpRequest) {
166        String useragent = httpRequest.getHeader("User-Agent").toLowerCase();
167
168        // only prompt on windows platform
169
170        if (!useragent.contains("windows")) {
171            log.debug("No NTLM LoginPrompt : User does not use Win32");
172            return false;
173        }
174
175        log.debug("NTLM LoginPrompt Needed");
176        return true;
177    }
178
179    public static NtlmPasswordAuthentication negotiate(HttpServletRequest req, HttpServletResponse resp,
180            boolean skipAuthentication) throws IOException, ServletException {
181        log.debug("NTLM negotiation starts");
182
183        String msg = req.getHeader("Authorization");
184
185        log.debug("NTLM negotiation header = " + msg);
186        NtlmPasswordAuthentication ntlm;
187        if (msg != null && msg.startsWith("NTLM ")) {
188            HttpSession ssn = req.getSession();
189            byte[] challenge;
190
191            UniAddress dc;
192            if (loadBalance) {
193                NtlmChallenge chal = (NtlmChallenge) ssn.getAttribute(NTLM_HTTP_CHAL_SESSION_KEY);
194                if (chal == null) {
195                    chal = SmbSession.getChallengeForDomain();
196                    ssn.setAttribute(NTLM_HTTP_CHAL_SESSION_KEY, chal);
197                }
198                dc = chal.dc;
199                challenge = chal.challenge;
200            } else {
201                dc = UniAddress.getByName(domainController, true);
202                dc = UniAddress.getByName(dc.getHostAddress(), true);
203                challenge = SmbSession.getChallenge(dc);
204            }
205
206            ntlm = NtlmSsp.authenticate(req, resp, challenge);
207            if (ntlm == null) {
208                log.debug("NtlmSsp.authenticate returned null");
209                return null;
210            }
211
212            log.debug("NtlmSsp.authenticate succeed");
213            log.debug("Domain controller is " + dc.getHostName());
214            if (ntlm.getDomain() != null) {
215                log.debug("NtlmSsp.authenticate => domain = " + ntlm.getDomain());
216            } else {
217                log.debug("NtlmSsp.authenticate => null domain");
218            }
219            if (ntlm.getUsername() != null) {
220                log.debug("NtlmSsp.authenticate => userName = " + ntlm.getUsername());
221            } else {
222                log.debug("NtlmSsp.authenticate => userName = null");
223            }
224            if (ntlm.getPassword() != null) {
225                log.debug("NtlmSsp.authenticate => password = " + ntlm.getPassword());
226            } else {
227                log.debug("NtlmSsp.authenticate => password = null");
228            }
229
230            /* negotiation complete, remove the challenge object */
231            ssn.removeAttribute(NTLM_HTTP_CHAL_SESSION_KEY);
232            if (!skipAuthentication) {
233                try {
234                    log.debug("Trying to logon NTLM session on dc " + dc.toString());
235                    SmbSession.logon(dc, ntlm);
236                    log.debug(ntlm + " successfully authenticated against " + dc);
237
238                } catch (SmbAuthException sae) {
239
240                    log.error(ntlm.getName() + ": 0x" + jcifs.util.Hexdump.toHexString(sae.getNtStatus(), 8) + ": "
241                            + sae);
242
243                    if (sae.getNtStatus() == NT_STATUS_ACCESS_VIOLATION) {
244                        /*
245                         * Server challenge no longer valid for externally supplied password hashes.
246                         */
247                        ssn = req.getSession(false);
248                        if (ssn != null) {
249                            ssn.removeAttribute(NTLM_HTTP_AUTH_SESSION_KEY);
250                        }
251                    }
252                    resp.setHeader("WWW-Authenticate", "NTLM");
253                    resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
254                    resp.setContentLength(0);
255                    resp.flushBuffer();
256                    return null;
257                }
258                req.getSession().setAttribute(NTLM_HTTP_AUTH_SESSION_KEY, ntlm);
259            }
260        } else {
261            log.debug("NTLM negotiation header is null");
262            return null;
263        }
264        return ntlm;
265    }
266
267}