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}