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