View Javadoc
1   /**
2    * Waffle (https://github.com/dblock/waffle)
3    *
4    * Copyright (c) 2010 - 2015 Application Security, Inc.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of the Eclipse Public License v1.0
8    * which accompanies this distribution, and is available at
9    * http://www.eclipse.org/legal/epl-v10.html
10   *
11   * Contributors:
12   *     Application Security, Inc.
13   */
14  package waffle.apache;
15  
16  import java.io.IOException;
17  import java.security.Principal;
18  
19  import javax.servlet.http.HttpServletResponse;
20  import javax.servlet.http.HttpSession;
21  
22  import org.apache.catalina.LifecycleException;
23  import org.apache.catalina.connector.Request;
24  import org.apache.catalina.deploy.LoginConfig;
25  import org.slf4j.LoggerFactory;
26  
27  import com.google.common.io.BaseEncoding;
28  import com.sun.jna.platform.win32.Win32Exception;
29  
30  import waffle.util.AuthorizationHeader;
31  import waffle.util.NtlmServletRequest;
32  import waffle.windows.auth.IWindowsIdentity;
33  import waffle.windows.auth.IWindowsSecurityContext;
34  
35  /**
36   * An Apache Negotiate (NTLM, Kerberos) Authenticator.
37   * 
38   * @author dblock[at]dblock[dot]org
39   */
40  public class NegotiateAuthenticator extends WaffleAuthenticatorBase {
41  
42      /**
43       * Instantiates a new negotiate authenticator.
44       */
45      public NegotiateAuthenticator() {
46          super();
47          this.log = LoggerFactory.getLogger(NegotiateAuthenticator.class);
48          this.info = "waffle.apache.NegotiateAuthenticator/1.0";
49          this.log.debug("[waffle.apache.NegotiateAuthenticator] loaded");
50      }
51  
52      /* (non-Javadoc)
53       * @see org.apache.catalina.authenticator.AuthenticatorBase#startInternal()
54       */
55      @Override
56      public synchronized void startInternal() throws LifecycleException {
57          this.log.info("[waffle.apache.NegotiateAuthenticator] started");
58          super.startInternal();
59      }
60  
61      /* (non-Javadoc)
62       * @see org.apache.catalina.authenticator.AuthenticatorBase#stopInternal()
63       */
64      @Override
65      public synchronized void stopInternal() throws LifecycleException {
66          super.stopInternal();
67          this.log.info("[waffle.apache.NegotiateAuthenticator] stopped");
68      }
69  
70      /* (non-Javadoc)
71       * @see org.apache.catalina.authenticator.AuthenticatorBase#authenticate(org.apache.catalina.connector.Request, javax.servlet.http.HttpServletResponse, org.apache.catalina.deploy.LoginConfig)
72       */
73      @Override
74      public boolean authenticate(final Request request, final HttpServletResponse response, final LoginConfig loginConfig) {
75  
76          Principal principal = request.getUserPrincipal();
77          final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
78          final boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();
79  
80          this.log.debug("{} {}, contentlength: {}", request.getMethod(), request.getRequestURI(),
81                  Integer.valueOf(request.getContentLength()));
82          this.log.debug("authorization: {}, ntlm post: {}", authorizationHeader, Boolean.valueOf(ntlmPost));
83  
84          if (principal != null && !ntlmPost) {
85              // user already authenticated
86              this.log.debug("previously authenticated user: {}", principal.getName());
87              return true;
88          }
89  
90          // authenticate user
91          if (!authorizationHeader.isNull()) {
92  
93              final String securityPackage = authorizationHeader.getSecurityPackage();
94              // maintain a connection-based session for NTLM tokens
95              final String connectionId = NtlmServletRequest.getConnectionId(request);
96  
97              this.log.debug("security package: {}, connection id: {}", securityPackage, connectionId);
98  
99              if (ntlmPost) {
100                 // type 1 NTLM authentication message received
101                 this.auth.resetSecurityToken(connectionId);
102             }
103 
104             // log the user in using the token
105             IWindowsSecurityContext securityContext;
106 
107             try {
108                 final byte[] tokenBuffer = authorizationHeader.getTokenBytes();
109                 this.log.debug("token buffer: {} byte(s)", Integer.valueOf(tokenBuffer.length));
110                 try {
111                     securityContext = this.auth.acceptSecurityToken(connectionId, tokenBuffer, securityPackage);
112                 } catch (final Win32Exception e) {
113                     this.log.warn("error logging in user: {}", e.getMessage());
114                     this.log.trace("{}", e);
115                     this.sendUnauthorized(response);
116                     return false;
117                 }
118                 this.log.debug("continue required: {}", Boolean.valueOf(securityContext.isContinue()));
119 
120                 final byte[] continueTokenBytes = securityContext.getToken();
121                 if (continueTokenBytes != null && continueTokenBytes.length > 0) {
122                     final String continueToken = BaseEncoding.base64().encode(continueTokenBytes);
123                     this.log.debug("continue token: {}", continueToken);
124                     response.addHeader("WWW-Authenticate", securityPackage + " " + continueToken);
125                 }
126 
127                 if (securityContext.isContinue() || ntlmPost) {
128                     response.setHeader("Connection", "keep-alive");
129                     response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
130                     response.flushBuffer();
131                     return false;
132                 }
133 
134             } catch (final IOException e) {
135                 this.log.warn("error logging in user: {}", e.getMessage());
136                 this.log.trace("{}", e);
137                 this.sendUnauthorized(response);
138                 return false;
139             }
140 
141             // realm: fail if no realm is configured
142             if (this.context == null || this.context.getRealm() == null) {
143                 this.log.warn("missing context/realm");
144                 this.sendError(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE);
145                 return false;
146             }
147 
148             // create and register the user principal with the session
149             final IWindowsIdentity windowsIdentity = securityContext.getIdentity();
150 
151             // disable guest login
152             if (!this.allowGuestLogin && windowsIdentity.isGuest()) {
153                 this.log.warn("guest login disabled: {}", windowsIdentity.getFqn());
154                 this.sendUnauthorized(response);
155                 return false;
156             }
157 
158             try {
159                 this.log.debug("logged in user: {} ({})", windowsIdentity.getFqn(), windowsIdentity.getSidString());
160 
161                 final GenericWindowsPrincipal windowsPrincipal = new GenericWindowsPrincipal(windowsIdentity,
162                         this.principalFormat, this.roleFormat);
163 
164                 this.log.debug("roles: {}", windowsPrincipal.getRolesString());
165 
166                 principal = windowsPrincipal;
167 
168                 // create a session associated with this request if there's none
169                 final HttpSession session = request.getSession(true);
170                 this.log.debug("session id: {}", session == null ? "null" : session.getId());
171 
172                 // register the authenticated principal
173                 this.register(request, response, principal, securityPackage, principal.getName(), null);
174                 this.log.info("successfully logged in user: {}", principal.getName());
175 
176             } finally {
177                 windowsIdentity.dispose();
178             }
179 
180             return true;
181         }
182 
183         this.log.debug("authorization required");
184         this.sendUnauthorized(response);
185         return false;
186     }
187 }