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