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(), e);
099            }
100            return true;
101        } else {
102            log.debug("No NTLM Prompt done since NTLM Auth was found :" + ntlm.getUsername());
103            return false;
104        }
105    }
106
107    public UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest httpRequest,
108            HttpServletResponse httpResponse) {
109
110        log.debug("NTML handleRetrieveIdentity");
111        NtlmPasswordAuthentication ntlm;
112
113        try {
114            ntlm = negotiate(httpRequest, httpResponse, false);
115        } catch (IOException | ServletException e) {
116            log.error("NTLM negotiation failed : " + e.getMessage(), e);
117            return null;
118        }
119
120        if (ntlm == null) {
121            log.debug("Negotiation returned a null NTLM token");
122            return null;
123        } else {
124            log.debug("Negotiation succeed and returned a NTLM token, creating UserIdentificationInfo");
125            String userName = ntlm.getUsername();
126            log.debug("ntlm.getUsername() = " + userName);
127            if (userName.startsWith(ntlm.getDomain())) {
128                userName = userName.replace(ntlm.getDomain() + "/", "");
129            }
130            log.debug("userName = " + userName);
131            String password = ntlm.getPassword();
132            if (password == null || "".equals(password)) {
133                // we don't get the NTLM password, so we have to trust NTLM auth
134                UserIdentificationInfo userInfo = new UserIdentificationInfo(ntlm.getUsername(), "ITrustNTLM");
135                userInfo.setLoginPluginName("Trusting_LM");
136                return userInfo;
137            } else {
138                return new UserIdentificationInfo(ntlm.getUsername(), ntlm.getPassword());
139            }
140        }
141    }
142
143    public void initPlugin(Map<String, String> parameters) {
144
145        Config.setProperty(JCIFS_SMB_CLIENT_SO_TIMEOUT, "300000");
146        Config.setProperty(JCIFS_NETBIOS_CACHE_POLICY, "1200");
147
148        // init CIFS from parameters
149        for (String name : parameters.keySet()) {
150            if (name.startsWith(JCIFS_PREFIX)) {
151                Config.setProperty(name, parameters.get(name));
152            }
153        }
154
155        // get params from CIFS config
156        defaultDomain = Config.getProperty(JCIFS_SMB_CLIENT_DOMAIN);
157        domainController = Config.getProperty(JCIFS_HTTP_DOMAIN_CONTROLLER);
158        if (domainController == null) {
159            domainController = defaultDomain;
160            loadBalance = Config.getBoolean(JCIFS_HTTP_LOAD_BALANCE, true);
161        }
162    }
163
164    public Boolean needLoginPrompt(HttpServletRequest httpRequest) {
165        String useragent = httpRequest.getHeader("User-Agent").toLowerCase();
166
167        // only prompt on windows platform
168
169        if (!useragent.contains("windows")) {
170            log.debug("No NTLM LoginPrompt : User does not use Win32");
171            return false;
172        }
173
174        log.debug("NTLM LoginPrompt Needed");
175        return true;
176    }
177
178    public static NtlmPasswordAuthentication negotiate(HttpServletRequest req, HttpServletResponse resp,
179            boolean skipAuthentication) throws IOException, ServletException {
180        log.debug("NTLM negotiation starts");
181
182        String msg = req.getHeader("Authorization");
183
184        log.debug("NTLM negotiation header = " + msg);
185        NtlmPasswordAuthentication ntlm;
186        if (msg != null && msg.startsWith("NTLM ")) {
187            HttpSession ssn = req.getSession();
188            byte[] challenge;
189
190            UniAddress dc;
191            if (loadBalance) {
192                NtlmChallenge chal = (NtlmChallenge) ssn.getAttribute(NTLM_HTTP_CHAL_SESSION_KEY);
193                if (chal == null) {
194                    chal = SmbSession.getChallengeForDomain();
195                    ssn.setAttribute(NTLM_HTTP_CHAL_SESSION_KEY, chal);
196                }
197                dc = chal.dc;
198                challenge = chal.challenge;
199            } else {
200                dc = UniAddress.getByName(domainController, true);
201                dc = UniAddress.getByName(dc.getHostAddress(), true);
202                challenge = SmbSession.getChallenge(dc);
203            }
204
205            ntlm = NtlmSsp.authenticate(req, resp, challenge);
206            if (ntlm == null) {
207                log.debug("NtlmSsp.authenticate returned null");
208                return null;
209            }
210
211            log.debug("NtlmSsp.authenticate succeed");
212            log.debug("Domain controller is " + dc.getHostName());
213            if (ntlm.getDomain() != null) {
214                log.debug("NtlmSsp.authenticate => domain = " + ntlm.getDomain());
215            } else {
216                log.debug("NtlmSsp.authenticate => null domain");
217            }
218            if (ntlm.getUsername() != null) {
219                log.debug("NtlmSsp.authenticate => userName = " + ntlm.getUsername());
220            } else {
221                log.debug("NtlmSsp.authenticate => userName = null");
222            }
223            if (ntlm.getPassword() != null) {
224                log.debug("NtlmSsp.authenticate => password = " + ntlm.getPassword());
225            } else {
226                log.debug("NtlmSsp.authenticate => password = null");
227            }
228
229            /* negotiation complete, remove the challenge object */
230            ssn.removeAttribute(NTLM_HTTP_CHAL_SESSION_KEY);
231            if (!skipAuthentication) {
232                try {
233                    log.debug("Trying to logon NTLM session on dc " + dc.toString());
234                    SmbSession.logon(dc, ntlm);
235                    log.debug(ntlm + " successfully authenticated against " + dc);
236
237                } catch (SmbAuthException sae) {
238
239                    log.error(ntlm.getName() + ": 0x" + jcifs.util.Hexdump.toHexString(sae.getNtStatus(), 8) + ": "
240                            + sae);
241
242                    if (sae.getNtStatus() == NT_STATUS_ACCESS_VIOLATION) {
243                        /*
244                         * Server challenge no longer valid for externally supplied password hashes.
245                         */
246                        ssn = req.getSession(false);
247                        if (ssn != null) {
248                            ssn.removeAttribute(NTLM_HTTP_AUTH_SESSION_KEY);
249                        }
250                    }
251                    resp.setHeader("WWW-Authenticate", "NTLM");
252                    resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
253                    resp.setContentLength(0);
254                    resp.flushBuffer();
255                    return null;
256                }
257                req.getSession().setAttribute(NTLM_HTTP_AUTH_SESSION_KEY, ntlm);
258            }
259        } else {
260            log.debug("NTLM negotiation header is null");
261            return null;
262        }
263        return ntlm;
264    }
265
266}