View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.server.ldap.handlers.bind;
21  
22  
23  import org.apache.directory.server.constants.ServerDNConstants;
24  import org.apache.directory.server.core.CoreSession;
25  import org.apache.directory.server.core.DirectoryService;
26  import org.apache.directory.server.ldap.LdapSession;
27  import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
28  import org.apache.directory.shared.ldap.entry.EntryAttribute;
29  import org.apache.directory.shared.ldap.exception.LdapException;
30  import org.apache.directory.shared.ldap.message.BindRequest;
31  import org.apache.directory.shared.ldap.message.LdapResult;
32  import org.apache.directory.shared.ldap.message.MutableControl;
33  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
34  import org.apache.directory.shared.ldap.name.LdapDN;
35  import org.apache.directory.shared.ldap.util.ExceptionUtils;
36  import org.apache.directory.shared.ldap.util.StringTools;
37  import org.apache.mina.common.IoSession;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  import javax.naming.Context;
42  import javax.naming.NamingException;
43  import javax.naming.ldap.InitialLdapContext;
44  import javax.naming.ldap.LdapContext;
45  import javax.security.auth.callback.Callback;
46  import javax.security.auth.callback.CallbackHandler;
47  import javax.security.auth.callback.NameCallback;
48  import javax.security.auth.callback.PasswordCallback;
49  import javax.security.sasl.AuthorizeCallback;
50  import javax.security.sasl.RealmCallback;
51  import java.util.Hashtable;
52  
53  
54  /**
55   * Base class for all SASL {@link CallbackHandler}s.  Implementations of SASL mechanisms
56   * selectively override the methods relevant to their mechanism.
57   * 
58   * @see javax.security.auth.callback.CallbackHandler
59   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
60   * @version $Rev$, $Date$
61   */
62  public abstract class AbstractSaslCallbackHandler implements CallbackHandler
63  {
64      /** The logger instance */
65      private static final Logger LOG = LoggerFactory.getLogger( AbstractSaslCallbackHandler.class );
66  
67      /** An empty control array */ 
68      private static final MutableControl[] EMPTY = new MutableControl[0];
69  
70      private String username;
71      private String realm;
72  
73      /** The reference on the user ldap session */
74      protected LdapSession ldapSession;
75      
76      /** The admin core session */
77      protected CoreSession adminSession;
78  
79      /** A reference on the DirectoryService instance */
80      protected final DirectoryService directoryService;
81      
82      /** The associated BindRequest */
83      protected final BindRequest bindRequest;
84  
85  
86      /**
87       * Creates a new instance of AbstractSaslCallbackHandler.
88       *
89       * @param directoryService
90       */
91      protected AbstractSaslCallbackHandler( DirectoryService directoryService, BindRequest bindRequest )
92      {
93          this.directoryService = directoryService;
94          this.bindRequest = bindRequest;
95      }
96  
97  
98      /**
99       * Implementors use this method to access the username resulting from a callback.
100      * Callback default name will be username, eg 'hnelson', for CRAM-MD5 and DIGEST-MD5.
101      * The {@link NameCallback} is not used by GSSAPI.
102      */
103     protected String getUsername()
104     {
105         return username;
106     }
107 
108 
109     /**
110      * Implementors use this method to access the realm resulting from a callback.
111      * Callback default text will be realm name, eg 'example.com', for DIGEST-MD5.
112      * The {@link RealmCallback} is not used by GSSAPI nor by CRAM-MD5.
113      */
114     protected String getRealm()
115     {
116         return realm;
117     }
118 
119     /**
120      * Implementors set the password based on a lookup, using the username and
121      * realm as keys.
122      * <ul>
123      * <li>For DIGEST-MD5, lookup password based on username and realm.
124      * <li>For CRAM-MD5, lookup password based on username.
125      * <li>For GSSAPI, this callback is unused.
126      * </ul>
127      * @param username The username.
128      * @param realm The realm.
129      * @return The Password entry attribute resulting from the lookup. It may contain more than one password
130      */
131     protected abstract EntryAttribute lookupPassword( String username, String realm );
132 
133 
134     /**
135      * Final check to authorize user.  Used by all SASL mechanisms.  This
136      * is the only callback used by GSSAPI.
137      * 
138      * Implementors use setAuthorizedID() to set the base DN after canonicalization.
139      * Implementors must setAuthorized() to <code>true</code> if authentication was successful.
140      * 
141      * @param callback An {@link AuthorizeCallback}.
142      */
143     protected abstract void authorize( AuthorizeCallback callback ) throws Exception;
144 
145 
146     /**
147      * SaslServer will use this method to call various callbacks, depending on the SASL
148      * mechanism in use for a session.
149      * 
150      * @param callbacks An array of one or more callbacks.
151      */
152     public void handle( Callback[] callbacks )
153     {
154         for ( int i = 0; i < callbacks.length; i++ )
155         {
156             Callback callback = callbacks[i];
157 
158             if ( LOG.isDebugEnabled() )
159             {
160                 LOG.debug( "Processing callback {} of {}: {}" + callback.getClass(), ( i + 1 ), callbacks.length );
161             }
162 
163             if ( callback instanceof NameCallback )
164             {
165                 NameCallback nameCB = ( NameCallback ) callback;
166                 LOG.debug( "NameCallback default name:  {}", nameCB.getDefaultName() );
167 
168                 username = nameCB.getDefaultName();
169             }
170             else if ( callback instanceof RealmCallback )
171             {
172                 RealmCallback realmCB = ( RealmCallback ) callback;
173                 LOG.debug( "RealmCallback default text:  {}", realmCB.getDefaultText() );
174 
175                 realm = realmCB.getDefaultText();
176             }
177             else if ( callback instanceof PasswordCallback )
178             {
179                 PasswordCallback passwordCB = ( PasswordCallback ) callback;
180                 EntryAttribute userPassword = lookupPassword( getUsername(), getRealm() );
181 
182                 if ( userPassword != null )
183                 {
184                     // We assume that we have only one password available
185                     byte[] password = (byte[])userPassword.get().get();
186                     
187                     String strPassword = StringTools.utf8ToString( password );
188                     passwordCB.setPassword( strPassword.toCharArray() );
189                 }
190             }
191             else if ( callback instanceof AuthorizeCallback )
192             {
193                 AuthorizeCallback authorizeCB = ( AuthorizeCallback ) callback;
194 
195                 // hnelson (CRAM-MD5, DIGEST-MD5)
196                 // hnelson@EXAMPLE.COM (GSSAPI)
197                 LOG.debug( "AuthorizeCallback authnID:  {}", authorizeCB.getAuthenticationID() );
198 
199                 // hnelson (CRAM-MD5, DIGEST-MD5)
200                 // hnelson@EXAMPLE.COM (GSSAPI)
201                 LOG.debug( "AuthorizeCallback authzID:  {}", authorizeCB.getAuthorizationID() );
202 
203                 // null (CRAM-MD5, DIGEST-MD5, GSSAPI)
204                 LOG.debug( "AuthorizeCallback authorizedID:  {}", authorizeCB.getAuthorizedID() );
205 
206                 // false (CRAM-MD5, DIGEST-MD5, GSSAPI)
207                 LOG.debug( "AuthorizeCallback isAuthorized:  {}", authorizeCB.isAuthorized() );
208 
209                 try
210                 {
211                     authorize( authorizeCB );
212                 }
213                 catch ( Exception e )
214                 {
215                     // TODO - figure out how to handle this properly.
216                     throw new RuntimeException( "Failed authorization in callback handler.", e );
217                 }
218             }
219         }
220     }
221 
222 
223     /**
224      * Convenience method for acquiring an {@link LdapContext} for the client to use for the
225      * duration of a session.
226      * 
227      * @param session The current session.
228      * @param bindRequest The current BindRequest.
229      * @param env An environment to be used to acquire an {@link LdapContext}.
230      * @return An {@link LdapContext} for the client.
231      */
232     protected LdapContext getContext( IoSession session, BindRequest bindRequest, Hashtable<String, Object> env )
233     {
234         LdapResult result = bindRequest.getResultResponse().getLdapResult();
235 
236         LdapContext ctx = null;
237 
238         try
239         {
240             MutableControl[] connCtls = bindRequest.getControls().values().toArray( EMPTY );
241             env.put( DirectoryService.JNDI_KEY, directoryService );
242             ctx = new InitialLdapContext( env, connCtls );
243         }
244         catch ( NamingException e )
245         {
246             ResultCodeEnum code;
247 
248             if ( e instanceof LdapException )
249             {
250                 code = ( ( LdapException ) e ).getResultCode();
251                 result.setResultCode( code );
252             }
253             else
254             {
255                 code = ResultCodeEnum.getBestEstimate( e, bindRequest.getType() );
256                 result.setResultCode( code );
257             }
258 
259             String msg = "Bind failed: " + e.getMessage();
260 
261             if ( LOG.isDebugEnabled() )
262             {
263                 msg += ":\n" + ExceptionUtils.getStackTrace( e );
264                 msg += "\n\nBindRequest = \n" + bindRequest.toString();
265             }
266 
267             if ( ( e.getResolvedName() != null )
268                 && ( ( code == ResultCodeEnum.NO_SUCH_OBJECT ) || ( code == ResultCodeEnum.ALIAS_PROBLEM )
269                     || ( code == ResultCodeEnum.INVALID_DN_SYNTAX ) || ( code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM ) ) )
270             {
271                 result.setMatchedDn( ( LdapDN ) e.getResolvedName() );
272             }
273 
274             result.setErrorMessage( msg );
275             session.write( bindRequest.getResultResponse() );
276             ctx = null;
277         }
278 
279         return ctx;
280     }
281 
282 
283     /**
284      * Convenience method for getting an environment suitable for acquiring
285      * an {@link LdapContext} for the client.
286      * 
287      * @param session The current session.
288      * @return An environment suitable for acquiring an {@link LdapContext} for the client.
289      */
290     protected Hashtable<String, Object> getEnvironment( IoSession session )
291     {
292         Hashtable<String, Object> env = new Hashtable<String, Object>();
293         env.put( Context.PROVIDER_URL, session.getAttribute( "baseDn" ) );
294         env.put( Context.INITIAL_CONTEXT_FACTORY, "org.apache.directory.server.core.jndi.CoreContextFactory" );
295         env.put( Context.SECURITY_PRINCIPAL, ServerDNConstants.ADMIN_SYSTEM_DN );
296         env.put( Context.SECURITY_CREDENTIALS, "secret" );
297         env.put( Context.SECURITY_AUTHENTICATION, AuthenticationLevel.SIMPLE.toString() );
298 
299         return env;
300     }
301 }