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.connector.Request;
23  import org.apache.catalina.connector.Response;
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#start()
54       */
55      @Override
56      public void start() {
57          this.log.info("[waffle.apache.NegotiateAuthenticator] started");
58      }
59  
60      /* (non-Javadoc)
61       * @see org.apache.catalina.authenticator.AuthenticatorBase#stop()
62       */
63      @Override
64      public void stop() {
65          this.log.info("[waffle.apache.NegotiateAuthenticator] stopped");
66      }
67  
68      /* (non-Javadoc)
69       * @see org.apache.catalina.authenticator.AuthenticatorBase#authenticate(org.apache.catalina.connector.Request, org.apache.catalina.connector.Response, org.apache.catalina.deploy.LoginConfig)
70       */
71      @Override
72      public boolean authenticate(final Request request, final Response response, final LoginConfig loginConfig) {
73  
74          Principal principal = request.getUserPrincipal();
75          final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request);
76          final boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();
77  
78          this.log.debug("{} {}, contentlength: {}", request.getMethod(), request.getRequestURI(),
79                  Integer.valueOf(request.getContentLength()));
80          this.log.debug("authorization: {}, ntlm post: {}", authorizationHeader, Boolean.valueOf(ntlmPost));
81  
82          if (principal != null && !ntlmPost) {
83              // user already authenticated
84              this.log.debug("previously authenticated user: {}", principal.getName());
85              return true;
86          }
87  
88          // authenticate user
89          if (!authorizationHeader.isNull()) {
90  
91              final String securityPackage = authorizationHeader.getSecurityPackage();
92              // maintain a connection-based session for NTLM tokens
93              final String connectionId = NtlmServletRequest.getConnectionId(request);
94  
95              this.log.debug("security package: {}, connection id: {}", securityPackage, connectionId);
96  
97              if (ntlmPost) {
98                  // type 1 NTLM authentication message received
99                  this.auth.resetSecurityToken(connectionId);
100             }
101 
102             // log the user in using the token
103             IWindowsSecurityContext securityContext;
104 
105             try {
106                 final byte[] tokenBuffer = authorizationHeader.getTokenBytes();
107                 this.log.debug("token buffer: {} byte(s)", Integer.valueOf(tokenBuffer.length));
108                 try {
109                     securityContext = this.auth.acceptSecurityToken(connectionId, tokenBuffer, securityPackage);
110                 } catch (final Win32Exception e) {
111                     this.log.warn("error logging in user: {}", e.getMessage());
112                     this.log.trace("{}", e);
113                     this.sendUnauthorized(response);
114                     return false;
115                 }
116                 this.log.debug("continue required: {}", Boolean.valueOf(securityContext.isContinue()));
117 
118                 final byte[] continueTokenBytes = securityContext.getToken();
119                 if (continueTokenBytes != null && continueTokenBytes.length > 0) {
120                     final String continueToken = BaseEncoding.base64().encode(continueTokenBytes);
121                     this.log.debug("continue token: {}", continueToken);
122                     response.addHeader("WWW-Authenticate", securityPackage + " " + continueToken);
123                 }
124 
125                 if (securityContext.isContinue() || ntlmPost) {
126                     response.setHeader("Connection", "keep-alive");
127                     response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
128                     response.flushBuffer();
129                     return false;
130                 }
131 
132             } catch (final IOException e) {
133                 this.log.warn("error logging in user: {}", e.getMessage());
134                 this.log.trace("{}", e);
135                 this.sendUnauthorized(response);
136                 return false;
137             }
138 
139             // realm: fail if no realm is configured
140             if (this.context == null || this.context.getRealm() == null) {
141                 this.log.warn("missing context/realm");
142                 this.sendError(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE);
143                 return false;
144             }
145 
146             // create and register the user principal with the session
147             final IWindowsIdentity windowsIdentity = securityContext.getIdentity();
148 
149             // disable guest login
150             if (!this.allowGuestLogin && windowsIdentity.isGuest()) {
151                 this.log.warn("guest login disabled: {}", windowsIdentity.getFqn());
152                 this.sendUnauthorized(response);
153                 return false;
154             }
155 
156             try {
157                 this.log.debug("logged in user: {} ({})", windowsIdentity.getFqn(), windowsIdentity.getSidString());
158 
159                 final GenericWindowsPrincipal windowsPrincipal = new GenericWindowsPrincipal(windowsIdentity,
160                         this.context.getRealm(), this.principalFormat, this.roleFormat);
161 
162                 this.log.debug("roles: {}", windowsPrincipal.getRolesString());
163 
164                 principal = windowsPrincipal;
165 
166                 // create a session associated with this request if there's none
167                 final HttpSession session = request.getSession(true);
168                 this.log.debug("session id: {}", session == null ? "null" : session.getId());
169 
170                 // register the authenticated principal
171                 this.register(request, response, principal, securityPackage, principal.getName(), null);
172                 this.log.info("successfully logged in user: {}", principal.getName());
173 
174             } finally {
175                 windowsIdentity.dispose();
176             }
177 
178             return true;
179         }
180 
181         this.log.debug("authorization required");
182         this.sendUnauthorized(response);
183         return false;
184     }
185 }