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