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;
74 resultCode = ldap_initialize(&handle, a_url.c_str());
76 if(resultCode != LDAP_SUCCESS)
77 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
80 resultCode = ldap_set_option(handle, LDAP_OPT_PROTOCOL_VERSION, &aux);
82 if(resultCode.isOk() == false)
83 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
86 resultCode = ldap_set_option(handle, LDAP_OPT_RESULT_CODE, &aux);
88 if(resultCode.isOk() == false)
89 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
92 case Session::Option::Defer::Never: aux = LDAP_DEREF_NEVER; break;
93 case Session::Option::Defer::Searching: aux = LDAP_DEREF_SEARCHING; break;
94 case Session::Option::Defer::Finding: aux = LDAP_DEREF_FINDING; break;
95 case Session::Option::Defer::Always: aux = LDAP_DEREF_ALWAYS; break;
98 resultCode = ldap_set_option(handle, LDAP_OPT_DEREF, &aux);
100 if(resultCode.isOk() == false)
101 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
103 resultCode = ldap_set_option(handle, LDAP_OPT_REFERRALS, (a_referral == Session::Option::Referral::On) ? LDAP_OPT_ON : LDAP_OPT_OFF);
105 if(resultCode.isOk() == false)
106 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
108 if(a_networkTimeout.tv_sec != -1) {
109 resultCode = ldap_set_option(handle, LDAP_OPT_NETWORK_TIMEOUT, &a_networkTimeout);
111 if(resultCode.isOk() == false)
112 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
114 } catch(RuntimeException&) {
116 ldap_memfree(&handle);
122 resultCode = ldap_simple_bind(handle, Request::asCString(a_user), Request::asCString(a_password));
124 if(resultCode < LDAP_SUCCESS)
125 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
128 ldap_get_option(handle, LDAP_OPT_DESC, &fd);
130 app::functions::component <comm::Communicator> (ANNA_FILE_LOCATION)->attach(this);
132 a_state = State::WaitingBind;
133 response_add(Response::instance(ClassCode::Bind, resultCode.getValue()));
134 } catch(RuntimeException&) {
140 const Response* Session::send(const Request* request)
141 throw(RuntimeException) {
142 if(a_state == State::Closed) {
143 string msg(asString());
144 msg += " | Session::bind must be called";
145 throw RuntimeException(msg, ANNA_FILE_LOCATION);
148 if(a_state == State::WaitingBind) {
149 string msg(asString());
150 msg += " | Waiting for connection ack";
151 throw RuntimeException(msg, ANNA_FILE_LOCATION);
154 Response* result(NULL);
156 string msg("ldap::Session::send | ");
159 msg += request->asString();
160 Logger::debug(msg, ANNA_FILE_LOCATION);
162 Guard guard(this, "ldap::Session::send");
163 ResultCode resultCode = request->send(*this);
166 resultCode.extractResultCode(this);
167 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
170 result = Response::instance(request->getClassCode(), resultCode.getValue());
171 result->setRequest(request);
172 response_add(result);
176 void Session::unbind()
177 throw(RuntimeException) {
178 if(a_state == State::Closed)
182 * Esta invocación terminará invocando al método ldap::Session::finalize
184 a_communicator->detach(this);
187 //------------------------------------------------------------------------------------------
188 // Se invoca desde el comm::Communicator cuando se detecta activada sobre el fd's asociado
189 // a la ldap::Session
190 //------------------------------------------------------------------------------------------
191 void Session::apply()
192 throw(RuntimeException) {
193 LOGMETHOD(TraceMethod traceMethod("ldap::Session", "apply", ANNA_FILE_LOCATION));
194 LDAP* handle = (LDAP*) a_ldap;
195 LDAPMessage* hmessage(NULL);
196 ResultCode resultCode;
197 // La SIGALRM se tratará en Engine::alarmnCatcher => por ahora sólo se ignora
199 // El código que recibe la respuesta no está protegido ante la apareción de una señal.
200 // Cuando este método se ejecuta es porque ya sabemos que ha algo => no habrá espera
201 // Cuando el MSGID de la respuesta recibida no se encuentra en la lista de respuestas esperadas la OpenLDAP
202 // invoca a un select en el que supuestamente se pone a esperar una respuesta 'esperada' ... esperá 100 ms
203 // como máximo ya que la señal que ponemos lo sacará de la espera.
204 resultCode = ldap_result(handle, LDAP_RES_ANY, LDAP_MSG_ONE, NULL, &hmessage);
208 if(resultCode.getValue() == -1) {
209 bool disconnect(false);
211 if(resultCode.extractResultCode(this) == false)
213 else if(resultCode.isServerDown() == true)
215 else if(resultCode.isConnectError() == true)
218 // Si espera fue interrumpida por una excepción el código de erro LDAP será -1, pero errno será EINTR
219 // En ese caso sólo ignoramos el mensaje, pero no cerramos la conexión.
220 if(disconnect == true && xerrno == EINTR)
223 ldap::Exception lex(this, resultCode, ANNA_FILE_LOCATION);
225 if(disconnect == true)
228 eventResponseError(resultCode, disconnect);
230 if(disconnect == true)
231 a_communicator->detach(this);
236 IdMessage idMessage = ldap_msgid(hmessage);
239 switch(resultCode.getValue()) {
240 case LDAP_RES_BIND: receiveBind(idMessage, hmessage); break;
241 case LDAP_RES_SEARCH_ENTRY: receiveEntry(idMessage, hmessage); break;
242 case LDAP_RES_SEARCH_REFERENCE: receiveReference(idMessage, hmessage); break;
243 case LDAP_RES_SEARCH_RESULT: receiveResult(idMessage, hmessage); break;
246 string msg(asString());
247 msg += functions::asText(" | IdMessage: ", idMessage);
249 msg += resultCode.asString();
250 Logger::warning(msg, ANNA_FILE_LOCATION);
255 ldap_msgfree(hmessage);
256 } catch(RuntimeException&) {
257 ldap_msgfree(hmessage);
263 * invocado por el comm::Communicator::detach.
265 void Session::finalize()
267 LDAP* handle = (LDAP*) a_ldap;
268 a_state = State::Closed;
275 eventServerShutdown();
276 ResultCode resultCode;
279 * Notifica la finalización de las respuestas pendientes de recibir
281 resultCode = LDAP_UNAVAILABLE;
283 for(response_iterator ii = response_begin(), maxii = response_end(); ii != maxii; ii ++) {
284 response = Session::response(ii);
285 response->setResultCode(resultCode);
286 response->cancelTimer();
289 eventResponse(*response);
290 } catch(RuntimeException& ex) {
294 Response::release(response);
300 void Session::receiveBind(const IdMessage idMessage, Session::HandleMessage _hmessage)
301 throw(RuntimeException) {
302 LDAP* handle = (LDAP*) a_ldap;
303 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
305 // Si se invoca al unbind después de hacer el bind ... y llega la respuesta
306 if(a_state != State::WaitingBind) {
307 string msg("ldap::Session::receiveBind | ");
309 msg += " | Unexpected Bind-response";
310 throw RuntimeException(msg, ANNA_FILE_LOCATION);
313 ResultCode resultCode;
314 int error = LDAP_SUCCESS;
315 Response* response = response_find(idMessage);
317 string msg("ldap::Session::receiveBind | ");
320 msg += response->asString();
321 Logger::debug(msg, ANNA_FILE_LOCATION);
323 response->cancelTimer();
324 const int ldap_result = ldap_parse_result(handle, hmessage, &error, NULL, NULL, NULL, NULL, 0);
325 bool unbindAfterDone = false;
326 resultCode.setValue(ldap_result, error);
328 string msg("ldap::Session::receiveBind | ");
331 msg += resultCode.asString();
332 msg += functions::asText(" | LDAP Result: ", ldap_result);
333 msg += functions::asText(" | LDAP Error: ", error);
334 Logger::debug(msg, ANNA_FILE_LOCATION);
337 if(resultCode.isOk() == false) {
338 Exception ldapex(this, resultCode = error, ANNA_FILE_LOCATION);
340 response->setResultCode(resultCode);
341 unbindAfterDone = true;
343 a_state = State::Bound;
346 eventResponse(*response);
347 } catch(RuntimeException& ex) {
351 response_erase(response);
353 if(unbindAfterDone == true)
357 //------------------------------------------------------------------------------------------------
358 // (1) Necesario para detectar el final de los atributos
359 //------------------------------------------------------------------------------------------------
360 void Session::receiveEntry(const IdMessage idMessage, Session::HandleMessage _hmessage)
361 throw(RuntimeException) {
362 LDAP* handle = (LDAP*) a_ldap;
363 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
364 BerElement *ber(NULL);
366 Response* response = response_find(idMessage);
368 string msg("ldap::Session::receiveEntry | ");
369 msg += response->asString();
370 Logger::debug(msg, ANNA_FILE_LOCATION);
372 ResultCode resultCode(ldap_get_dn_ber(handle, hmessage, &ber, &name));
374 if(resultCode.isOk() == false) {
375 Exception ldapex(this, resultCode , ANNA_FILE_LOCATION);
377 response->setResultCode(resultCode);
378 eventIntermediateResponseError(*response);
384 Attribute* attribute;
385 response->setName(value.assign(name.bv_val, name.bv_len));
387 string msg("ldap::Session::receiveEntry | DN: ");
389 Logger::debug(msg, ANNA_FILE_LOCATION);
392 while(ldap_get_attribute_ber(handle, hmessage, ber, &name, &values) == LDAP_SUCCESS) {
393 if(name.bv_val == NULL) // (1)
396 attribute = response->createAttribute(value.assign(name.bv_val, name.bv_len));
398 string msg("ldap::Session::receiveEntry | Attribute: ");
400 Logger::debug(msg, ANNA_FILE_LOCATION);
404 for(int i = 0; values [i].bv_val != NULL; i ++) {
405 attribute->add(value.assign(values [i].bv_val, values [i].bv_len));
407 string msg("ldap::Session::receiveEntry | Value: ");
409 Logger::debug(msg, ANNA_FILE_LOCATION);
420 void Session::receiveReference(const IdMessage idMessage, Session::HandleMessage _hmessage)
421 throw(RuntimeException) {
422 LDAP* handle = (LDAP*) a_ldap;
423 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
425 Response* response = response_find(idMessage);
427 string msg("ldap::Session::receiveReference | ");
428 msg += response->asString();
429 Logger::debug(msg, ANNA_FILE_LOCATION);
431 ResultCode resultCode(ldap_parse_reference(handle, hmessage, &values, NULL, 0));
433 if(resultCode.isOk() == false) {
434 Exception ldapex(this, resultCode , ANNA_FILE_LOCATION);
436 response->setResultCode(resultCode);
437 eventIntermediateResponseError(*response);
442 for(int i = 0; values [i] != NULL; i ++)
443 response->createReferral(values [i]);
445 ber_memvfree((void**) values);
449 void Session::receiveResult(const IdMessage idMessage, Session::HandleMessage _hmessage)
450 throw(RuntimeException) {
451 LDAP* handle = (LDAP*) a_ldap;
452 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
453 Response* response = response_find(idMessage);
455 string msg("ldap::Session::receiveResult | ");
458 msg += response->asString();
459 Logger::debug(msg, ANNA_FILE_LOCATION);
461 int error = LDAP_SUCCESS;
463 ResultCode resultCode;
464 response->cancelTimer();
465 const int ldap_result = ldap_parse_result(handle, hmessage, &error, NULL, NULL, &values, NULL, 0);
466 resultCode.setValue(ldap_result, error);
468 string msg("ldap::Session::receiveResult | ");
471 msg += resultCode.asString();
472 msg += functions::asText(" | LDAP Result: ", ldap_result);
473 msg += functions::asText(" | LDAP Error: ", error);
474 Logger::debug(msg, ANNA_FILE_LOCATION);
477 if(resultCode.isOk() == false) {
478 Exception ldapex(this, resultCode = error, ANNA_FILE_LOCATION);
480 response->setResultCode(resultCode);
481 } else if(values != NULL) {
482 for(int i = 0; values [i] != NULL; i ++)
483 response->createReferral(values [i]);
485 ldap_value_free(values);
489 eventResponse(*response);
490 } catch(RuntimeException& ex) {
494 response_erase(response);
497 //-------------------------------------------------------------------------
498 // Se invoca desde ldap::timer::Prototype::expire
499 //-------------------------------------------------------------------------
500 void Session::expireResponse(ldap::Response* response)
502 LDAP* handle = (LDAP*) a_ldap;
503 ResultCode resultCode;
505 * Si lo que ha caducado es una petición de Bind hay que cerrar la conexión
506 * y liberar los recursos.
508 bool unbindAfterDone(true);
510 if(response->getClassCode() != ClassCode::Bind) {
511 if(response->getRequest()->getOnExpiry() == Request::OnExpiry::Abandon) {
512 resultCode = ldap_abandon(handle, response->getIdMessage());
514 if(resultCode.isOk() == false) {
515 Exception ldapex(this, resultCode, ANNA_FILE_LOCATION);
520 unbindAfterDone = false;
523 response->setResultCode(resultCode = LDAP_TIMEOUT);
526 eventResponse(*response);
527 } catch(RuntimeException& ex) {
531 response_erase(response);
533 if(unbindAfterDone == true)
537 void Session::response_add(Response* response)
539 a_responses.add(response);
540 response->setSession(this);
543 response->activateTimer();
544 } catch(anna::Exception& ex) {
549 void Session::response_erase(Response* response)
551 a_responses.erase(response);
552 Response::release(response);
555 Response* Session::response_find(const IdMessage idMessage)
556 throw(RuntimeException) {
557 ldap::Response* result = a_responses.find(idMessage);
560 string msg(asString());
561 msg += functions::asText(" | IdMessage: ", idMessage);
562 msg += " | Message not registered at session";
563 throw RuntimeException(msg, ANNA_FILE_LOCATION);
569 std::string Session::asString() const
571 string result("ldap::Session { ");
572 result += comm::Handler::asString();
573 result += " | State: ";
574 result += asText(a_state);
575 result += " | URL: ";
577 result += " | User: ";
578 result += Request::asText(a_user);
580 if(a_externalID != -1)
581 result += functions::asText(" | ExternalID: ", a_externalID);
583 if(hasNetworkTimeout()) {
584 result += functions::asText(" | Network timeout { Sec: ", (int) a_networkTimeout.tv_sec);
585 result += functions::asText(" | uSec: ", (int) a_networkTimeout.tv_usec);
589 return result += " }";
592 xml::Node* Session::asXML(xml::Node* parent) const
594 parent = comm::Handler::asXML(parent);
595 xml::Node* result = parent->createChild("ldap.Session");
596 result->createAttribute("State", asText(a_state));
597 result->createAttribute("User", Request::asText(a_user));
598 result->createAttribute("N", a_responses.size());
599 result->createChild("URL")->createAttribute("Value", a_url);
601 if(a_externalID != -1)
602 result->createChild("ExternalID")->createAttribute("Value", a_externalID);
604 if(hasNetworkTimeout())
605 result->createChild("NetworkTimeout")->createAttribute("Value", getNetworkTimeout());
607 xml::Node* requests = result->createChild("ldap.Requests");
608 const Request* request;
610 for(const_response_iterator ii = response_begin(), maxii = response_end(); ii != maxii; ii ++) {
611 if((request = Session::response(ii)->getRequest()) != NULL)
612 request->asXML(requests);
618 int Session::getDangerousFileDescriptor() const
619 throw(RuntimeException) {
620 ResultCode resultCode;
622 resultCode = ldap_get_option((LDAP*) a_ldap, LDAP_OPT_DESC, &result);
624 if(resultCode.isOk() == false)
625 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
628 string msg("Session::getDangerousFileDescriptor | Result: ");
629 msg += functions::asString(result);
630 Logger::warning(msg, ANNA_FILE_LOCATION);
635 const char* Session::asText(const State::_v state)
637 static const char* states [] = { "Closed", "WaitingBind", "Bound" };
638 return states [state];
641 IdMessage Session::SortById::value(const Response* response)
643 return response->getIdMessage();