001/* 002 * Copyright 2011-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2011-2017 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 */ 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 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}