1 // ANNA - Anna is Not Nothingness Anymore //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo //
5 // See project site at http://redmine.teslayout.com/projects/anna-suite //
6 // See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE //
14 #include <anna/core/tracing/Logger.hpp>
15 #include <anna/core/functions.hpp>
16 #include <anna/core/tracing/TraceMethod.hpp>
18 #include <anna/xml/Node.hpp>
19 #include <anna/xml/Attribute.hpp>
21 #include <anna/app/functions.hpp>
23 #include <anna/comm/Communicator.hpp>
25 #include <anna/ldap/Session.hpp>
26 #include <anna/ldap/ResultCode.hpp>
27 #include <anna/ldap/Attribute.hpp>
28 #include <anna/ldap/Response.hpp>
29 #include <anna/ldap/Request.hpp>
31 #include <anna/ldap/internal/Exception.hpp>
35 using namespace anna::ldap;
38 const Millisecond Session::DefaultTimeout(1000);
41 comm::Handler(comm::Handler::Type::Custom, comm::Handler::Support::None),
42 a_state(State::Closed),
44 a_defer(Option::Defer::Never),
45 a_referral(Option::Referral::Off),
47 comm::Handler::a_communicator = app::functions::component <comm::Communicator> (ANNA_FILE_LOCATION);
49 for(int i = ClassCode::Min; i < ClassCode::Max; i ++)
50 a_timeouts [i] = DefaultTimeout;
52 a_networkTimeout.tv_sec = -1; a_networkTimeout.tv_usec = 0;
55 //---------------------------------------------------------------------------------
56 // Se invoca desde el ldap::Engine
57 //---------------------------------------------------------------------------------
59 throw(RuntimeException) {
60 if(a_state != State::Closed)
64 string msg("ldap::Session::bind | ");
66 Logger::debug(msg, ANNA_FILE_LOCATION);
68 Guard guard(this, "ldap::Session::bind");
71 ResultCode resultCode;
72 Response* response(NULL);
75 resultCode = ldap_initialize(&handle, a_url.c_str());
77 if(resultCode != LDAP_SUCCESS)
78 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
81 resultCode = ldap_set_option(handle, LDAP_OPT_PROTOCOL_VERSION, &aux);
83 if(resultCode.isOk() == false)
84 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
87 resultCode = ldap_set_option(handle, LDAP_OPT_RESULT_CODE, &aux);
89 if(resultCode.isOk() == false)
90 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
93 case Session::Option::Defer::Never: aux = LDAP_DEREF_NEVER; break;
94 case Session::Option::Defer::Searching: aux = LDAP_DEREF_SEARCHING; break;
95 case Session::Option::Defer::Finding: aux = LDAP_DEREF_FINDING; break;
96 case Session::Option::Defer::Always: aux = LDAP_DEREF_ALWAYS; break;
99 resultCode = ldap_set_option(handle, LDAP_OPT_DEREF, &aux);
101 if(resultCode.isOk() == false)
102 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
104 resultCode = ldap_set_option(handle, LDAP_OPT_REFERRALS, (a_referral == Session::Option::Referral::On) ? LDAP_OPT_ON : LDAP_OPT_OFF);
106 if(resultCode.isOk() == false)
107 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
109 if(a_networkTimeout.tv_sec != -1) {
110 resultCode = ldap_set_option(handle, LDAP_OPT_NETWORK_TIMEOUT, &a_networkTimeout);
112 if(resultCode.isOk() == false)
113 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
115 } catch(RuntimeException&) {
117 ldap_memfree(&handle);
123 resultCode = ldap_simple_bind(handle, Request::asCString(a_user), Request::asCString(a_password));
125 if(resultCode < LDAP_SUCCESS)
126 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
129 ldap_get_option(handle, LDAP_OPT_DESC, &fd);
131 app::functions::component <comm::Communicator> (ANNA_FILE_LOCATION)->attach(this);
133 a_state = State::WaitingBind;
134 response_add(Response::instance(ClassCode::Bind, resultCode.getValue()));
135 } catch(RuntimeException&) {
141 const Response* Session::send(const Request* request)
142 throw(RuntimeException) {
143 if(a_state == State::Closed) {
144 string msg(asString());
145 msg += " | Session::bind must be called";
146 throw RuntimeException(msg, ANNA_FILE_LOCATION);
149 if(a_state == State::WaitingBind) {
150 string msg(asString());
151 msg += " | Waiting for connection ack";
152 throw RuntimeException(msg, ANNA_FILE_LOCATION);
155 Response* result(NULL);
157 string msg("ldap::Session::send | ");
160 msg += request->asString();
161 Logger::debug(msg, ANNA_FILE_LOCATION);
163 Guard guard(this, "ldap::Session::send");
164 ResultCode resultCode = request->send(*this);
167 resultCode.extractResultCode(this);
168 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
171 result = Response::instance(request->getClassCode(), resultCode.getValue());
172 result->setRequest(request);
173 response_add(result);
177 void Session::unbind()
178 throw(RuntimeException) {
179 if(a_state == State::Closed)
183 * Esta invocación terminará invocando al método ldap::Session::finalize
185 a_communicator->detach(this);
188 //------------------------------------------------------------------------------------------
189 // Se invoca desde el comm::Communicator cuando se detecta activada sobre el fd's asociado
190 // a la ldap::Session
191 //------------------------------------------------------------------------------------------
192 void Session::apply()
193 throw(RuntimeException) {
194 LOGMETHOD(TraceMethod traceMethod("ldap::Session", "apply", ANNA_FILE_LOCATION));
195 LDAP* handle = (LDAP*) a_ldap;
196 LDAPMessage* hmessage(NULL);
197 ResultCode resultCode;
198 // La SIGALRM se tratará en Engine::alarmnCatcher => por ahora sólo se ignora
200 // El código que recibe la respuesta no está protegido ante la apareción de una señal.
201 // Cuando este método se ejecuta es porque ya sabemos que ha algo => no habrá espera
202 // Cuando el MSGID de la respuesta recibida no se encuentra en la lista de respuestas esperadas la OpenLDAP
203 // invoca a un select en el que supuestamente se pone a esperar una respuesta 'esperada' ... esperá 100 ms
204 // como máximo ya que la señal que ponemos lo sacará de la espera.
205 resultCode = ldap_result(handle, LDAP_RES_ANY, LDAP_MSG_ONE, NULL, &hmessage);
209 if(resultCode.getValue() == -1) {
210 bool disconnect(false);
212 if(resultCode.extractResultCode(this) == false)
214 else if(resultCode.isServerDown() == true)
216 else if(resultCode.isConnectError() == true)
219 // Si espera fue interrumpida por una excepción el código de erro LDAP será -1, pero errno será EINTR
220 // En ese caso sólo ignoramos el mensaje, pero no cerramos la conexión.
221 if(disconnect == true && xerrno == EINTR)
224 ldap::Exception lex(this, resultCode, ANNA_FILE_LOCATION);
226 if(disconnect == true)
229 eventResponseError(resultCode, disconnect);
231 if(disconnect == true)
232 a_communicator->detach(this);
237 IdMessage idMessage = ldap_msgid(hmessage);
240 switch(resultCode.getValue()) {
241 case LDAP_RES_BIND: receiveBind(idMessage, hmessage); break;
242 case LDAP_RES_SEARCH_ENTRY: receiveEntry(idMessage, hmessage); break;
243 case LDAP_RES_SEARCH_REFERENCE: receiveReference(idMessage, hmessage); break;
244 case LDAP_RES_SEARCH_RESULT: receiveResult(idMessage, hmessage); break;
247 string msg(asString());
248 msg += functions::asText(" | IdMessage: ", idMessage);
250 msg += resultCode.asString();
251 Logger::warning(msg, ANNA_FILE_LOCATION);
256 ldap_msgfree(hmessage);
257 } catch(RuntimeException&) {
258 ldap_msgfree(hmessage);
264 * invocado por el comm::Communicator::detach.
266 void Session::finalize()
268 LDAP* handle = (LDAP*) a_ldap;
269 a_state = State::Closed;
276 eventServerShutdown();
277 ResultCode resultCode;
280 * Notifica la finalización de las respuestas pendientes de recibir
282 resultCode = LDAP_UNAVAILABLE;
284 for(response_iterator ii = response_begin(), maxii = response_end(); ii != maxii; ii ++) {
285 response = Session::response(ii);
286 response->setResultCode(resultCode);
287 response->cancelTimer();
290 eventResponse(*response);
291 } catch(RuntimeException& ex) {
295 Response::release(response);
301 void Session::receiveBind(const IdMessage idMessage, Session::HandleMessage _hmessage)
302 throw(RuntimeException) {
303 LDAP* handle = (LDAP*) a_ldap;
304 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
306 // Si se invoca al unbind después de hacer el bind ... y llega la respuesta
307 if(a_state != State::WaitingBind) {
308 string msg("ldap::Session::receiveBind | ");
310 msg += " | Unexpected Bind-response";
311 throw RuntimeException(msg, ANNA_FILE_LOCATION);
314 ResultCode resultCode;
315 int error = LDAP_SUCCESS;
316 Response* response = response_find(idMessage);
318 string msg("ldap::Session::receiveBind | ");
321 msg += response->asString();
322 Logger::debug(msg, ANNA_FILE_LOCATION);
324 response->cancelTimer();
325 const int ldap_result = ldap_parse_result(handle, hmessage, &error, NULL, NULL, NULL, NULL, 0);
326 bool unbindAfterDone = false;
327 resultCode.setValue(ldap_result, error);
329 string msg("ldap::Session::receiveBind | ");
332 msg += resultCode.asString();
333 msg += functions::asText(" | LDAP Result: ", ldap_result);
334 msg += functions::asText(" | LDAP Error: ", error);
335 Logger::debug(msg, ANNA_FILE_LOCATION);
338 if(resultCode.isOk() == false) {
339 Exception ldapex(this, resultCode = error, ANNA_FILE_LOCATION);
341 response->setResultCode(resultCode);
342 unbindAfterDone = true;
344 a_state = State::Bound;
347 eventResponse(*response);
348 } catch(RuntimeException& ex) {
352 response_erase(response);
354 if(unbindAfterDone == true)
358 //------------------------------------------------------------------------------------------------
359 // (1) Necesario para detectar el final de los atributos
360 //------------------------------------------------------------------------------------------------
361 void Session::receiveEntry(const IdMessage idMessage, Session::HandleMessage _hmessage)
362 throw(RuntimeException) {
363 LDAP* handle = (LDAP*) a_ldap;
364 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
365 BerElement *ber(NULL);
367 Response* response = response_find(idMessage);
369 string msg("ldap::Session::receiveEntry | ");
370 msg += response->asString();
371 Logger::debug(msg, ANNA_FILE_LOCATION);
373 ResultCode resultCode(ldap_get_dn_ber(handle, hmessage, &ber, &name));
375 if(resultCode.isOk() == false) {
376 Exception ldapex(this, resultCode , ANNA_FILE_LOCATION);
378 response->setResultCode(resultCode);
379 eventIntermediateResponseError(*response);
385 Attribute* attribute;
386 response->setName(value.assign(name.bv_val, name.bv_len));
388 string msg("ldap::Session::receiveEntry | DN: ");
390 Logger::debug(msg, ANNA_FILE_LOCATION);
393 while(ldap_get_attribute_ber(handle, hmessage, ber, &name, &values) == LDAP_SUCCESS) {
394 if(name.bv_val == NULL) // (1)
397 attribute = response->createAttribute(value.assign(name.bv_val, name.bv_len));
399 string msg("ldap::Session::receiveEntry | Attribute: ");
401 Logger::debug(msg, ANNA_FILE_LOCATION);
405 for(int i = 0; values [i].bv_val != NULL; i ++) {
406 attribute->add(value.assign(values [i].bv_val, values [i].bv_len));
408 string msg("ldap::Session::receiveEntry | Value: ");
410 Logger::debug(msg, ANNA_FILE_LOCATION);
421 void Session::receiveReference(const IdMessage idMessage, Session::HandleMessage _hmessage)
422 throw(RuntimeException) {
423 LDAP* handle = (LDAP*) a_ldap;
424 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
426 Response* response = response_find(idMessage);
428 string msg("ldap::Session::receiveReference | ");
429 msg += response->asString();
430 Logger::debug(msg, ANNA_FILE_LOCATION);
432 ResultCode resultCode(ldap_parse_reference(handle, hmessage, &values, NULL, 0));
434 if(resultCode.isOk() == false) {
435 Exception ldapex(this, resultCode , ANNA_FILE_LOCATION);
437 response->setResultCode(resultCode);
438 eventIntermediateResponseError(*response);
443 for(int i = 0; values [i] != NULL; i ++)
444 response->createReferral(values [i]);
446 ber_memvfree((void**) values);
450 void Session::receiveResult(const IdMessage idMessage, Session::HandleMessage _hmessage)
451 throw(RuntimeException) {
452 LDAP* handle = (LDAP*) a_ldap;
453 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
454 Response* response = response_find(idMessage);
456 string msg("ldap::Session::receiveResult | ");
459 msg += response->asString();
460 Logger::debug(msg, ANNA_FILE_LOCATION);
462 int error = LDAP_SUCCESS;
464 ResultCode resultCode;
466 response->cancelTimer();
467 const int ldap_result = ldap_parse_result(handle, hmessage, &error, NULL, NULL, &values, NULL, 0);
468 resultCode.setValue(ldap_result, error);
470 string msg("ldap::Session::receiveResult | ");
473 msg += resultCode.asString();
474 msg += functions::asText(" | LDAP Result: ", ldap_result);
475 msg += functions::asText(" | LDAP Error: ", error);
476 Logger::debug(msg, ANNA_FILE_LOCATION);
479 if(resultCode.isOk() == false) {
480 Exception ldapex(this, resultCode = error, ANNA_FILE_LOCATION);
482 response->setResultCode(resultCode);
483 } else if(values != NULL) {
484 for(int i = 0; values [i] != NULL; i ++)
485 response->createReferral(values [i]);
487 ldap_value_free(values);
491 eventResponse(*response);
492 } catch(RuntimeException& ex) {
496 response_erase(response);
499 //-------------------------------------------------------------------------
500 // Se invoca desde ldap::timer::Prototype::expire
501 //-------------------------------------------------------------------------
502 void Session::expireResponse(ldap::Response* response)
504 LDAP* handle = (LDAP*) a_ldap;
505 ResultCode resultCode;
507 * Si lo que ha caducado es una petición de Bind hay que cerrar la conexión
508 * y liberar los recursos.
510 bool unbindAfterDone(true);
512 if(response->getClassCode() != ClassCode::Bind) {
513 if(response->getRequest()->getOnExpiry() == Request::OnExpiry::Abandon) {
514 resultCode = ldap_abandon(handle, response->getIdMessage());
516 if(resultCode.isOk() == false) {
517 Exception ldapex(this, resultCode, ANNA_FILE_LOCATION);
522 unbindAfterDone = false;
525 response->setResultCode(resultCode = LDAP_TIMEOUT);
528 eventResponse(*response);
529 } catch(RuntimeException& ex) {
533 response_erase(response);
535 if(unbindAfterDone == true)
539 void Session::response_add(Response* response)
541 a_responses.add(response);
542 response->setSession(this);
545 response->activateTimer();
546 } catch(anna::Exception& ex) {
551 void Session::response_erase(Response* response)
553 a_responses.erase(response);
554 Response::release(response);
557 Response* Session::response_find(const IdMessage idMessage)
558 throw(RuntimeException) {
559 ldap::Response* result = a_responses.find(idMessage);
562 string msg(asString());
563 msg += functions::asText(" | IdMessage: ", idMessage);
564 msg += " | Message not registered at session";
565 throw RuntimeException(msg, ANNA_FILE_LOCATION);
571 std::string Session::asString() const
573 string result("ldap::Session { ");
574 result += comm::Handler::asString();
575 result += " | State: ";
576 result += asText(a_state);
577 result += " | URL: ";
579 result += " | User: ";
580 result += Request::asText(a_user);
582 if(a_externalID != -1)
583 result += functions::asText(" | ExternalID: ", a_externalID);
585 if(hasNetworkTimeout()) {
586 result += functions::asText(" | Network timeout { Sec: ", (int) a_networkTimeout.tv_sec);
587 result += functions::asText(" | uSec: ", (int) a_networkTimeout.tv_usec);
591 return result += " }";
594 xml::Node* Session::asXML(xml::Node* parent) const
596 parent = comm::Handler::asXML(parent);
597 xml::Node* result = parent->createChild("ldap.Session");
598 result->createAttribute("State", asText(a_state));
599 result->createAttribute("User", Request::asText(a_user));
600 result->createAttribute("N", a_responses.size());
601 result->createChild("URL")->createAttribute("Value", a_url);
603 if(a_externalID != -1)
604 result->createChild("ExternalID")->createAttribute("Value", a_externalID);
606 if(hasNetworkTimeout())
607 result->createChild("NetworkTimeout")->createAttribute("Value", getNetworkTimeout());
609 xml::Node* requests = result->createChild("ldap.Requests");
610 const Response* response;
611 const Request* request;
613 for(const_response_iterator ii = response_begin(), maxii = response_end(); ii != maxii; ii ++) {
614 if((request = Session::response(ii)->getRequest()) != NULL)
615 request->asXML(requests);
621 int Session::getDangerousFileDescriptor() const
622 throw(RuntimeException) {
623 ResultCode resultCode;
625 resultCode = ldap_get_option((LDAP*) a_ldap, LDAP_OPT_DESC, &result);
627 if(resultCode.isOk() == false)
628 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
631 string msg("Session::getDangerousFileDescriptor | Result: ");
632 msg += functions::asString(result);
633 Logger::warning(msg, ANNA_FILE_LOCATION);
638 const char* Session::asText(const State::_v state)
640 static const char* states [] = { "Closed", "WaitingBind", "Bound" };
641 return states [state];
644 IdMessage Session::SortById::value(const Response* response)
646 return response->getIdMessage();