001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2009 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2017 ForgeRock AS. 016 */ 017package org.opends.server.extensions; 018 019import static org.opends.messages.ExtensionMessages.*; 020import static org.opends.server.util.ServerConstants.*; 021import static org.opends.server.util.StaticUtils.*; 022 023import java.security.PrivilegedActionException; 024import java.security.PrivilegedExceptionAction; 025import java.util.HashMap; 026import java.util.List; 027 028import javax.security.auth.Subject; 029import javax.security.auth.callback.Callback; 030import javax.security.auth.callback.CallbackHandler; 031import javax.security.auth.callback.NameCallback; 032import javax.security.auth.callback.PasswordCallback; 033import javax.security.auth.callback.UnsupportedCallbackException; 034import javax.security.auth.login.LoginContext; 035import javax.security.sasl.AuthorizeCallback; 036import javax.security.sasl.RealmCallback; 037import javax.security.sasl.Sasl; 038import javax.security.sasl.SaslException; 039import javax.security.sasl.SaslServer; 040 041import org.forgerock.i18n.LocalizableMessage; 042import org.forgerock.i18n.LocalizedIllegalArgumentException; 043import org.forgerock.i18n.slf4j.LocalizedLogger; 044import org.forgerock.opendj.ldap.ByteString; 045import org.forgerock.opendj.ldap.DN; 046import org.forgerock.opendj.ldap.ResultCode; 047import org.ietf.jgss.GSSException; 048import org.opends.server.api.AuthenticationPolicyState; 049import org.opends.server.api.ClientConnection; 050import org.opends.server.api.IdentityMapper; 051import org.opends.server.core.AccessControlConfigManager; 052import org.opends.server.core.BindOperation; 053import org.opends.server.core.DirectoryServer; 054import org.opends.server.core.PasswordPolicyState; 055import org.opends.server.protocols.internal.InternalClientConnection; 056import org.opends.server.protocols.ldap.LDAPClientConnection; 057import org.opends.server.types.AuthenticationInfo; 058import org.opends.server.types.DirectoryException; 059import org.opends.server.types.Entry; 060import org.opends.server.types.Privilege; 061 062/** 063 * This class defines the SASL context needed to process GSSAPI and DIGEST-MD5 064 * bind requests from clients. 065 */ 066public class SASLContext implements CallbackHandler, 067 PrivilegedExceptionAction<Boolean> 068{ 069 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 070 071 072 073 /** 074 * Instantiate a GSSAPI/DIGEST-MD5 SASL context using the specified 075 * parameters. 076 * 077 * @param saslProps 078 * The properties to use in creating the SASL server. 079 * @param serverFQDN 080 * The fully qualified domain name to use in creating the SASL 081 * server. 082 * @param mechanism 083 * The SASL mechanism name. 084 * @param identityMapper 085 * The identity mapper to use in mapping identities. 086 * @return A fully instantiated SASL context to use in processing a SASL bind 087 * for the GSSAPI or DIGEST-MD5 mechanisms. 088 * @throws SaslException 089 * If the SASL server can not be instantiated. 090 */ 091 public static SASLContext createSASLContext( 092 final HashMap<String, String> saslProps, final String serverFQDN, 093 final String mechanism, final IdentityMapper<?> identityMapper) 094 throws SaslException 095 { 096 return new SASLContext(saslProps, serverFQDN, mechanism, identityMapper); 097 } 098 099 100 101 /** The SASL server to use in the authentication. */ 102 private SaslServer saslServer; 103 104 /** The identity mapper to use when mapping identities. */ 105 private final IdentityMapper<?> identityMapper; 106 107 /** The property set to use when creating the SASL server. */ 108 private final HashMap<String, String> saslProps; 109 110 /** The fully qualified domain name to use when creating the SASL server. */ 111 private final String serverFQDN; 112 113 /** The SASL mechanism name. */ 114 private final String mechanism; 115 116 /** The authorization entry used in the authentication. */ 117 private Entry authEntry; 118 119 /** The authorization entry used in the authentication. */ 120 private Entry authzEntry; 121 122 /** The user name used in the authentication taken from the name callback. */ 123 private String userName; 124 125 /** Error message used by callbacks. */ 126 private LocalizableMessage cbMsg; 127 128 /** Error code used by callbacks. */ 129 private ResultCode cbResultCode; 130 131 /** The current bind operation used by the callbacks. */ 132 private BindOperation bindOp; 133 134 /** Used to check if negotiated QOP is confidentiality or integrity. */ 135 private static final String confidentiality = "auth-conf"; 136 private static final String integrity = "auth-int"; 137 138 /** Maximum buffer sizes that we will allow after negotiation. */ 139 private static final int SASL_MAX_BUFFER_SIZE = 65 * 1024; 140 141 142 /** 143 * Create a SASL context using the specified parameters. A SASL server will be 144 * instantiated only for the DIGEST-MD5 mechanism. The GSSAPI mechanism must 145 * instantiate the SASL server as the login context in a separate step. 146 * 147 * @param saslProps 148 * The properties to use in creating the SASL server. 149 * @param serverFQDN 150 * The fully qualified domain name to use in creating the SASL 151 * server. 152 * @param mechanism 153 * The SASL mechanism name. 154 * @param identityMapper 155 * The identity mapper to use in mapping identities. 156 * @throws SaslException 157 * If the SASL server can not be instantiated. 158 */ 159 private SASLContext(final HashMap<String, String> saslProps, 160 final String serverFQDN, final String mechanism, 161 final IdentityMapper<?> identityMapper) throws SaslException 162 { 163 this.identityMapper = identityMapper; 164 this.mechanism = mechanism; 165 this.saslProps = saslProps; 166 this.serverFQDN = serverFQDN; 167 168 if (mechanism.equals(SASL_MECHANISM_DIGEST_MD5)) 169 { 170 initSASLServer(); 171 } 172 } 173 174 175 176 /** 177 * Process the specified callback array. 178 * 179 * @param callbacks 180 * An array of callbacks that need processing. 181 * @throws UnsupportedCallbackException 182 * If a callback is not supported. 183 */ 184 @Override 185 public void handle(final Callback[] callbacks) 186 throws UnsupportedCallbackException 187 { 188 for (final Callback callback : callbacks) 189 { 190 if (callback instanceof NameCallback) 191 { 192 nameCallback((NameCallback) callback); 193 } 194 else if (callback instanceof PasswordCallback) 195 { 196 passwordCallback((PasswordCallback) callback); 197 } 198 else if (callback instanceof RealmCallback) 199 { 200 realmCallback((RealmCallback) callback); 201 } 202 else if (callback instanceof AuthorizeCallback) 203 { 204 authorizeCallback((AuthorizeCallback) callback); 205 } 206 else 207 { 208 final LocalizableMessage message = INFO_SASL_UNSUPPORTED_CALLBACK.get(mechanism, callback); 209 throw new UnsupportedCallbackException(callback, message.toString()); 210 } 211 } 212 } 213 214 215 216 /** 217 * The method performs all GSSAPI processing. It is run as the context of the 218 * login context performed by the GSSAPI mechanism handler. See comments for 219 * processing overview. 220 * 221 * @return {@code true} if the authentication processing was successful. 222 */ 223 @Override 224 public Boolean run() 225 { 226 final ClientConnection clientConn = bindOp.getClientConnection(); 227 228 // If the SASL server is null then this is the first handshake and the 229 // server needs to be initialized before any processing can be performed. 230 // If the SASL server cannot be created then all processing is abandoned 231 // and INVALID_CREDENTIALS is returned to the client. 232 if (saslServer == null) 233 { 234 try 235 { 236 initSASLServer(); 237 } 238 catch (final SaslException ex) 239 { 240 logger.traceException(ex); 241 final GSSException gex = (GSSException) ex.getCause(); 242 243 final LocalizableMessage msg; 244 if (gex != null) 245 { 246 msg = ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI, 247 GSSAPISASLMechanismHandler.getGSSExceptionMessage(gex)); 248 } 249 else 250 { 251 msg = ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI, 252 getExceptionMessage(ex)); 253 } 254 255 clientConn.setSASLAuthStateInfo(null); 256 bindOp.setAuthFailureReason(msg); 257 bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS); 258 return false; 259 } 260 } 261 262 final ByteString clientCredentials = bindOp.getSASLCredentials(); 263 clientConn.setSASLAuthStateInfo(null); 264 try 265 { 266 final ByteString responseAuthStr = evaluateResponse(clientCredentials); 267 268 // If the bind has not been completed,then 269 // more handshake is needed and SASL_BIND_IN_PROGRESS is returned back 270 // to the client. 271 if (isBindComplete()) 272 { 273 bindOp.setResultCode(ResultCode.SUCCESS); 274 bindOp.setSASLAuthUserEntry(authEntry); 275 final AuthenticationInfo authInfo = new AuthenticationInfo(authEntry, 276 authzEntry, mechanism, clientCredentials, 277 DirectoryServer.isRootDN(authEntry.getName())); 278 bindOp.setAuthenticationInfo(authInfo); 279 280 // If confidentiality/integrity has been negotiated then 281 // create a SASL security provider and save it in the client 282 // connection. If confidentiality/integrity has not been 283 // negotiated, dispose of the SASL server. 284 if (isConfidentialIntegrity()) 285 { 286 final SASLByteChannel saslByteChannel = SASLByteChannel 287 .getSASLByteChannel(clientConn, mechanism, this); 288 final LDAPClientConnection ldapConn = 289 (LDAPClientConnection) clientConn; 290 ldapConn.setSASLPendingProvider(saslByteChannel); 291 } 292 else 293 { 294 dispose(); 295 clientConn.setSASLAuthStateInfo(null); 296 } 297 } 298 else 299 { 300 bindOp.setServerSASLCredentials(responseAuthStr); 301 clientConn.setSASLAuthStateInfo(this); 302 bindOp.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS); 303 } 304 } 305 catch (final SaslException e) 306 { 307 logger.traceException(e); 308 309 final LocalizableMessage msg = ERR_SASL_PROTOCOL_ERROR.get(mechanism, 310 getExceptionMessage(e)); 311 handleError(msg); 312 return false; 313 } 314 315 return true; 316 } 317 318 319 320 /** 321 * Dispose of the SASL server instance. 322 */ 323 void dispose() 324 { 325 try 326 { 327 saslServer.dispose(); 328 } 329 catch (final SaslException e) 330 { 331 logger.traceException(e); 332 } 333 } 334 335 336 337 /** 338 * Evaluate the final stage of a DIGEST-MD5 SASL bind using the specified bind 339 * operation. 340 * 341 * @param bindOp 342 * The bind operation to use in processing. 343 */ 344 void evaluateFinalStage(final BindOperation bindOp) 345 { 346 this.bindOp = bindOp; 347 final ByteString clientCredentials = bindOp.getSASLCredentials(); 348 349 if (clientCredentials == null || clientCredentials.length() == 0) 350 { 351 final LocalizableMessage msg = ERR_SASL_NO_CREDENTIALS.get(mechanism, mechanism); 352 handleError(msg); 353 return; 354 } 355 356 final ClientConnection clientConn = bindOp.getClientConnection(); 357 clientConn.setSASLAuthStateInfo(null); 358 359 try 360 { 361 final ByteString responseAuthStr = evaluateResponse(clientCredentials); 362 bindOp.setResultCode(ResultCode.SUCCESS); 363 bindOp.setServerSASLCredentials(responseAuthStr); 364 bindOp.setSASLAuthUserEntry(authEntry); 365 final AuthenticationInfo authInfo = new AuthenticationInfo(authEntry, 366 authzEntry, mechanism, clientCredentials, 367 DirectoryServer.isRootDN(authEntry.getName())); 368 bindOp.setAuthenticationInfo(authInfo); 369 370 // If confidentiality/integrity has been negotiated, then create a 371 // SASL security provider and save it in the client connection for 372 // use in later processing. 373 if (isConfidentialIntegrity()) 374 { 375 final SASLByteChannel saslByteChannel = SASLByteChannel 376 .getSASLByteChannel(clientConn, mechanism, this); 377 final LDAPClientConnection ldapConn = (LDAPClientConnection) clientConn; 378 ldapConn.setSASLPendingProvider(saslByteChannel); 379 } 380 else 381 { 382 dispose(); 383 clientConn.setSASLAuthStateInfo(null); 384 } 385 } 386 catch (final SaslException e) 387 { 388 logger.traceException(e); 389 390 final LocalizableMessage msg = ERR_SASL_PROTOCOL_ERROR.get(mechanism, 391 getExceptionMessage(e)); 392 handleError(msg); 393 } 394 } 395 396 397 398 /** 399 * Process the initial stage of a DIGEST-MD5 SASL bind using the specified 400 * bind operation. 401 * 402 * @param bindOp 403 * The bind operation to use in processing. 404 */ 405 void evaluateInitialStage(final BindOperation bindOp) 406 { 407 this.bindOp = bindOp; 408 final ClientConnection clientConn = bindOp.getClientConnection(); 409 410 try 411 { 412 final ByteString challenge = evaluateResponse(ByteString.empty()); 413 bindOp.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS); 414 bindOp.setServerSASLCredentials(challenge); 415 clientConn.setSASLAuthStateInfo(this); 416 } 417 catch (final SaslException e) 418 { 419 logger.traceException(e); 420 final LocalizableMessage msg = ERR_SASL_PROTOCOL_ERROR.get(mechanism, 421 getExceptionMessage(e)); 422 handleError(msg); 423 } 424 } 425 426 private int getPropertyAsInt(final String propertyName) 427 { 428 final String str = (String) saslServer.getNegotiatedProperty(propertyName); 429 try 430 { 431 return Math.min(SASL_MAX_BUFFER_SIZE, Integer.parseInt(str)); 432 } 433 catch (final NumberFormatException e) 434 { 435 return SASL_MAX_BUFFER_SIZE; 436 } 437 } 438 439 /** 440 * Returns the negotiated maximum size of protected data which can be received 441 * from the client. 442 * 443 * @return The negotiated maximum size of protected data which can be received 444 * from the client. 445 */ 446 int getMaxReceiveBufferSize() 447 { 448 return getPropertyAsInt(Sasl.MAX_BUFFER); 449 } 450 451 452 453 /** 454 * Returns the negotiated maximum size of raw data which can be sent to the 455 * client. 456 * 457 * @return The negotiated maximum size of raw data which can be sent to the 458 * client. 459 */ 460 int getMaxRawSendBufferSize() 461 { 462 return getPropertyAsInt(Sasl.RAW_SEND_SIZE); 463 } 464 465 466 467 /** 468 * Return the Security Strength Factor of the cipher if the QOP property is 469 * confidentiality, or, 1 if it is integrity. 470 * 471 * @return The SSF of the cipher used during confidentiality or integrity 472 * processing. 473 */ 474 int getSSF() 475 { 476 int ssf = 0; 477 final String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP); 478 if (integrity.equalsIgnoreCase(qop)) 479 { 480 ssf = 1; 481 } 482 else if (confidentiality.equalsIgnoreCase(qop)) 483 { 484 final String negStrength = (String) saslServer 485 .getNegotiatedProperty(Sasl.STRENGTH); 486 if ("low".equalsIgnoreCase(negStrength)) 487 { 488 ssf = 40; 489 } 490 else if ("medium".equalsIgnoreCase(negStrength)) 491 { 492 ssf = 56; 493 } 494 else if ("high".equalsIgnoreCase(negStrength)) 495 { 496 ssf = 128; 497 } 498 /* Treat anything else as if not security is provided and keep the 499 server running 500 */ 501 } 502 return ssf; 503 } 504 505 506 507 /** 508 * Return {@code true} if the bind has been completed. If the context is 509 * supporting confidentiality or integrity, the security provider will need to 510 * check if the context has completed the handshake with the client and is 511 * ready to process confidentiality or integrity messages. 512 * 513 * @return {@code true} if the handshaking is complete. 514 */ 515 boolean isBindComplete() 516 { 517 return saslServer.isComplete(); 518 } 519 520 521 522 /** 523 * Perform the authentication as the specified login context. The specified 524 * bind operation needs to be saved so the callbacks have access to it. Only 525 * used by the GSSAPI mechanism. 526 * 527 * @param loginContext 528 * The login context to perform the authentication as. 529 * @param bindOp 530 * The bind operation needed by the callbacks to process the 531 * authentication. 532 */ 533 void performAuthentication(final LoginContext loginContext, 534 final BindOperation bindOp) 535 { 536 this.bindOp = bindOp; 537 try 538 { 539 Subject.doAs(loginContext.getSubject(), this); 540 } 541 catch (final PrivilegedActionException e) 542 { 543 logger.traceException(e); 544 final LocalizableMessage msg = ERR_SASL_PROTOCOL_ERROR.get(mechanism, 545 getExceptionMessage(e)); 546 handleError(msg); 547 } 548 } 549 550 551 552 /** 553 * Unwrap the specified byte array using the provided offset and length 554 * values. Used only when the SASL server has negotiated confidentiality or 555 * integrity processing. 556 * 557 * @param bytes 558 * The byte array to unwrap. 559 * @param offset 560 * The offset in the array. 561 * @param len 562 * The length from the offset of the number of bytes to unwrap. 563 * @return A byte array containing the clear or unwrapped bytes. 564 * @throws SaslException 565 * If the bytes cannot be unwrapped. 566 */ 567 byte[] unwrap(final byte[] bytes, final int offset, final int len) 568 throws SaslException 569 { 570 return saslServer.unwrap(bytes, offset, len); 571 } 572 573 574 575 /** 576 * Wrap the specified clear byte array using the provided offset and length 577 * values. Used only when the SASL server has negotiated 578 * confidentiality/integrity processing. 579 * 580 * @param clearBytes 581 * The clear byte array to wrap. 582 * @param offset 583 * The offset into the clear byte array.. 584 * @param len 585 * The length from the offset of the number of bytes to wrap. 586 * @return A byte array containing the wrapped bytes. 587 * @throws SaslException 588 * If the clear bytes cannot be wrapped. 589 */ 590 byte[] wrap(final byte[] clearBytes, final int offset, final int len) 591 throws SaslException 592 { 593 return saslServer.wrap(clearBytes, offset, len); 594 } 595 596 597 598 /** 599 * This callback is used to process the authorize callback. It is used during 600 * both GSSAPI and DIGEST-MD5 processing. When processing the GSSAPI 601 * mechanism, this is the only callback invoked. When processing the 602 * DIGEST-MD5 mechanism, it is the last callback invoked after the name and 603 * password callbacks respectively. 604 * 605 * @param callback 606 * The authorize callback instance to process. 607 */ 608 private void authorizeCallback(final AuthorizeCallback callback) 609 { 610 final String responseAuthzID = callback.getAuthorizationID(); 611 612 // If the authEntry is null, then we are processing a GSSAPI SASL bind, 613 // and first need to try to map the authentication ID to an user entry. 614 // The authEntry is never null, when processing a DIGEST-MD5 SASL bind. 615 if (authEntry == null) 616 { 617 final String authid = callback.getAuthenticationID(); 618 try 619 { 620 authEntry = identityMapper.getEntryForID(authid); 621 if (authEntry == null) 622 { 623 setCallbackMsg(ERR_SASL_AUTHENTRY_NO_MAPPED_ENTRY.get(authid)); 624 callback.setAuthorized(false); 625 return; 626 } 627 } 628 catch (final DirectoryException de) 629 { 630 logger.traceException(de); 631 setCallbackMsg(ERR_SASL_CANNOT_MAP_AUTHENTRY.get(authid, 632 de.getMessage())); 633 callback.setAuthorized(false); 634 return; 635 } 636 userName = authid; 637 } 638 639 if (responseAuthzID.length() == 0) 640 { 641 setCallbackMsg(ERR_SASLDIGESTMD5_EMPTY_AUTHZID.get()); 642 callback.setAuthorized(false); 643 } 644 else if (!responseAuthzID.equals(userName)) 645 { 646 final String lowerAuthzID = toLowerCase(responseAuthzID); 647 648 // Process the callback differently depending on if the authzid 649 // string begins with the string "dn:" or not. 650 if (lowerAuthzID.startsWith("dn:")) 651 { 652 authzDNCheck(callback); 653 } 654 else 655 { 656 authzIDCheck(callback); 657 } 658 } 659 else 660 { 661 authzEntry = authEntry; 662 callback.setAuthorized(true); 663 } 664 } 665 666 667 668 /** 669 * Process the specified authorize callback. This method is called if the 670 * callback's authorization ID begins with the string "dn:". 671 * 672 * @param callback 673 * The authorize callback to process. 674 */ 675 private void authzDNCheck(final AuthorizeCallback callback) 676 { 677 final String responseAuthzID = callback.getAuthorizationID(); 678 DN authzDN; 679 callback.setAuthorized(true); 680 681 try 682 { 683 authzDN = DN.valueOf(responseAuthzID.substring(3)); 684 } 685 catch (final LocalizedIllegalArgumentException e) 686 { 687 logger.traceException(e); 688 setCallbackMsg(ERR_SASL_AUTHZID_INVALID_DN.get(responseAuthzID, 689 e.getMessageObject())); 690 callback.setAuthorized(false); 691 return; 692 } 693 694 final DN actualAuthzDN = DirectoryServer.getActualRootBindDN(authzDN); 695 if (actualAuthzDN != null) 696 { 697 authzDN = actualAuthzDN; 698 } 699 700 if (!authzDN.equals(authEntry.getName())) 701 { 702 if (authzDN.isRootDN()) 703 { 704 authzEntry = null; 705 } 706 else 707 { 708 try 709 { 710 authzEntry = DirectoryServer.getEntry(authzDN); 711 if (authzEntry == null) 712 { 713 setCallbackMsg(ERR_SASL_AUTHZID_NO_SUCH_ENTRY.get(authzDN)); 714 callback.setAuthorized(false); 715 return; 716 } 717 } 718 catch (final DirectoryException e) 719 { 720 logger.traceException(e); 721 setCallbackMsg(ERR_SASL_AUTHZID_CANNOT_GET_ENTRY.get(authzDN, e.getMessageObject())); 722 callback.setAuthorized(false); 723 return; 724 } 725 } 726 final AuthenticationInfo authInfo = new AuthenticationInfo(authEntry, 727 DirectoryServer.isRootDN(authEntry.getName())); 728 if (!hasPrivilege(authInfo)) 729 { 730 callback.setAuthorized(false); 731 } 732 else 733 { 734 callback.setAuthorized(hasPermission(authInfo)); 735 } 736 } 737 } 738 739 740 741 /** 742 * Process the specified authorize callback. This method is called if the 743 * callback's authorization ID does not begin with the string "dn:". 744 * 745 * @param callback 746 * The authorize callback to process. 747 */ 748 private void authzIDCheck(final AuthorizeCallback callback) 749 { 750 final String authzid = callback.getAuthorizationID(); 751 final String lowerAuthzID = toLowerCase(authzid); 752 String idStr; 753 callback.setAuthorized(true); 754 755 if (lowerAuthzID.startsWith("u:")) 756 { 757 idStr = authzid.substring(2); 758 } 759 else 760 { 761 idStr = authzid; 762 } 763 764 if (idStr.length() == 0) 765 { 766 authzEntry = null; 767 } 768 else 769 { 770 try 771 { 772 authzEntry = identityMapper.getEntryForID(idStr); 773 if (authzEntry == null) 774 { 775 setCallbackMsg(ERR_SASL_AUTHZID_NO_MAPPED_ENTRY.get(authzid)); 776 callback.setAuthorized(false); 777 return; 778 } 779 } 780 catch (final DirectoryException e) 781 { 782 logger.traceException(e); 783 setCallbackMsg(ERR_SASL_AUTHZID_NO_MAPPED_ENTRY.get(authzid)); 784 callback.setAuthorized(false); 785 return; 786 } 787 } 788 789 if (authzEntry == null || !authzEntry.getName().equals(authEntry.getName())) 790 { 791 // Create temporary authorization information and run it both 792 // through the privilege and then the access control subsystems. 793 final AuthenticationInfo authInfo = new AuthenticationInfo(authEntry, 794 DirectoryServer.isRootDN(authEntry.getName())); 795 if (!hasPrivilege(authInfo)) 796 { 797 callback.setAuthorized(false); 798 } 799 else 800 { 801 callback.setAuthorized(hasPermission(authInfo)); 802 } 803 } 804 } 805 806 807 808 /** 809 * Helper routine to call the SASL server evaluateResponse method with the 810 * specified ByteString. 811 * 812 * @param response A ByteString containing the response to pass to the 813 * SASL server. 814 * @return A ByteString containing the result of the evaluation. 815 * @throws SaslException 816 * If the SASL server cannot evaluate the byte array. 817 */ 818 private ByteString evaluateResponse(ByteString response) throws SaslException 819 { 820 if (response == null) 821 { 822 response = ByteString.empty(); 823 } 824 825 final byte[] evalResponse = saslServer.evaluateResponse(response 826 .toByteArray()); 827 if (evalResponse == null) 828 { 829 return ByteString.empty(); 830 } 831 else 832 { 833 return ByteString.wrap(evalResponse); 834 } 835 } 836 837 838 839 /** 840 * Try to get a entry from the directory using the specified DN. Used only for 841 * DIGEST-MD5 SASL mechanism. 842 * 843 * @param userDN 844 * The DN of the entry to retrieve from the server. 845 */ 846 private void getAuthEntry(final DN userDN) 847 { 848 try 849 { 850 authEntry = DirectoryServer.getEntry(userDN); 851 } 852 catch (final DirectoryException e) 853 { 854 logger.traceException(e); 855 setCallbackMsg(ERR_SASL_CANNOT_GET_ENTRY_BY_DN.get( 856 userDN, SASL_MECHANISM_DIGEST_MD5, e.getMessageObject())); 857 } 858 } 859 860 861 862 /** 863 * This method is used to process an exception that is thrown during bind 864 * processing. It will try to determine if the exception is a result of 865 * callback processing, and if it is, will try to use a more informative 866 * failure message set by the callback. If the exception is a result of a 867 * error during the the SASL server processing, the callback message will be 868 * null, and the method will use the specified message parameter as the 869 * failure reason. This is a more cryptic exception message hard-coded in the 870 * SASL server internals. The method also disposes of the SASL server, clears 871 * the authentication state and sets the result code to INVALID_CREDENTIALs 872 * 873 * @param msg 874 * The message to use if the callback message is not null. 875 */ 876 private void handleError(final LocalizableMessage msg) 877 { 878 dispose(); 879 final ClientConnection clientConn = bindOp.getClientConnection(); 880 clientConn.setSASLAuthStateInfo(null); 881 882 // Check if the callback message is null and use that message if not. 883 if (cbResultCode != null) 884 { 885 bindOp.setResultCode(cbResultCode); 886 } 887 else 888 { 889 bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS); 890 } 891 892 if (cbMsg != null) 893 { 894 bindOp.setAuthFailureReason(cbMsg); 895 } 896 else 897 { 898 bindOp.setAuthFailureReason(msg); 899 } 900 } 901 902 903 904 /** 905 * Checks the specified authentication information parameter against the 906 * access control subsystem to see if it has the "proxy" right. 907 * 908 * @param authInfo 909 * The authentication information to check access on. 910 * @return {@code true} if the authentication information has proxy access. 911 */ 912 private boolean hasPermission(final AuthenticationInfo authInfo) 913 { 914 boolean ret = true; 915 Entry e = authzEntry; 916 917 // If the authz entry is null, use the entry associated with the NULL DN. 918 if (e == null) 919 { 920 try 921 { 922 e = DirectoryServer.getEntry(DN.rootDN()); 923 } 924 catch (final DirectoryException ex) 925 { 926 return false; 927 } 928 } 929 930 if (!AccessControlConfigManager.getInstance().getAccessControlHandler() 931 .mayProxy(authInfo.getAuthenticationEntry(), e, bindOp)) 932 { 933 setCallbackMsg(ERR_SASL_AUTHZID_INSUFFICIENT_ACCESS.get(authEntry.getName())); 934 ret = false; 935 } 936 937 return ret; 938 } 939 940 941 942 /** 943 * Checks the specified authentication information parameter against the 944 * privilege subsystem to see if it has PROXIED_AUTH privileges. 945 * 946 * @param authInfo 947 * The authentication information to use in the check. 948 * @return {@code true} if the authentication information has PROXIED_AUTH 949 * privileges. 950 */ 951 private boolean hasPrivilege(final AuthenticationInfo authInfo) 952 { 953 boolean ret = true; 954 final InternalClientConnection tempConn = new InternalClientConnection( 955 authInfo); 956 if (!tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOp)) 957 { 958 setCallbackMsg(ERR_SASL_AUTHZID_INSUFFICIENT_PRIVILEGES.get(authEntry.getName())); 959 ret = false; 960 } 961 return ret; 962 } 963 964 965 966 /** 967 * Initialize the SASL server using parameters specified in the constructor. 968 */ 969 private void initSASLServer() throws SaslException 970 { 971 saslServer = Sasl.createSaslServer(mechanism, SASL_DEFAULT_PROTOCOL, 972 serverFQDN, saslProps, this); 973 if (saslServer == null) 974 { 975 final LocalizableMessage msg = ERR_SASL_CREATE_SASL_SERVER_FAILED.get(mechanism, 976 serverFQDN); 977 throw new SaslException(msg.toString()); 978 } 979 } 980 981 982 983 /** 984 * Return true if the SASL server has negotiated with the client to support 985 * confidentiality or integrity. 986 * 987 * @return {@code true} if the context supports confidentiality or integrity. 988 */ 989 private boolean isConfidentialIntegrity() 990 { 991 boolean ret = false; 992 final String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP); 993 if (qop.equalsIgnoreCase(confidentiality) 994 || qop.equalsIgnoreCase(integrity)) 995 { 996 ret = true; 997 } 998 return ret; 999 } 1000 1001 1002 1003 /** 1004 * Process the specified name callback. Used only for DIGEST-MD5 SASL 1005 * mechanism. 1006 * 1007 * @param nameCallback 1008 * The name callback to process. 1009 */ 1010 private void nameCallback(final NameCallback nameCallback) 1011 { 1012 userName = nameCallback.getDefaultName(); 1013 final String lowerUserName = toLowerCase(userName); 1014 1015 // Process the user name differently if it starts with the string "dn:". 1016 if (lowerUserName.startsWith("dn:")) 1017 { 1018 DN userDN; 1019 try 1020 { 1021 userDN = DN.valueOf(userName.substring(3)); 1022 } 1023 catch (final LocalizedIllegalArgumentException e) 1024 { 1025 logger.traceException(e); 1026 setCallbackMsg(ERR_SASL_CANNOT_DECODE_USERNAME_AS_DN.get(mechanism, 1027 userName, e.getMessageObject())); 1028 return; 1029 } 1030 1031 if (userDN.isRootDN()) 1032 { 1033 setCallbackMsg(ERR_SASL_USERNAME_IS_NULL_DN.get(mechanism)); 1034 return; 1035 } 1036 1037 final DN rootDN = DirectoryServer.getActualRootBindDN(userDN); 1038 if (rootDN != null) 1039 { 1040 userDN = rootDN; 1041 } 1042 getAuthEntry(userDN); 1043 } 1044 else 1045 { 1046 // The entry name is not a DN, try to map it using the identity 1047 // mapper. 1048 String entryID = userName; 1049 if (lowerUserName.startsWith("u:")) 1050 { 1051 if (lowerUserName.equals("u:")) 1052 { 1053 setCallbackMsg(ERR_SASL_ZERO_LENGTH_USERNAME 1054 .get(mechanism, mechanism)); 1055 return; 1056 } 1057 entryID = userName.substring(2); 1058 } 1059 try 1060 { 1061 authEntry = identityMapper.getEntryForID(entryID); 1062 } 1063 catch (final DirectoryException e) 1064 { 1065 logger.traceException(e); 1066 setCallbackMsg(ERR_SASLDIGESTMD5_CANNOT_MAP_USERNAME.get(userName, e.getMessageObject())); 1067 } 1068 } 1069 /* 1070 At this point, the authEntry should not be null. 1071 If it is, it's an error, but the password callback will catch it. 1072 There is no way to stop the processing from the name callback. 1073 */ 1074 } 1075 1076 1077 1078 /** 1079 * Process the specified password callback. Used only for the DIGEST-MD5 SASL 1080 * mechanism. The password callback is processed after the name callback. 1081 * 1082 * @param passwordCallback 1083 * The password callback to process. 1084 */ 1085 private void passwordCallback(final PasswordCallback passwordCallback) 1086 { 1087 // If there is no authEntry this is an error. 1088 if (authEntry == null) 1089 { 1090 setCallbackMsg(ERR_SASL_NO_MATCHING_ENTRIES.get(userName)); 1091 return; 1092 } 1093 1094 // Try to get a clear password to use. 1095 List<ByteString> clearPasswords; 1096 try 1097 { 1098 final AuthenticationPolicyState authState = AuthenticationPolicyState 1099 .forUser(authEntry, false); 1100 1101 if (!authState.isPasswordPolicy()) 1102 { 1103 final LocalizableMessage message = ERR_SASL_ACCOUNT_NOT_LOCAL.get(mechanism,authEntry.getName()); 1104 setCallbackMsg(ResultCode.INAPPROPRIATE_AUTHENTICATION, message); 1105 return; 1106 } 1107 1108 final PasswordPolicyState pwPolicyState = (PasswordPolicyState) authState; 1109 1110 clearPasswords = pwPolicyState.getClearPasswords(); 1111 if (clearPasswords == null || clearPasswords.isEmpty()) 1112 { 1113 setCallbackMsg(ERR_SASL_NO_REVERSIBLE_PASSWORDS.get(mechanism, authEntry.getName())); 1114 return; 1115 } 1116 } 1117 catch (final Exception e) 1118 { 1119 logger.traceException(e); 1120 setCallbackMsg(ERR_SASL_CANNOT_GET_REVERSIBLE_PASSWORDS.get(authEntry.getName(), mechanism, e)); 1121 return; 1122 } 1123 1124 // Use the first password. 1125 final char[] password = clearPasswords.get(0).toString().toCharArray(); 1126 passwordCallback.setPassword(password); 1127 } 1128 1129 1130 1131 /** 1132 * This callback is used to process realm information. It is not used. 1133 * 1134 * @param callback 1135 * The realm callback instance to process. 1136 */ 1137 private void realmCallback(final RealmCallback callback) 1138 { 1139 } 1140 1141 1142 1143 /** 1144 * Sets the callback message to the specified message. 1145 * 1146 * @param cbMsg 1147 * The message to set the callback message to. 1148 */ 1149 private void setCallbackMsg(final LocalizableMessage cbMsg) 1150 { 1151 setCallbackMsg(ResultCode.INVALID_CREDENTIALS, cbMsg); 1152 } 1153 1154 1155 1156 /** 1157 * Sets the callback message to the specified message. 1158 * 1159 * @param cbResultCode 1160 * The result code. 1161 * @param cbMsg 1162 * The message. 1163 */ 1164 private void setCallbackMsg(final ResultCode cbResultCode, 1165 final LocalizableMessage cbMsg) 1166 { 1167 this.cbResultCode = cbResultCode; 1168 this.cbMsg = cbMsg; 1169 } 1170}