001    /*
002     * Copyright 2011-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2011-2015 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.listener;
022    
023    
024    
025    import java.util.Arrays;
026    import java.util.List;
027    import java.util.Map;
028    
029    import com.unboundid.asn1.ASN1OctetString;
030    import com.unboundid.ldap.protocol.LDAPMessage;
031    import com.unboundid.ldap.sdk.BindResult;
032    import com.unboundid.ldap.sdk.Control;
033    import com.unboundid.ldap.sdk.DN;
034    import com.unboundid.ldap.sdk.Entry;
035    import com.unboundid.ldap.sdk.LDAPException;
036    import com.unboundid.ldap.sdk.ResultCode;
037    import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
038    import com.unboundid.ldap.sdk.controls.AuthorizationIdentityResponseControl;
039    import com.unboundid.util.Debug;
040    import com.unboundid.util.NotMutable;
041    import com.unboundid.util.StaticUtils;
042    import com.unboundid.util.ThreadSafety;
043    import com.unboundid.util.ThreadSafetyLevel;
044    
045    import static com.unboundid.ldap.listener.ListenerMessages.*;
046    
047    
048    
049    /**
050     * This class defines a SASL bind handler which may be used to provide support
051     * for the SASL PLAIN mechanism (as defined in RFC 4616) in the in-memory
052     * directory server.
053     */
054    @NotMutable()
055    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
056    public final class PLAINBindHandler
057           extends InMemorySASLBindHandler
058    {
059      /**
060       * Creates a new instance of this SASL bind handler.
061       */
062      public PLAINBindHandler()
063      {
064        // No initialization is required.
065      }
066    
067    
068    
069      /**
070       * {@inheritDoc}
071       */
072      @Override()
073      public String getSASLMechanismName()
074      {
075        return "PLAIN";
076      }
077    
078    
079    
080      /**
081       * {@inheritDoc}
082       */
083      @Override()
084      public BindResult processSASLBind(final InMemoryRequestHandler handler,
085                                        final int messageID, final DN bindDN,
086                                        final ASN1OctetString credentials,
087                                        final List<Control> controls)
088      {
089        // Process the provided request controls.
090        final Map<String,Control> controlMap;
091        try
092        {
093          controlMap = RequestControlPreProcessor.processControls(
094               LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST, controls);
095        }
096        catch (final LDAPException le)
097        {
098          Debug.debugException(le);
099          return  new BindResult(messageID, le.getResultCode(),
100               le.getMessage(), le.getMatchedDN(), le.getReferralURLs(),
101               le.getResponseControls());
102        }
103    
104    
105        // Parse the credentials, which should be in the form:
106        //      [authzid] UTF8NUL authcid UTF8NUL passwd
107        if (credentials == null)
108        {
109          return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS,
110               ERR_PLAIN_BIND_NO_CREDENTIALS.get(), null, null, null);
111        }
112    
113        int firstNullPos  = -1;
114        int secondNullPos = -1;
115        final byte[] credBytes = credentials.getValue();
116        for (int i=0; i < credBytes.length; i++)
117        {
118          if (credBytes[i] == 0x00)
119          {
120            if (firstNullPos < 0)
121            {
122              firstNullPos = i;
123            }
124            else
125            {
126              secondNullPos = i;
127              break;
128            }
129          }
130        }
131    
132        if (secondNullPos < 0)
133        {
134          return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS,
135               ERR_PLAIN_BIND_MALFORMED_CREDENTIALS.get(), null, null, null);
136        }
137    
138    
139        // There must have been at least an authentication identity.  Verify that it
140        // is valid.
141        final String authzID;
142        final String authcID = StaticUtils.toUTF8String(credBytes, (firstNullPos+1),
143             (secondNullPos-firstNullPos-1));
144        if (firstNullPos == 0)
145        {
146          authzID = null;
147        }
148        else
149        {
150          authzID = StaticUtils.toUTF8String(credBytes, 0, firstNullPos);
151        }
152    
153        DN authDN;
154        try
155        {
156          authDN = handler.getDNForAuthzID(authcID);
157        }
158        catch (final LDAPException le)
159        {
160          Debug.debugException(le);
161          return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS,
162               le.getMessage(), le.getMatchedDN(), le.getReferralURLs(),
163               le.getResponseControls());
164        }
165    
166    
167        // Verify that the password is correct.
168        final byte[] bindPWBytes = new byte[credBytes.length - secondNullPos - 1];
169        System.arraycopy(credBytes, secondNullPos+1, bindPWBytes, 0,
170             bindPWBytes.length);
171    
172        final boolean passwordValid;
173        if (authDN.isNullDN())
174        {
175          // For an anonymous bind, the password must be empty, and no authorization
176          // ID may have been provided.
177          passwordValid = ((bindPWBytes.length == 0) && (authzID == null));
178        }
179        else
180        {
181          // Determine the password for the target user, which may be an actual
182          // entry or be included in the additional bind credentials.
183          final byte[] userPWBytes;
184          final Entry authEntry = handler.getEntry(authDN);
185          if (authEntry == null)
186          {
187            userPWBytes = handler.getAdditionalBindCredentials(authDN);
188          }
189          else
190          {
191            userPWBytes = authEntry.getAttributeValueBytes("userPassword");
192          }
193    
194          passwordValid =  Arrays.equals(bindPWBytes, userPWBytes);
195        }
196    
197        if (! passwordValid)
198        {
199          return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS,
200               null, null, null, null);
201        }
202    
203    
204        // The server doesn't really distinguish between authID and authzID, so
205        // if an authzID was provided then we'll just behave as if the user
206        // specified as the authzID had bound.
207        String authID = authcID;
208        if (authzID != null)
209        {
210          try
211          {
212            authID = authzID;
213            authDN = handler.getDNForAuthzID(authzID);
214          }
215          catch (final LDAPException le)
216          {
217            Debug.debugException(le);
218            return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS,
219                 le.getMessage(), le.getMatchedDN(), le.getReferralURLs(),
220                 le.getResponseControls());
221          }
222        }
223    
224        handler.setAuthenticatedDN(authDN);
225        final Control[] responseControls;
226        if (controlMap.containsKey(AuthorizationIdentityRequestControl.
227                 AUTHORIZATION_IDENTITY_REQUEST_OID))
228        {
229          if (authDN == null)
230          {
231            responseControls = new Control[]
232            {
233              new AuthorizationIdentityResponseControl("")
234            };
235          }
236          else
237          {
238            responseControls = new Control[]
239            {
240              new AuthorizationIdentityResponseControl("dn:" + authDN.toString())
241            };
242          }
243        }
244        else
245        {
246          responseControls = null;
247        }
248    
249        return new BindResult(messageID, ResultCode.SUCCESS, null, null, null,
250             responseControls);
251      }
252    }