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 */ 020package org.apache.directory.server.ldap.handlers; 021 022 023import org.apache.commons.lang3.exception.ExceptionUtils; 024import org.apache.directory.api.ldap.codec.api.LdapApiService; 025import org.apache.directory.api.ldap.model.exception.LdapOperationException; 026import org.apache.directory.api.ldap.model.exception.LdapReferralException; 027import org.apache.directory.api.ldap.model.message.AbandonRequest; 028import org.apache.directory.api.ldap.model.message.BindRequest; 029import org.apache.directory.api.ldap.model.message.BindResponse; 030import org.apache.directory.api.ldap.model.message.BindResponseImpl; 031import org.apache.directory.api.ldap.model.message.ExtendedRequest; 032import org.apache.directory.api.ldap.model.message.LdapResult; 033import org.apache.directory.api.ldap.model.message.Referral; 034import org.apache.directory.api.ldap.model.message.ReferralImpl; 035import org.apache.directory.api.ldap.model.message.Request; 036import org.apache.directory.api.ldap.model.message.ResultCodeEnum; 037import org.apache.directory.api.ldap.model.message.ResultResponse; 038import org.apache.directory.api.ldap.model.message.ResultResponseRequest; 039import org.apache.directory.server.core.api.CoreSession; 040import org.apache.directory.server.core.shared.DefaultCoreSession; 041import org.apache.directory.server.i18n.I18n; 042import org.apache.directory.server.ldap.LdapServer; 043import org.apache.directory.server.ldap.LdapSession; 044import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler; 045import org.apache.mina.core.filterchain.IoFilterChain; 046import org.apache.mina.core.session.IoSession; 047import org.apache.mina.handler.demux.MessageHandler; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051 052/** 053 * A base class for all LDAP request handlers. 054 * 055 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 056 */ 057public abstract class LdapRequestHandler<T extends Request> implements MessageHandler<T> 058{ 059 /** The logger for this class */ 060 protected static final Logger LOG = LoggerFactory.getLogger( LdapRequestHandler.class ); 061 062 /** The reference on the Ldap server instance */ 063 protected LdapServer ldapServer; 064 065 066 /** 067 * @return The associated ldap server instance 068 */ 069 public final LdapServer getLdapServer() 070 { 071 return ldapServer; 072 } 073 074 075 /** 076 * Associates a Ldap server instance to the message handler 077 * @param ldapServer the associated ldap server instance 078 */ 079 public final void setLdapServer( LdapServer ldapServer ) 080 { 081 this.ldapServer = ldapServer; 082 } 083 084 085 /** 086 * Checks to see if confidentiality requirements are met. If the 087 * LdapServer requires confidentiality and the SSLFilter is engaged 088 * this will return true. If confidentiality is not required this 089 * will return true. If confidentially is required and the SSLFilter 090 * is not engaged in the IoFilterChain this will return false. 091 * 092 * This method is used by handlers to determine whether to send back 093 * {@link ResultCodeEnum#CONFIDENTIALITY_REQUIRED} error responses back 094 * to clients. 095 * 096 * @param session the MINA IoSession to check for TLS security 097 * @return true if confidentiality requirement is met, false otherwise 098 */ 099 public final boolean isConfidentialityRequirementSatisfied( IoSession session ) 100 { 101 102 if ( !ldapServer.isConfidentialityRequired() ) 103 { 104 return true; 105 } 106 107 IoFilterChain chain = session.getFilterChain(); 108 return chain.contains( "sslFilter" ); 109 } 110 111 112 public void rejectWithoutConfidentiality( IoSession session, ResultResponse resp ) 113 { 114 LdapResult result = resp.getLdapResult(); 115 result.setResultCode( ResultCodeEnum.CONFIDENTIALITY_REQUIRED ); 116 result.setDiagnosticMessage( "Confidentiality (TLS secured connection) is required." ); 117 session.write( resp ); 118 } 119 120 121 /** 122 *{@inheritDoc} 123 */ 124 @Override 125 public final void handleMessage( IoSession session, T message ) throws Exception 126 { 127 LdapSession ldapSession = ldapServer.getLdapSessionManager().getLdapSession( session ); 128 129 if ( ldapSession == null ) 130 { 131 // in some cases the session is becoming null though the client is sending the UnbindRequest 132 // before closing 133 LOG.info( "ignoring the message {} received from null session", message ); 134 return; 135 } 136 137 // First check that the client hasn't issued a previous BindRequest, unless it 138 // was a SASL BindRequest 139 if ( ldapSession.isAuthPending() ) 140 { 141 // Only SASL BinRequest are allowed if we already are handling a 142 // SASL BindRequest 143 if ( !( message instanceof BindRequest ) || ( ( BindRequest ) message ).isSimple() 144 || ldapSession.isSimpleAuthPending() ) 145 { 146 LOG.error( I18n.err( I18n.ERR_732 ) ); 147 BindResponse bindResponse = new BindResponseImpl( message.getMessageId() ); 148 LdapResult bindResult = bindResponse.getLdapResult(); 149 bindResult.setResultCode( ResultCodeEnum.UNWILLING_TO_PERFORM ); 150 bindResult.setDiagnosticMessage( I18n.err( I18n.ERR_732 ) ); 151 ldapSession.getIoSession().write( bindResponse ); 152 return; 153 } 154 } 155 156 // TODO - session you get from LdapServer should have the ldapServer 157 // member already set no? Should remove these lines where ever they 158 // may be if that's the case. 159 ldapSession.setLdapServer( ldapServer ); 160 161 // protect against insecure conns when confidentiality is required 162 if ( !isConfidentialityRequirementSatisfied( session ) ) 163 { 164 if ( message instanceof ExtendedRequest ) 165 { 166 // Reject all extended operations except StartTls 167 ExtendedRequest req = ( ExtendedRequest ) message; 168 169 if ( !req.getRequestName().equals( StartTlsHandler.EXTENSION_OID ) ) 170 { 171 rejectWithoutConfidentiality( session, req.getResultResponse() ); 172 return; 173 } 174 175 // Allow StartTls extended operations to go through 176 } 177 else if ( message instanceof ResultResponseRequest ) 178 { 179 // Reject all other operations that have a result response 180 rejectWithoutConfidentiality( session, ( ( ResultResponseRequest ) message ) 181 .getResultResponse() ); 182 return; 183 } 184 else 185 // Just return from unbind, and abandon immediately 186 { 187 return; 188 } 189 } 190 191 // We should check that the server allows anonymous requests 192 // only if it's not a BindRequest 193 if ( message instanceof BindRequest ) 194 { 195 handle( ldapSession, message ); 196 } 197 else 198 { 199 CoreSession coreSession = null; 200 201 /* 202 * All requests except bind automatically presume the authentication 203 * is anonymous if the session has not been authenticated. Hence a 204 * default bind is presumed as the anonymous identity. 205 */ 206 if ( ldapSession.isAuthenticated() ) 207 { 208 coreSession = ldapSession.getCoreSession(); 209 handle( ldapSession, message ); 210 return; 211 } 212 213 coreSession = getLdapServer().getDirectoryService().getSession(); 214 ldapSession.setCoreSession( coreSession ); 215 216 // Store the IoSession in the coreSession 217 ( ( DefaultCoreSession ) coreSession ).setIoSession( ldapSession.getIoSession() ); 218 219 if ( message instanceof AbandonRequest ) 220 { 221 return; 222 } 223 224 handle( ldapSession, message ); 225 } 226 } 227 228 229 /** 230 * Handle a Ldap message associated with a session 231 * 232 * @param session The associated session 233 * @param message The message we have to handle 234 * @throws Exception If there is an error during the processing of this message 235 */ 236 public abstract void handle( LdapSession session, T message ) throws Exception; 237 238 239 /** 240 * Handles processing with referrals without ManageDsaIT decorator. 241 * 242 * @param session The associated session 243 * @param req The response 244 * @param e The associated exception 245 */ 246 public void handleException( LdapSession session, ResultResponseRequest request, ResultResponse response, Exception e ) 247 { 248 LdapResult result = request.getResultResponse().getLdapResult(); 249 250 /* 251 * Set the result code or guess the best option. 252 */ 253 ResultCodeEnum code; 254 255 if ( e instanceof LdapOperationException ) 256 { 257 code = ( ( LdapOperationException ) e ).getResultCode(); 258 } 259 else 260 { 261 code = ResultCodeEnum.getBestEstimate( e, request.getType() ); 262 } 263 264 result.setResultCode( code ); 265 266 /* 267 * Setup the error message to put into the request and put entire 268 * exception into the message if we are in debug mode. Note we 269 * embed the result code name into the message. 270 */ 271 String msg = code.toString() + ": failed for " + request + ": " + e.getLocalizedMessage(); 272 273 LOG.debug( msg, e ); 274 275 if ( LOG.isDebugEnabled() || ( code == ResultCodeEnum.OTHER ) ) 276 { 277 msg += ":\n" + ExceptionUtils.getStackTrace( e ); 278 } 279 280 result.setDiagnosticMessage( msg ); 281 282 if ( e instanceof LdapOperationException ) 283 { 284 LdapOperationException ne = ( LdapOperationException ) e; 285 286 // Add the matchedDN if necessary 287 boolean setMatchedDn = code == ResultCodeEnum.NO_SUCH_OBJECT || code == ResultCodeEnum.ALIAS_PROBLEM 288 || code == ResultCodeEnum.INVALID_DN_SYNTAX || code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM; 289 290 if ( ( ne.getResolvedDn() != null ) && setMatchedDn ) 291 { 292 result.setMatchedDn( ne.getResolvedDn() ); 293 } 294 295 // Add the referrals if necessary 296 if ( e instanceof LdapReferralException ) 297 { 298 Referral referrals = new ReferralImpl(); 299 300 do 301 { 302 String ref = ( ( LdapReferralException ) e ).getReferralInfo(); 303 referrals.addLdapUrl( ref ); 304 } 305 while ( ( ( LdapReferralException ) e ).skipReferral() ); 306 307 result.setReferral( referrals ); 308 } 309 } 310 311 session.getIoSession().write( response ); 312 } 313 314 315 /** 316 * @return The LDAP API Codec service 317 */ 318 protected LdapApiService getLdapApiService() 319 { 320 return ldapServer.getDirectoryService().getLdapCodecService(); 321 } 322}