001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.server.ldap.handlers.bind.plain; 021 022 023 import java.io.IOException; 024 025 import org.apache.directory.server.core.CoreSession; 026 import org.apache.directory.server.core.interceptor.context.BindOperationContext; 027 import org.apache.directory.server.i18n.I18n; 028 import org.apache.directory.server.ldap.LdapSession; 029 import org.apache.directory.server.ldap.handlers.bind.AbstractSaslServer; 030 import org.apache.directory.shared.ldap.constants.SupportedSaslMechanisms; 031 import org.apache.directory.shared.ldap.message.internal.InternalBindRequest; 032 import org.apache.directory.shared.ldap.name.DN; 033 import org.apache.directory.shared.ldap.schema.PrepareString; 034 import org.apache.directory.shared.ldap.util.StringTools; 035 036 import javax.naming.InvalidNameException; 037 import javax.security.sasl.SaslException; 038 039 040 /** 041 * A SaslServer implementation for PLAIN based SASL mechanism. This is 042 * required unfortunately because the JDK's SASL provider does not support 043 * this mechanism. 044 * 045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 046 * @version $$Rev$$ 047 */ 048 public class PlainSaslServer extends AbstractSaslServer 049 { 050 /** The authzid property stored into the LdapSession instance */ 051 public static final String SASL_PLAIN_AUTHZID = "authzid"; 052 053 /** The authcid property stored into the LdapSession instance */ 054 public static final String SASL_PLAIN_AUTHCID = "authcid"; 055 056 /** The password property stored into the LdapSession instance */ 057 public static final String SASL_PLAIN_PASSWORD = "password"; 058 059 060 /** 061 * The possible states for the negotiation of a PLAIN mechanism. 062 */ 063 private enum NegotiationState 064 { 065 INITIALIZED, // Negotiation has just started 066 MECH_RECEIVED, // We have received the PLAIN mechanism 067 COMPLETED // The user/password have been received 068 } 069 070 071 /** 072 * The different state used by the iInitialResponse decoding 073 */ 074 private enum InitialResponse 075 { 076 AUTHZID_EXPECTED, // We are expecting a authzid element 077 AUTHCID_EXPECTED, // We are expecting a authcid element 078 PASSWORD_EXPECTED // We are expecting a password element 079 } 080 081 /** The current negotiation state */ 082 private NegotiationState state; 083 084 085 /** 086 * 087 * Creates a new instance of PlainSaslServer. 088 * 089 * @param bindRequest The associated BindRequest object 090 * @param ldapSession The associated LdapSession instance 091 */ 092 public PlainSaslServer( LdapSession ldapSession, CoreSession adminSession, InternalBindRequest bindRequest ) 093 { 094 super( ldapSession, adminSession, bindRequest ); 095 state = NegotiationState.INITIALIZED; 096 097 // Reinitialize the SASL properties 098 getLdapSession().removeSaslProperty( SASL_PLAIN_AUTHZID ); 099 getLdapSession().removeSaslProperty( SASL_PLAIN_AUTHCID ); 100 getLdapSession().removeSaslProperty( SASL_PLAIN_PASSWORD ); 101 } 102 103 104 /** 105 * {@inheritDoc} 106 */ 107 public String getMechanismName() 108 { 109 return SupportedSaslMechanisms.PLAIN; 110 } 111 112 113 /** 114 * {@inheritDoc} 115 */ 116 public byte[] evaluateResponse( byte[] initialResponse ) throws SaslException 117 { 118 if ( StringTools.isEmpty( initialResponse ) ) 119 { 120 state = NegotiationState.MECH_RECEIVED; 121 return null; 122 } 123 else 124 { 125 // Split the credentials in three parts : 126 // - the optional authzId 127 // - the authId 128 // - the password 129 InitialResponse element = InitialResponse.AUTHZID_EXPECTED; 130 String authzId = null; 131 String authcId = null; 132 String password = null; 133 134 int start = 0; 135 int end = 0; 136 137 try 138 { 139 for ( byte b:initialResponse ) 140 { 141 if ( b == '\0' ) 142 { 143 if ( start - end == 0 ) 144 { 145 // We don't have any value 146 if ( element == InitialResponse.AUTHZID_EXPECTED ) 147 { 148 // This is optional : do nothing, but change 149 // the element type 150 element = InitialResponse.AUTHCID_EXPECTED; 151 continue; 152 } 153 else 154 { 155 // This not allowed 156 throw new IllegalArgumentException( I18n.err( I18n.ERR_671 ) ); 157 } 158 } 159 else 160 { 161 start++; 162 String value = new String( initialResponse, start, end - start + 1, "UTF-8" ); 163 164 switch ( element ) 165 { 166 case AUTHZID_EXPECTED : 167 element = InitialResponse.AUTHCID_EXPECTED; 168 authzId = PrepareString.normalize( value, PrepareString.StringType.CASE_EXACT_IA5 ); 169 end++; 170 start = end; 171 break; 172 173 case AUTHCID_EXPECTED : 174 element = InitialResponse.PASSWORD_EXPECTED; 175 authcId = PrepareString.normalize( value, PrepareString.StringType.DIRECTORY_STRING ); 176 end++; 177 start = end; 178 break; 179 180 181 default : 182 // This is an error ! 183 throw new IllegalArgumentException( I18n.err( I18n.ERR_672 ) ); 184 } 185 } 186 } 187 else 188 { 189 end++; 190 } 191 } 192 193 if ( start == end ) 194 { 195 throw new IllegalArgumentException( I18n.err( I18n.ERR_671 ) ); 196 } 197 198 start++; 199 String value = StringTools.utf8ToString( initialResponse, start, end - start + 1 ); 200 201 password = PrepareString.normalize( value, PrepareString.StringType.CASE_EXACT_IA5 ); 202 203 if ( ( authcId == null ) || ( password == null ) ) 204 { 205 throw new IllegalArgumentException( I18n.err( I18n.ERR_671 ) ); 206 } 207 208 // Now that we have the authcid and password, try to authenticate. 209 CoreSession userSession = authenticate( authcId, password ); 210 211 getLdapSession().setCoreSession( userSession ); 212 213 state = NegotiationState.COMPLETED; 214 } 215 catch ( IOException ioe ) 216 { 217 throw new IllegalArgumentException( I18n.err( I18n.ERR_674 ) ); 218 } 219 catch ( InvalidNameException ine ) 220 { 221 throw new IllegalArgumentException( I18n.err( I18n.ERR_675 ) ); 222 } 223 catch ( Exception e ) 224 { 225 throw new SaslException( I18n.err( I18n.ERR_676, authcId ) ); 226 } 227 } 228 229 return StringTools.EMPTY_BYTES; 230 } 231 232 233 public boolean isComplete() 234 { 235 return state == NegotiationState.COMPLETED; 236 } 237 238 239 /** 240 * Try to authenticate the usr against the underlying LDAP server. 241 */ 242 private CoreSession authenticate( String user, String password ) throws InvalidNameException, Exception 243 { 244 BindOperationContext bindContext = new BindOperationContext( getLdapSession().getCoreSession() ); 245 bindContext.setDn( new DN( user ) ); 246 bindContext.setCredentials( StringTools.getBytesUtf8( password ) ); 247 248 getAdminSession().getDirectoryService().getOperationManager().bind( bindContext ); 249 250 return bindContext.getSession(); 251 } 252 }