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