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}