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