001/* 002 * Copyright 2011-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2011-2024 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-2024 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.NotNull; 057import com.unboundid.util.Nullable; 058import com.unboundid.util.StaticUtils; 059import com.unboundid.util.ThreadSafety; 060import com.unboundid.util.ThreadSafetyLevel; 061 062import static com.unboundid.ldap.listener.ListenerMessages.*; 063 064 065 066/** 067 * This class defines a SASL bind handler which may be used to provide support 068 * for the SASL PLAIN mechanism (as defined in RFC 4616) in the in-memory 069 * directory server. 070 */ 071@NotMutable() 072@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 073public final class PLAINBindHandler 074 extends InMemorySASLBindHandler 075{ 076 /** 077 * Creates a new instance of this SASL bind handler. 078 */ 079 public PLAINBindHandler() 080 { 081 // No initialization is required. 082 } 083 084 085 086 /** 087 * {@inheritDoc} 088 */ 089 @Override() 090 @NotNull() 091 public String getSASLMechanismName() 092 { 093 return "PLAIN"; 094 } 095 096 097 098 /** 099 * {@inheritDoc} 100 */ 101 @Override() 102 @NotNull() 103 public BindResult processSASLBind( 104 @NotNull final InMemoryRequestHandler handler, 105 final int messageID, @NotNull final DN bindDN, 106 @Nullable final ASN1OctetString credentials, 107 @NotNull final List<Control> controls) 108 { 109 // Process the provided request controls. 110 final Map<String,Control> controlMap; 111 try 112 { 113 controlMap = RequestControlPreProcessor.processControls( 114 LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST, controls); 115 } 116 catch (final LDAPException le) 117 { 118 Debug.debugException(le); 119 return new BindResult(messageID, le.getResultCode(), 120 le.getMessage(), le.getMatchedDN(), le.getReferralURLs(), 121 le.getResponseControls()); 122 } 123 124 125 // Parse the credentials, which should be in the form: 126 // [authzid] UTF8NUL authcid UTF8NUL passwd 127 if (credentials == null) 128 { 129 return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS, 130 ERR_PLAIN_BIND_NO_CREDENTIALS.get(), null, null, null); 131 } 132 133 int firstNullPos = -1; 134 int secondNullPos = -1; 135 final byte[] credBytes = credentials.getValue(); 136 for (int i=0; i < credBytes.length; i++) 137 { 138 if (credBytes[i] == 0x00) 139 { 140 if (firstNullPos < 0) 141 { 142 firstNullPos = i; 143 } 144 else 145 { 146 secondNullPos = i; 147 break; 148 } 149 } 150 } 151 152 if (secondNullPos < 0) 153 { 154 return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS, 155 ERR_PLAIN_BIND_MALFORMED_CREDENTIALS.get(), null, null, null); 156 } 157 158 159 // There must have been at least an authentication identity. Verify that it 160 // is valid. 161 final String authzID; 162 final String authcID = StaticUtils.toUTF8String(credBytes, (firstNullPos+1), 163 (secondNullPos-firstNullPos-1)); 164 if (firstNullPos == 0) 165 { 166 authzID = null; 167 } 168 else 169 { 170 authzID = StaticUtils.toUTF8String(credBytes, 0, firstNullPos); 171 } 172 173 DN authDN; 174 try 175 { 176 authDN = handler.getDNForAuthzID(authcID); 177 } 178 catch (final LDAPException le) 179 { 180 Debug.debugException(le); 181 return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS, 182 le.getMessage(), le.getMatchedDN(), le.getReferralURLs(), 183 le.getResponseControls()); 184 } 185 186 187 // Verify that the password is correct. 188 final byte[] bindPWBytes = new byte[credBytes.length - secondNullPos - 1]; 189 System.arraycopy(credBytes, secondNullPos+1, bindPWBytes, 0, 190 bindPWBytes.length); 191 192 final boolean passwordValid; 193 if (authDN.isNullDN()) 194 { 195 // For an anonymous bind, the password must be empty, and no authorization 196 // ID may have been provided. 197 passwordValid = ((bindPWBytes.length == 0) && (authzID == null)); 198 } 199 else 200 { 201 // Determine the password for the target user, which may be an actual 202 // entry or be included in the additional bind credentials. 203 final Entry authEntry = handler.getEntry(authDN); 204 if (authEntry == null) 205 { 206 final byte[] userPWBytes = handler.getAdditionalBindCredentials(authDN); 207 passwordValid = Arrays.equals(bindPWBytes, userPWBytes); 208 } 209 else 210 { 211 final List<InMemoryDirectoryServerPassword> passwordList = 212 handler.getPasswordsInEntry(authEntry, 213 new ASN1OctetString(bindPWBytes)); 214 passwordValid = (! passwordList.isEmpty()); 215 } 216 } 217 218 if (! passwordValid) 219 { 220 return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS, 221 null, null, null, null); 222 } 223 224 225 // The server doesn't really distinguish between authID and authzID, so 226 // if an authzID was provided then we'll just behave as if the user 227 // specified as the authzID had bound. 228 if (authzID != null) 229 { 230 try 231 { 232 authDN = handler.getDNForAuthzID(authzID); 233 } 234 catch (final LDAPException le) 235 { 236 Debug.debugException(le); 237 return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS, 238 le.getMessage(), le.getMatchedDN(), le.getReferralURLs(), 239 le.getResponseControls()); 240 } 241 } 242 243 handler.setAuthenticatedDN(authDN); 244 final Control[] responseControls; 245 if (controlMap.containsKey(AuthorizationIdentityRequestControl. 246 AUTHORIZATION_IDENTITY_REQUEST_OID)) 247 { 248 if (authDN == null) 249 { 250 responseControls = new Control[] 251 { 252 new AuthorizationIdentityResponseControl("") 253 }; 254 } 255 else 256 { 257 responseControls = new Control[] 258 { 259 new AuthorizationIdentityResponseControl("dn:" + authDN.toString()) 260 }; 261 } 262 } 263 else 264 { 265 responseControls = null; 266 } 267 268 return new BindResult(messageID, ResultCode.SUCCESS, null, null, null, 269 responseControls); 270 } 271}