001/* 002 * Copyright 2011-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2011-2019 Ping Identity Corporation 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 */ 021package com.unboundid.ldap.listener; 022 023 024 025import java.util.Arrays; 026import java.util.List; 027import java.util.Map; 028 029import com.unboundid.asn1.ASN1OctetString; 030import com.unboundid.ldap.protocol.LDAPMessage; 031import com.unboundid.ldap.sdk.BindResult; 032import com.unboundid.ldap.sdk.Control; 033import com.unboundid.ldap.sdk.DN; 034import com.unboundid.ldap.sdk.Entry; 035import com.unboundid.ldap.sdk.LDAPException; 036import com.unboundid.ldap.sdk.ResultCode; 037import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl; 038import com.unboundid.ldap.sdk.controls.AuthorizationIdentityResponseControl; 039import com.unboundid.util.Debug; 040import com.unboundid.util.NotMutable; 041import com.unboundid.util.StaticUtils; 042import com.unboundid.util.ThreadSafety; 043import com.unboundid.util.ThreadSafetyLevel; 044 045import 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) 056public 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 Entry authEntry = handler.getEntry(authDN); 184 if (authEntry == null) 185 { 186 final byte[] userPWBytes = handler.getAdditionalBindCredentials(authDN); 187 passwordValid = Arrays.equals(bindPWBytes, userPWBytes); 188 } 189 else 190 { 191 final List<InMemoryDirectoryServerPassword> passwordList = 192 handler.getPasswordsInEntry(authEntry, 193 new ASN1OctetString(bindPWBytes)); 194 passwordValid = (! passwordList.isEmpty()); 195 } 196 } 197 198 if (! passwordValid) 199 { 200 return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS, 201 null, null, null, null); 202 } 203 204 205 // The server doesn't really distinguish between authID and authzID, so 206 // if an authzID was provided then we'll just behave as if the user 207 // specified as the authzID had bound. 208 if (authzID != null) 209 { 210 try 211 { 212 authDN = handler.getDNForAuthzID(authzID); 213 } 214 catch (final LDAPException le) 215 { 216 Debug.debugException(le); 217 return new BindResult(messageID, ResultCode.INVALID_CREDENTIALS, 218 le.getMessage(), le.getMatchedDN(), le.getReferralURLs(), 219 le.getResponseControls()); 220 } 221 } 222 223 handler.setAuthenticatedDN(authDN); 224 final Control[] responseControls; 225 if (controlMap.containsKey(AuthorizationIdentityRequestControl. 226 AUTHORIZATION_IDENTITY_REQUEST_OID)) 227 { 228 if (authDN == null) 229 { 230 responseControls = new Control[] 231 { 232 new AuthorizationIdentityResponseControl("") 233 }; 234 } 235 else 236 { 237 responseControls = new Control[] 238 { 239 new AuthorizationIdentityResponseControl("dn:" + authDN.toString()) 240 }; 241 } 242 } 243 else 244 { 245 responseControls = null; 246 } 247 248 return new BindResult(messageID, ResultCode.SUCCESS, null, null, null, 249 responseControls); 250 } 251}