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; |
21 |
|
22 |
|
23 |
import java.util.Map; |
24 |
|
25 |
import javax.naming.Name; |
26 |
import javax.naming.NameNotFoundException; |
27 |
import javax.security.auth.Subject; |
28 |
import javax.security.auth.kerberos.KerberosKey; |
29 |
import javax.security.auth.kerberos.KerberosPrincipal; |
30 |
import javax.security.sasl.SaslException; |
31 |
import javax.security.sasl.SaslServer; |
32 |
|
33 |
import org.apache.directory.server.core.CoreSession; |
34 |
import org.apache.directory.server.core.DirectoryService; |
35 |
import org.apache.directory.server.core.authn.LdapPrincipal; |
36 |
import org.apache.directory.server.core.entry.ClonedServerEntry; |
37 |
import org.apache.directory.server.core.interceptor.context.BindOperationContext; |
38 |
import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType; |
39 |
import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey; |
40 |
import org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntry; |
41 |
import org.apache.directory.server.kerberos.shared.store.operations.GetPrincipal; |
42 |
import org.apache.directory.server.ldap.LdapProtocolUtils; |
43 |
import org.apache.directory.server.ldap.LdapService; |
44 |
import org.apache.directory.server.ldap.LdapSession; |
45 |
import org.apache.directory.server.ldap.handlers.bind.MechanismHandler; |
46 |
import org.apache.directory.server.ldap.handlers.bind.SaslConstants; |
47 |
import org.apache.directory.server.protocol.shared.ServiceConfigurationException; |
48 |
import org.apache.directory.shared.ldap.constants.SchemaConstants; |
49 |
import org.apache.directory.shared.ldap.exception.LdapAuthenticationException; |
50 |
import org.apache.directory.shared.ldap.exception.LdapException; |
51 |
import org.apache.directory.shared.ldap.message.BindRequest; |
52 |
import org.apache.directory.shared.ldap.message.BindResponse; |
53 |
import org.apache.directory.shared.ldap.message.LdapResult; |
54 |
import org.apache.directory.shared.ldap.message.ResultCodeEnum; |
55 |
import org.apache.directory.shared.ldap.name.LdapDN; |
56 |
import org.apache.directory.shared.ldap.util.ExceptionUtils; |
57 |
import org.apache.directory.shared.ldap.util.StringTools; |
58 |
|
59 |
import org.slf4j.Logger; |
60 |
import org.slf4j.LoggerFactory; |
61 |
|
62 |
|
63 |
/** |
64 |
* A single reply handler for {@link BindRequest}s. |
65 |
* |
66 |
* Implements server-side of RFC 2222, sections 4.2 and 4.3. |
67 |
* |
68 |
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
69 |
* @version $Rev: 664302 $, $Date: 2008-06-07 04:44:00 -0400 (Sat, 07 Jun 2008) $ |
70 |
*/ |
71 |
public class BindHandler extends LdapRequestHandler<BindRequest> |
72 |
{ |
73 |
private static final Logger LOG = LoggerFactory.getLogger( BindHandler.class ); |
74 |
|
75 |
/** A Hashed Adapter mapping SASL mechanisms to their handlers. */ |
76 |
private Map<String, MechanismHandler> handlers; |
77 |
|
78 |
/** |
79 |
* Set the mechanisms handler map. |
80 |
* |
81 |
* @param handlers The associations btween a machanism and its handler |
82 |
*/ |
83 |
public void setSaslMechanismHandlers( Map<String, MechanismHandler> handlers ) |
84 |
{ |
85 |
this.handlers = handlers; |
86 |
} |
87 |
|
88 |
|
89 |
/** |
90 |
* Handle the Simple authentication. |
91 |
* |
92 |
* @param session The associated Session |
93 |
* @param message The BindRequest received |
94 |
* @throws Exception If the authentication cannot be done |
95 |
*/ |
96 |
public void handleSimpleAuth( LdapSession ldapSession, BindRequest bindRequest ) throws Exception |
97 |
{ |
98 |
// if the user is already bound, we have to unbind him |
99 |
if ( !ldapSession.isAnonymous() ) |
100 |
{ |
101 |
// We already have a bound session for this user. We have to |
102 |
// abandon it first. |
103 |
ldapSession.getCoreSession().unbind(); |
104 |
|
105 |
// Reset the status to Anonymous |
106 |
ldapSession.setAnonymous(); |
107 |
} |
108 |
|
109 |
// Now, bind the user |
110 |
|
111 |
// create a new Bind context, with a null session, as we don't have |
112 |
// any context yet. |
113 |
BindOperationContext opContext = new BindOperationContext( null ); |
114 |
|
115 |
// Stores the DN of the user to check, and its password |
116 |
opContext.setDn( bindRequest.getName() ); |
117 |
opContext.setCredentials( bindRequest.getCredentials() ); |
118 |
|
119 |
// Stores the request controls into the operation context |
120 |
LdapProtocolUtils.setRequestControls( opContext, bindRequest ); |
121 |
|
122 |
try |
123 |
{ |
124 |
/* |
125 |
* Referral handling as specified by RFC 3296 here: |
126 |
* |
127 |
* http://www.faqs.org/rfcs/rfc3296.html |
128 |
* |
129 |
* See section 5.6.1 where if the bind principal DN is a referral |
130 |
* we return an invalidCredentials result response. Optionally we |
131 |
* could support delegated authentication in the future with this |
132 |
* potential. See the following JIRA for more on this possibility: |
133 |
* |
134 |
* https://issues.apache.org/jira/browse/DIRSERVER-1217 |
135 |
* |
136 |
* NOTE: if this is done then this handler should extend the |
137 |
* a modified form of the ReferralAwareRequestHandler so it can |
138 |
* detect conditions where ancestors of the DN are referrals |
139 |
* and delegate appropriately. |
140 |
*/ |
141 |
ClonedServerEntry principalEntry = null; |
142 |
|
143 |
try |
144 |
{ |
145 |
principalEntry = getLdapServer().getDirectoryService() |
146 |
.getAdminSession().lookup( bindRequest.getName() ); |
147 |
} |
148 |
catch ( NameNotFoundException e ) |
149 |
{ |
150 |
// this is OK |
151 |
} |
152 |
|
153 |
if ( principalEntry == null || |
154 |
principalEntry.getOriginalEntry().contains( SchemaConstants.OBJECT_CLASS_AT, |
155 |
SchemaConstants.REFERRAL_OC ) ) |
156 |
{ |
157 |
LdapResult result = bindRequest.getResultResponse().getLdapResult(); |
158 |
result.setErrorMessage( "Bind principalDn points to referral." ); |
159 |
result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS ); |
160 |
ldapSession.getIoSession().write( bindRequest.getResultResponse() ); |
161 |
return; |
162 |
} |
163 |
|
164 |
// TODO - might cause issues since lookups are not returning all |
165 |
// attributes right now - this is an optimization that can be |
166 |
// enabled later after determining whether or not this will cause |
167 |
// issues. |
168 |
// reuse the looked up entry so we don't incur another lookup |
169 |
// opContext.setEntry( principalEntry ); |
170 |
|
171 |
// And call the OperationManager bind operation. |
172 |
getLdapServer().getDirectoryService().getOperationManager().bind( opContext ); |
173 |
|
174 |
// As a result, store the created session in the Core Session |
175 |
ldapSession.setCoreSession( opContext.getSession() ); |
176 |
|
177 |
if ( ! ldapSession.getCoreSession().isAnonymous() ) |
178 |
{ |
179 |
ldapSession.setAuthenticated(); |
180 |
} |
181 |
|
182 |
// Return the successful response |
183 |
sendBindSuccess( ldapSession, bindRequest, null ); |
184 |
} |
185 |
catch ( Exception e ) |
186 |
{ |
187 |
// Something went wrong. Write back an error message |
188 |
ResultCodeEnum code = null; |
189 |
LdapResult result = bindRequest.getResultResponse().getLdapResult(); |
190 |
|
191 |
if ( e instanceof LdapException ) |
192 |
{ |
193 |
code = ( ( LdapException ) e ).getResultCode(); |
194 |
result.setResultCode( code ); |
195 |
} |
196 |
else |
197 |
{ |
198 |
code = ResultCodeEnum.getBestEstimate( e, bindRequest.getType() ); |
199 |
result.setResultCode( code ); |
200 |
} |
201 |
|
202 |
String msg = code.toString() + ": Bind failed: " + e.getMessage(); |
203 |
|
204 |
if ( LOG.isDebugEnabled() ) |
205 |
{ |
206 |
msg += ":\n" + ExceptionUtils.getStackTrace( e ); |
207 |
msg += "\n\nBindRequest = \n" + bindRequest.toString(); |
208 |
} |
209 |
|
210 |
Name name = null; |
211 |
|
212 |
if ( e instanceof LdapAuthenticationException ) |
213 |
{ |
214 |
name = ( ( LdapAuthenticationException ) e ).getResolvedName(); |
215 |
} |
216 |
|
217 |
if ( ( name != null ) |
218 |
&& ( ( code == ResultCodeEnum.NO_SUCH_OBJECT ) || ( code == ResultCodeEnum.ALIAS_PROBLEM ) |
219 |
|| ( code == ResultCodeEnum.INVALID_DN_SYNTAX ) || ( code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM ) ) ) |
220 |
{ |
221 |
result.setMatchedDn( new LdapDN( name ) ); |
222 |
} |
223 |
|
224 |
result.setErrorMessage( msg ); |
225 |
ldapSession.getIoSession().write( bindRequest.getResultResponse() ); |
226 |
} |
227 |
} |
228 |
|
229 |
|
230 |
/** |
231 |
* Check if the mechanism exists. |
232 |
*/ |
233 |
private boolean checkMechanism( LdapSession ldapSession, String saslMechanism ) throws Exception |
234 |
{ |
235 |
// Guard clause: Reject unsupported SASL mechanisms. |
236 |
if ( ! ldapService.getSupportedMechanisms().contains( saslMechanism ) ) |
237 |
{ |
238 |
LOG.error( "Bind error : {} mechanism not supported. Please check the server.xml " + |
239 |
"configuration file (supportedMechanisms field)", |
240 |
saslMechanism ); |
241 |
|
242 |
return false; |
243 |
} |
244 |
else |
245 |
{ |
246 |
return true; |
247 |
} |
248 |
} |
249 |
|
250 |
|
251 |
/** |
252 |
* For challenge/response exchange, generate the challenge |
253 |
* |
254 |
* @param ldapSession |
255 |
* @param ss |
256 |
* @param bindRequest |
257 |
*/ |
258 |
private void generateSaslChallenge( LdapSession ldapSession, SaslServer ss, BindRequest bindRequest ) |
259 |
{ |
260 |
LdapResult result = bindRequest.getResultResponse().getLdapResult(); |
261 |
|
262 |
// SaslServer will throw an exception if the credentials are null. |
263 |
if ( bindRequest.getCredentials() == null ) |
264 |
{ |
265 |
bindRequest.setCredentials( StringTools.EMPTY_BYTES ); |
266 |
} |
267 |
|
268 |
try |
269 |
{ |
270 |
// Compute the challenge |
271 |
byte[] tokenBytes = ss.evaluateResponse( bindRequest.getCredentials() ); |
272 |
|
273 |
if ( ss.isComplete() ) |
274 |
{ |
275 |
// This is the end of the C/R exchange |
276 |
if ( tokenBytes != null ) |
277 |
{ |
278 |
/* |
279 |
* There may be a token to return to the client. We set it here |
280 |
* so it will be returned in a SUCCESS message, after an LdapContext |
281 |
* has been initialized for the client. |
282 |
*/ |
283 |
ldapSession.putSaslProperty( SaslConstants.SASL_CREDS, tokenBytes ); |
284 |
} |
285 |
|
286 |
// Return the successful response |
287 |
sendBindSuccess( ldapSession, bindRequest, tokenBytes ); |
288 |
} |
289 |
else |
290 |
{ |
291 |
// The SASL bind must continue, we are sending the computed challenge |
292 |
LOG.info( "Continuation token had length " + tokenBytes.length ); |
293 |
|
294 |
// Build the response |
295 |
result.setResultCode( ResultCodeEnum.SASL_BIND_IN_PROGRESS ); |
296 |
BindResponse resp = ( BindResponse ) bindRequest.getResultResponse(); |
297 |
|
298 |
// Store the challenge |
299 |
resp.setServerSaslCreds( tokenBytes ); |
300 |
|
301 |
// Switch to AuthPending |
302 |
ldapSession.setAuthPending(); |
303 |
|
304 |
// And write back the response |
305 |
ldapSession.getIoSession().write( resp ); |
306 |
LOG.debug( "Returning final authentication data to client to complete context." ); |
307 |
} |
308 |
} |
309 |
catch ( SaslException se ) |
310 |
{ |
311 |
LOG.error( se.getMessage() ); |
312 |
result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS ); |
313 |
result.setErrorMessage( ResultCodeEnum.INVALID_CREDENTIALS.toString() + ": " |
314 |
+ se.getMessage() ); |
315 |
|
316 |
// Reinitialize the state to Anonymous and clear the sasl properties |
317 |
ldapSession.clearSaslProperties(); |
318 |
ldapSession.setAnonymous(); |
319 |
|
320 |
// Write back the error response |
321 |
ldapSession.getIoSession().write( bindRequest.getResultResponse() ); |
322 |
} |
323 |
} |
324 |
|
325 |
|
326 |
/** |
327 |
* Send back an AUTH-METH-NOT-SUPPORTED error message to the client |
328 |
*/ |
329 |
private void sendAuthMethNotSupported( LdapSession ldapSession, BindRequest bindRequest ) |
330 |
{ |
331 |
// First, r-einit the state to Anonymous, and clear the |
332 |
// saslProperty map |
333 |
ldapSession.clearSaslProperties(); |
334 |
ldapSession.setAnonymous(); |
335 |
|
336 |
// And send the response to the client |
337 |
LdapResult bindResult = bindRequest.getResultResponse().getLdapResult(); |
338 |
bindResult.setResultCode( ResultCodeEnum.AUTH_METHOD_NOT_SUPPORTED ); |
339 |
bindResult.setErrorMessage( ResultCodeEnum.AUTH_METHOD_NOT_SUPPORTED.toString() + ": " |
340 |
+ bindRequest.getSaslMechanism() + " is not a supported mechanism." ); |
341 |
|
342 |
// Write back the error |
343 |
ldapSession.getIoSession().write( bindRequest.getResultResponse() ); |
344 |
|
345 |
return; |
346 |
} |
347 |
|
348 |
|
349 |
/** |
350 |
* Send back an INVALID-CREDENTIAL error message to the user. If we have an exception |
351 |
* as a third argument, then send back the associated message to the client. |
352 |
*/ |
353 |
private void sendInvalidCredentials( LdapSession ldapSession, BindRequest bindRequest, Exception e ) |
354 |
{ |
355 |
LdapResult result = bindRequest.getResultResponse().getLdapResult(); |
356 |
|
357 |
String message = ""; |
358 |
|
359 |
if ( e != null ) |
360 |
{ |
361 |
message = ResultCodeEnum.INVALID_CREDENTIALS + ": " + e.getMessage(); |
362 |
} |
363 |
else |
364 |
{ |
365 |
message = ResultCodeEnum.INVALID_CREDENTIALS.toString(); |
366 |
} |
367 |
|
368 |
LOG.error( message ); |
369 |
result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS ); |
370 |
result.setErrorMessage( message ); |
371 |
|
372 |
// Reinitialize the state to Anonymous and clear the sasl properties |
373 |
ldapSession.clearSaslProperties(); |
374 |
ldapSession.setAnonymous(); |
375 |
|
376 |
// Write back the error response |
377 |
ldapSession.getIoSession().write( bindRequest.getResultResponse() ); |
378 |
} |
379 |
|
380 |
|
381 |
/** |
382 |
* Send a SUCCESS message back to the client. |
383 |
*/ |
384 |
private void sendBindSuccess( LdapSession ldapSession, BindRequest bindRequest, byte[] tokenBytes ) |
385 |
{ |
386 |
// Return the successful response |
387 |
BindResponse response = ( BindResponse ) bindRequest.getResultResponse(); |
388 |
response.getLdapResult().setResultCode( ResultCodeEnum.SUCCESS ); |
389 |
response.setServerSaslCreds( tokenBytes ); |
390 |
|
391 |
if ( ! ldapSession.getCoreSession().isAnonymous() ) |
392 |
{ |
393 |
// If we have not been asked to authenticate as Anonymous, authenticate the user |
394 |
ldapSession.setAuthenticated(); |
395 |
} |
396 |
else |
397 |
{ |
398 |
// Otherwise, switch back to Anonymous |
399 |
ldapSession.setAnonymous(); |
400 |
} |
401 |
|
402 |
// Clean the SaslProperties, we don't need them anymore |
403 |
MechanismHandler handler = (MechanismHandler)ldapSession.getSaslProperty( SaslConstants.SASL_MECH_HANDLER ); |
404 |
|
405 |
if ( handler != null ) |
406 |
{ |
407 |
handler.cleanup( ldapSession ); |
408 |
} |
409 |
|
410 |
ldapSession.getIoSession().write( response ); |
411 |
|
412 |
LOG.debug( "Returned SUCCESS message: {}.", response ); |
413 |
} |
414 |
|
415 |
|
416 |
private void handleSaslAuthPending( LdapSession ldapSession, BindRequest bindRequest, DirectoryService ds ) throws Exception |
417 |
{ |
418 |
// First, check that we have the same mechanism |
419 |
String saslMechanism = bindRequest.getSaslMechanism(); |
420 |
|
421 |
// The empty mechanism is also a request for a new Bind session |
422 |
if ( StringTools.isEmpty( saslMechanism ) || |
423 |
!ldapSession.getSaslProperty( SaslConstants.SASL_MECH ).equals( saslMechanism ) ) |
424 |
{ |
425 |
sendAuthMethNotSupported( ldapSession, bindRequest ); |
426 |
return; |
427 |
} |
428 |
|
429 |
// We have already received a first BindRequest, and sent back some challenge. |
430 |
// First, check if the mechanism is the same |
431 |
MechanismHandler mechanismHandler = handlers.get( saslMechanism ); |
432 |
|
433 |
if ( mechanismHandler == null ) |
434 |
{ |
435 |
String message = "Handler unavailable for " + saslMechanism; |
436 |
|
437 |
// Clear the saslProperties, and move to the anonymous state |
438 |
ldapSession.clearSaslProperties(); |
439 |
ldapSession.setAnonymous(); |
440 |
|
441 |
LOG.error( message ); |
442 |
throw new IllegalArgumentException( message ); |
443 |
} |
444 |
|
445 |
// Get the previously created SaslServer instance |
446 |
SaslServer ss = mechanismHandler.handleMechanism( ldapSession, bindRequest ); |
447 |
|
448 |
/* |
449 |
* SaslServer will throw an exception if the credentials are null. |
450 |
*/ |
451 |
if ( bindRequest.getCredentials() == null ) |
452 |
{ |
453 |
bindRequest.setCredentials( StringTools.EMPTY_BYTES ); |
454 |
} |
455 |
|
456 |
byte[] tokenBytes = ss.evaluateResponse( bindRequest.getCredentials() ); |
457 |
|
458 |
if ( ss.isComplete() ) |
459 |
{ |
460 |
if ( tokenBytes != null ) |
461 |
{ |
462 |
/* |
463 |
* There may be a token to return to the client. We set it here |
464 |
* so it will be returned in a SUCCESS message, after an LdapContext |
465 |
* has been initialized for the client. |
466 |
*/ |
467 |
ldapSession.putSaslProperty( SaslConstants.SASL_CREDS, tokenBytes ); |
468 |
} |
469 |
|
470 |
// Create the user's coreSession |
471 |
try |
472 |
{ |
473 |
LdapPrincipal ldapPrincipal = (LdapPrincipal)ldapSession.getSaslProperty( SaslConstants.SASL_AUTHENT_USER ); |
474 |
|
475 |
CoreSession userSession = ds.getSession( ldapPrincipal.getJndiName(), ldapPrincipal.getUserPassword(), saslMechanism, null ); |
476 |
|
477 |
// Set the user session into the ldap session |
478 |
ldapSession.setCoreSession( userSession ); |
479 |
|
480 |
// Mark the user as authenticated |
481 |
ldapSession.setAuthenticated(); |
482 |
|
483 |
// Call the cleanup method for the selected mechanism |
484 |
MechanismHandler handler = (MechanismHandler)ldapSession.getSaslProperty( SaslConstants.SASL_MECH_HANDLER ); |
485 |
handler.cleanup( ldapSession ); |
486 |
|
487 |
// And send a Success response |
488 |
sendBindSuccess( ldapSession, bindRequest, tokenBytes ); |
489 |
} |
490 |
catch ( Exception e ) |
491 |
{ |
492 |
// TODO - why is this exception being ignored? Isn't this |
493 |
// really bad? |
494 |
LOG.error( "Exception encountered while processing Sasl BindRequest", e ); |
495 |
} |
496 |
} |
497 |
} |
498 |
|
499 |
|
500 |
/** |
501 |
* Handle the SASL authentication. If the mechanism is known, we are |
502 |
* facing three cases : |
503 |
* <ul> |
504 |
* <li>The user does not has a session yet</li> |
505 |
* <li>The user already has a session</li> |
506 |
* <li>The user has started a SASL negotiation</li> |
507 |
* </lu><br/> |
508 |
* |
509 |
* In the first case, we initiate a SaslBind session, which will be used all |
510 |
* along the negotiation.<br/> |
511 |
* In the second case, we first have to unbind the user, and initiate a new |
512 |
* SaslBind session.<br/> |
513 |
* In the third case, we have sub cases : |
514 |
* <ul> |
515 |
* <li>The mechanism is not provided : that means the user want to reset the |
516 |
* current negotiation. We move back to an Anonymous state</li> |
517 |
* <li>The mechanism is provided : the user is initializing a new negotiation |
518 |
* with another mechanism. The current SaslBind session is reinitialized</li> |
519 |
* <li></li> |
520 |
* </ul><br/> |
521 |
* |
522 |
* @param session The associated Session |
523 |
* @param message The BindRequest received |
524 |
* @throws Exception If the authentication cannot be done |
525 |
*/ |
526 |
public void handleSaslAuth( LdapSession ldapSession, BindRequest bindRequest ) throws Exception |
527 |
{ |
528 |
String saslMechanism = bindRequest.getSaslMechanism(); |
529 |
DirectoryService ds = getLdapServer().getDirectoryService(); |
530 |
|
531 |
// Case #2 : the user does have a session. We have to unbind him |
532 |
if ( ldapSession.isAuthenticated() ) |
533 |
{ |
534 |
// We already have a bound session for this user. We have to |
535 |
// close the previous session first. |
536 |
ldapSession.getCoreSession().unbind(); |
537 |
|
538 |
// Reset the status to Anonymous |
539 |
ldapSession.setAnonymous(); |
540 |
|
541 |
// Clean the sasl properties |
542 |
ldapSession.clearSaslProperties(); |
543 |
|
544 |
// Now we can continue as if the client was Anonymous from the beginning |
545 |
} |
546 |
|
547 |
// case #1 : The user does not have a session. |
548 |
if ( ldapSession.isAnonymous() ) |
549 |
{ |
550 |
if ( !StringTools.isEmpty( saslMechanism ) ) |
551 |
{ |
552 |
// fist check that the mechanism exists |
553 |
if ( !checkMechanism( ldapSession, saslMechanism ) ) |
554 |
{ |
555 |
// get out ! |
556 |
sendAuthMethNotSupported( ldapSession, bindRequest ); |
557 |
|
558 |
return; |
559 |
} |
560 |
|
561 |
// Store the mechanism in the ldap session |
562 |
ldapSession.putSaslProperty( SaslConstants.SASL_MECH, saslMechanism ); |
563 |
|
564 |
|
565 |
// Get the handler for this mechanism |
566 |
MechanismHandler mechanismHandler = handlers.get( saslMechanism ); |
567 |
|
568 |
// Store the mechanism handler in the salsProperties |
569 |
ldapSession.putSaslProperty( SaslConstants.SASL_MECH_HANDLER, mechanismHandler ); |
570 |
|
571 |
// Initialize the mechanism specific data |
572 |
mechanismHandler.init( ldapSession ); |
573 |
|
574 |
// Get the SaslServer instance which manage the C/R exchange |
575 |
SaslServer ss = mechanismHandler.handleMechanism( ldapSession, bindRequest ); |
576 |
|
577 |
// We have to generate a challenge |
578 |
generateSaslChallenge( ldapSession, ss, bindRequest ); |
579 |
|
580 |
// And get back |
581 |
return; |
582 |
} |
583 |
} |
584 |
else if ( ldapSession.isAuthPending() ) |
585 |
{ |
586 |
try |
587 |
{ |
588 |
handleSaslAuthPending( ldapSession, bindRequest, ds ); |
589 |
} |
590 |
catch ( SaslException se ) |
591 |
{ |
592 |
sendInvalidCredentials( ldapSession, bindRequest, se ); |
593 |
} |
594 |
|
595 |
return; |
596 |
} |
597 |
} |
598 |
|
599 |
|
600 |
/** |
601 |
* Create a list of all the configured realms. |
602 |
* |
603 |
* @param ldapService the LdapService for which we want to get the realms |
604 |
* @return a list of realms, separated by spaces |
605 |
*/ |
606 |
private String getActiveRealms( LdapService ldapService ) |
607 |
{ |
608 |
StringBuilder realms = new StringBuilder(); |
609 |
boolean isFirst = true; |
610 |
|
611 |
for ( String realm:ldapService.getSaslRealms() ) |
612 |
{ |
613 |
if ( isFirst ) |
614 |
{ |
615 |
isFirst = false; |
616 |
} |
617 |
else |
618 |
{ |
619 |
realms.append( ' ' ); |
620 |
} |
621 |
|
622 |
realms.append( realm ); |
623 |
} |
624 |
|
625 |
return realms.toString(); |
626 |
} |
627 |
|
628 |
|
629 |
private Subject getSubject( LdapService ldapService ) throws Exception |
630 |
{ |
631 |
String servicePrincipalName = ldapService.getSaslPrincipal(); |
632 |
|
633 |
KerberosPrincipal servicePrincipal = new KerberosPrincipal( servicePrincipalName ); |
634 |
GetPrincipal getPrincipal = new GetPrincipal( servicePrincipal ); |
635 |
|
636 |
PrincipalStoreEntry entry = null; |
637 |
|
638 |
try |
639 |
{ |
640 |
entry = findPrincipal( ldapService, getPrincipal ); |
641 |
} |
642 |
catch ( ServiceConfigurationException sce ) |
643 |
{ |
644 |
String message = "Service principal " + servicePrincipalName + " not found at search base DN " |
645 |
+ ldapService.getSearchBaseDn() + "."; |
646 |
throw new ServiceConfigurationException( message, sce ); |
647 |
} |
648 |
|
649 |
if ( entry == null ) |
650 |
{ |
651 |
String message = "Service principal " + servicePrincipalName + " not found at search base DN " |
652 |
+ ldapService.getSearchBaseDn() + "."; |
653 |
throw new ServiceConfigurationException( message ); |
654 |
} |
655 |
|
656 |
Subject subject = new Subject(); |
657 |
|
658 |
for ( EncryptionType encryptionType:entry.getKeyMap().keySet() ) |
659 |
{ |
660 |
EncryptionKey key = entry.getKeyMap().get( encryptionType ); |
661 |
|
662 |
byte[] keyBytes = key.getKeyValue(); |
663 |
int type = key.getKeyType().getOrdinal(); |
664 |
int kvno = key.getKeyVersion(); |
665 |
|
666 |
KerberosKey serviceKey = new KerberosKey( servicePrincipal, keyBytes, type, kvno ); |
667 |
|
668 |
subject.getPrivateCredentials().add( serviceKey ); |
669 |
} |
670 |
|
671 |
return subject; |
672 |
} |
673 |
|
674 |
|
675 |
private PrincipalStoreEntry findPrincipal( LdapService ldapService, GetPrincipal getPrincipal ) throws Exception |
676 |
{ |
677 |
CoreSession adminSession = ldapService.getDirectoryService().getAdminSession(); |
678 |
|
679 |
return ( PrincipalStoreEntry ) getPrincipal.execute( adminSession, null ); |
680 |
} |
681 |
|
682 |
|
683 |
/** |
684 |
* Deal with a received BindRequest |
685 |
* |
686 |
* @param session The current session |
687 |
* @param bindRequest The received BindRequest |
688 |
* @throws Exception If the authentication cannot be handled |
689 |
*/ |
690 |
@Override |
691 |
public void handle( LdapSession ldapSession, BindRequest bindRequest ) throws Exception |
692 |
{ |
693 |
LOG.debug( "Received: {}", bindRequest ); |
694 |
|
695 |
// Guard clause: LDAP version 3 |
696 |
if ( ! bindRequest.getVersion3() ) |
697 |
{ |
698 |
LOG.error( "Bind error : Only LDAP v3 is supported." ); |
699 |
LdapResult bindResult = bindRequest.getResultResponse().getLdapResult(); |
700 |
bindResult.setResultCode( ResultCodeEnum.PROTOCOL_ERROR ); |
701 |
bindResult.setErrorMessage( "Only LDAP v3 is supported." ); |
702 |
ldapSession.getIoSession().write( bindRequest.getResultResponse() ); |
703 |
return; |
704 |
} |
705 |
|
706 |
// Deal with the two kinds of authentication : Simple and SASL |
707 |
if ( bindRequest.isSimple() ) |
708 |
{ |
709 |
handleSimpleAuth( ldapSession, bindRequest ); |
710 |
} |
711 |
else |
712 |
{ |
713 |
handleSaslAuth( ldapSession, bindRequest ); |
714 |
} |
715 |
} |
716 |
} |