1 // ANNA - Anna is Not 'N' Anymore
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
5 // https://bitbucket.org/testillano/anna
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
11 // * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 // * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
17 // * Neither the name of Google Inc. nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 // Authors: eduardo.ramos.testillano@gmail.com
34 // cisco.tierra@gmail.com
42 #include <anna/core/tracing/Logger.hpp>
43 #include <anna/core/functions.hpp>
44 #include <anna/core/tracing/TraceMethod.hpp>
46 #include <anna/xml/Node.hpp>
47 #include <anna/xml/Attribute.hpp>
49 #include <anna/app/functions.hpp>
51 #include <anna/comm/Communicator.hpp>
53 #include <anna/ldap/Session.hpp>
54 #include <anna/ldap/ResultCode.hpp>
55 #include <anna/ldap/Attribute.hpp>
56 #include <anna/ldap/Response.hpp>
57 #include <anna/ldap/Request.hpp>
59 #include <anna/ldap/internal/Exception.hpp>
63 using namespace anna::ldap;
66 const Millisecond Session::DefaultTimeout(1000);
69 comm::Handler(comm::Handler::Type::Custom, comm::Handler::Support::None),
70 a_state(State::Closed),
72 a_defer(Option::Defer::Never),
73 a_referral(Option::Referral::Off),
75 comm::Handler::a_communicator = app::functions::component <comm::Communicator> (ANNA_FILE_LOCATION);
77 for(int i = ClassCode::Min; i < ClassCode::Max; i ++)
78 a_timeouts [i] = DefaultTimeout;
80 a_networkTimeout.tv_sec = -1; a_networkTimeout.tv_usec = 0;
83 //---------------------------------------------------------------------------------
84 // Se invoca desde el ldap::Engine
85 //---------------------------------------------------------------------------------
87 throw(RuntimeException) {
88 if(a_state != State::Closed)
92 string msg("ldap::Session::bind | ");
94 Logger::debug(msg, ANNA_FILE_LOCATION);
96 Guard guard(this, "ldap::Session::bind");
99 ResultCode resultCode;
100 Response* response(NULL);
103 resultCode = ldap_initialize(&handle, a_url.c_str());
105 if(resultCode != LDAP_SUCCESS)
106 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
109 resultCode = ldap_set_option(handle, LDAP_OPT_PROTOCOL_VERSION, &aux);
111 if(resultCode.isOk() == false)
112 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
115 resultCode = ldap_set_option(handle, LDAP_OPT_RESULT_CODE, &aux);
117 if(resultCode.isOk() == false)
118 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
121 case Session::Option::Defer::Never: aux = LDAP_DEREF_NEVER; break;
122 case Session::Option::Defer::Searching: aux = LDAP_DEREF_SEARCHING; break;
123 case Session::Option::Defer::Finding: aux = LDAP_DEREF_FINDING; break;
124 case Session::Option::Defer::Always: aux = LDAP_DEREF_ALWAYS; break;
127 resultCode = ldap_set_option(handle, LDAP_OPT_DEREF, &aux);
129 if(resultCode.isOk() == false)
130 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
132 resultCode = ldap_set_option(handle, LDAP_OPT_REFERRALS, (a_referral == Session::Option::Referral::On) ? LDAP_OPT_ON : LDAP_OPT_OFF);
134 if(resultCode.isOk() == false)
135 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
137 if(a_networkTimeout.tv_sec != -1) {
138 resultCode = ldap_set_option(handle, LDAP_OPT_NETWORK_TIMEOUT, &a_networkTimeout);
140 if(resultCode.isOk() == false)
141 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
143 } catch(RuntimeException&) {
145 ldap_memfree(&handle);
151 resultCode = ldap_simple_bind(handle, Request::asCString(a_user), Request::asCString(a_password));
153 if(resultCode < LDAP_SUCCESS)
154 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
157 ldap_get_option(handle, LDAP_OPT_DESC, &fd);
159 app::functions::component <comm::Communicator> (ANNA_FILE_LOCATION)->attach(this);
161 a_state = State::WaitingBind;
162 response_add(Response::instance(ClassCode::Bind, resultCode.getValue()));
163 } catch(RuntimeException&) {
169 const Response* Session::send(const Request* request)
170 throw(RuntimeException) {
171 if(a_state == State::Closed) {
172 string msg(asString());
173 msg += " | Session::bind must be called";
174 throw RuntimeException(msg, ANNA_FILE_LOCATION);
177 if(a_state == State::WaitingBind) {
178 string msg(asString());
179 msg += " | Waiting for connection ack";
180 throw RuntimeException(msg, ANNA_FILE_LOCATION);
183 Response* result(NULL);
185 string msg("ldap::Session::send | ");
188 msg += request->asString();
189 Logger::debug(msg, ANNA_FILE_LOCATION);
191 Guard guard(this, "ldap::Session::send");
192 ResultCode resultCode = request->send(*this);
195 resultCode.extractResultCode(this);
196 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
199 result = Response::instance(request->getClassCode(), resultCode.getValue());
200 result->setRequest(request);
201 response_add(result);
205 void Session::unbind()
206 throw(RuntimeException) {
207 if(a_state == State::Closed)
211 * Esta invocación terminará invocando al método ldap::Session::finalize
213 a_communicator->detach(this);
216 //------------------------------------------------------------------------------------------
217 // Se invoca desde el comm::Communicator cuando se detecta activada sobre el fd's asociado
218 // a la ldap::Session
219 //------------------------------------------------------------------------------------------
220 void Session::apply()
221 throw(RuntimeException) {
222 LOGMETHOD(TraceMethod traceMethod("ldap::Session", "apply", ANNA_FILE_LOCATION));
223 LDAP* handle = (LDAP*) a_ldap;
224 LDAPMessage* hmessage(NULL);
225 ResultCode resultCode;
226 // La SIGALRM se tratará en Engine::alarmnCatcher => por ahora sólo se ignora
228 // El código que recibe la respuesta no está protegido ante la apareción de una señal.
229 // Cuando este método se ejecuta es porque ya sabemos que ha algo => no habrá espera
230 // Cuando el MSGID de la respuesta recibida no se encuentra en la lista de respuestas esperadas la OpenLDAP
231 // invoca a un select en el que supuestamente se pone a esperar una respuesta 'esperada' ... esperá 100 ms
232 // como máximo ya que la señal que ponemos lo sacará de la espera.
233 resultCode = ldap_result(handle, LDAP_RES_ANY, LDAP_MSG_ONE, NULL, &hmessage);
237 if(resultCode.getValue() == -1) {
238 bool disconnect(false);
240 if(resultCode.extractResultCode(this) == false)
242 else if(resultCode.isServerDown() == true)
244 else if(resultCode.isConnectError() == true)
247 // Si espera fue interrumpida por una excepción el código de erro LDAP será -1, pero errno será EINTR
248 // En ese caso sólo ignoramos el mensaje, pero no cerramos la conexión.
249 if(disconnect == true && xerrno == EINTR)
252 ldap::Exception lex(this, resultCode, ANNA_FILE_LOCATION);
254 if(disconnect == true)
257 eventResponseError(resultCode, disconnect);
259 if(disconnect == true)
260 a_communicator->detach(this);
265 IdMessage idMessage = ldap_msgid(hmessage);
268 switch(resultCode.getValue()) {
269 case LDAP_RES_BIND: receiveBind(idMessage, hmessage); break;
270 case LDAP_RES_SEARCH_ENTRY: receiveEntry(idMessage, hmessage); break;
271 case LDAP_RES_SEARCH_REFERENCE: receiveReference(idMessage, hmessage); break;
272 case LDAP_RES_SEARCH_RESULT: receiveResult(idMessage, hmessage); break;
275 string msg(asString());
276 msg += functions::asText(" | IdMessage: ", idMessage);
278 msg += resultCode.asString();
279 Logger::warning(msg, ANNA_FILE_LOCATION);
284 ldap_msgfree(hmessage);
285 } catch(RuntimeException&) {
286 ldap_msgfree(hmessage);
292 * invocado por el comm::Communicator::detach.
294 void Session::finalize()
296 LDAP* handle = (LDAP*) a_ldap;
297 a_state = State::Closed;
304 eventServerShutdown();
305 ResultCode resultCode;
308 * Notifica la finalización de las respuestas pendientes de recibir
310 resultCode = LDAP_UNAVAILABLE;
312 for(response_iterator ii = response_begin(), maxii = response_end(); ii != maxii; ii ++) {
313 response = Session::response(ii);
314 response->setResultCode(resultCode);
315 response->cancelTimer();
318 eventResponse(*response);
319 } catch(RuntimeException& ex) {
323 Response::release(response);
329 void Session::receiveBind(const IdMessage idMessage, Session::HandleMessage _hmessage)
330 throw(RuntimeException) {
331 LDAP* handle = (LDAP*) a_ldap;
332 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
334 // Si se invoca al unbind después de hacer el bind ... y llega la respuesta
335 if(a_state != State::WaitingBind) {
336 string msg("ldap::Session::receiveBind | ");
338 msg += " | Unexpected Bind-response";
339 throw RuntimeException(msg, ANNA_FILE_LOCATION);
342 ResultCode resultCode;
343 int error = LDAP_SUCCESS;
344 Response* response = response_find(idMessage);
346 string msg("ldap::Session::receiveBind | ");
349 msg += response->asString();
350 Logger::debug(msg, ANNA_FILE_LOCATION);
352 response->cancelTimer();
353 const int ldap_result = ldap_parse_result(handle, hmessage, &error, NULL, NULL, NULL, NULL, 0);
354 bool unbindAfterDone = false;
355 resultCode.setValue(ldap_result, error);
357 string msg("ldap::Session::receiveBind | ");
360 msg += resultCode.asString();
361 msg += functions::asText(" | LDAP Result: ", ldap_result);
362 msg += functions::asText(" | LDAP Error: ", error);
363 Logger::debug(msg, ANNA_FILE_LOCATION);
366 if(resultCode.isOk() == false) {
367 Exception ldapex(this, resultCode = error, ANNA_FILE_LOCATION);
369 response->setResultCode(resultCode);
370 unbindAfterDone = true;
372 a_state = State::Bound;
375 eventResponse(*response);
376 } catch(RuntimeException& ex) {
380 response_erase(response);
382 if(unbindAfterDone == true)
386 //------------------------------------------------------------------------------------------------
387 // (1) Necesario para detectar el final de los atributos
388 //------------------------------------------------------------------------------------------------
389 void Session::receiveEntry(const IdMessage idMessage, Session::HandleMessage _hmessage)
390 throw(RuntimeException) {
391 LDAP* handle = (LDAP*) a_ldap;
392 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
393 BerElement *ber(NULL);
395 Response* response = response_find(idMessage);
397 string msg("ldap::Session::receiveEntry | ");
398 msg += response->asString();
399 Logger::debug(msg, ANNA_FILE_LOCATION);
401 ResultCode resultCode(ldap_get_dn_ber(handle, hmessage, &ber, &name));
403 if(resultCode.isOk() == false) {
404 Exception ldapex(this, resultCode , ANNA_FILE_LOCATION);
406 response->setResultCode(resultCode);
407 eventIntermediateResponseError(*response);
413 Attribute* attribute;
414 response->setName(value.assign(name.bv_val, name.bv_len));
416 string msg("ldap::Session::receiveEntry | DN: ");
418 Logger::debug(msg, ANNA_FILE_LOCATION);
421 while(ldap_get_attribute_ber(handle, hmessage, ber, &name, &values) == LDAP_SUCCESS) {
422 if(name.bv_val == NULL) // (1)
425 attribute = response->createAttribute(value.assign(name.bv_val, name.bv_len));
427 string msg("ldap::Session::receiveEntry | Attribute: ");
429 Logger::debug(msg, ANNA_FILE_LOCATION);
433 for(int i = 0; values [i].bv_val != NULL; i ++) {
434 attribute->add(value.assign(values [i].bv_val, values [i].bv_len));
436 string msg("ldap::Session::receiveEntry | Value: ");
438 Logger::debug(msg, ANNA_FILE_LOCATION);
449 void Session::receiveReference(const IdMessage idMessage, Session::HandleMessage _hmessage)
450 throw(RuntimeException) {
451 LDAP* handle = (LDAP*) a_ldap;
452 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
454 Response* response = response_find(idMessage);
456 string msg("ldap::Session::receiveReference | ");
457 msg += response->asString();
458 Logger::debug(msg, ANNA_FILE_LOCATION);
460 ResultCode resultCode(ldap_parse_reference(handle, hmessage, &values, NULL, 0));
462 if(resultCode.isOk() == false) {
463 Exception ldapex(this, resultCode , ANNA_FILE_LOCATION);
465 response->setResultCode(resultCode);
466 eventIntermediateResponseError(*response);
471 for(int i = 0; values [i] != NULL; i ++)
472 response->createReferral(values [i]);
474 ber_memvfree((void**) values);
478 void Session::receiveResult(const IdMessage idMessage, Session::HandleMessage _hmessage)
479 throw(RuntimeException) {
480 LDAP* handle = (LDAP*) a_ldap;
481 LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
482 Response* response = response_find(idMessage);
484 string msg("ldap::Session::receiveResult | ");
487 msg += response->asString();
488 Logger::debug(msg, ANNA_FILE_LOCATION);
490 int error = LDAP_SUCCESS;
492 ResultCode resultCode;
494 response->cancelTimer();
495 const int ldap_result = ldap_parse_result(handle, hmessage, &error, NULL, NULL, &values, NULL, 0);
496 resultCode.setValue(ldap_result, error);
498 string msg("ldap::Session::receiveResult | ");
501 msg += resultCode.asString();
502 msg += functions::asText(" | LDAP Result: ", ldap_result);
503 msg += functions::asText(" | LDAP Error: ", error);
504 Logger::debug(msg, ANNA_FILE_LOCATION);
507 if(resultCode.isOk() == false) {
508 Exception ldapex(this, resultCode = error, ANNA_FILE_LOCATION);
510 response->setResultCode(resultCode);
511 } else if(values != NULL) {
512 for(int i = 0; values [i] != NULL; i ++)
513 response->createReferral(values [i]);
515 ldap_value_free(values);
519 eventResponse(*response);
520 } catch(RuntimeException& ex) {
524 response_erase(response);
527 //-------------------------------------------------------------------------
528 // Se invoca desde ldap::timer::Prototype::expire
529 //-------------------------------------------------------------------------
530 void Session::expireResponse(ldap::Response* response)
532 LDAP* handle = (LDAP*) a_ldap;
533 ResultCode resultCode;
535 * Si lo que ha caducado es una petición de Bind hay que cerrar la conexión
536 * y liberar los recursos.
538 bool unbindAfterDone(true);
540 if(response->getClassCode() != ClassCode::Bind) {
541 if(response->getRequest()->getOnExpiry() == Request::OnExpiry::Abandon) {
542 resultCode = ldap_abandon(handle, response->getIdMessage());
544 if(resultCode.isOk() == false) {
545 Exception ldapex(this, resultCode, ANNA_FILE_LOCATION);
550 unbindAfterDone = false;
553 response->setResultCode(resultCode = LDAP_TIMEOUT);
556 eventResponse(*response);
557 } catch(RuntimeException& ex) {
561 response_erase(response);
563 if(unbindAfterDone == true)
567 void Session::response_add(Response* response)
569 a_responses.add(response);
570 response->setSession(this);
573 response->activateTimer();
574 } catch(anna::Exception& ex) {
579 void Session::response_erase(Response* response)
581 a_responses.erase(response);
582 Response::release(response);
585 Response* Session::response_find(const IdMessage idMessage)
586 throw(RuntimeException) {
587 ldap::Response* result = a_responses.find(idMessage);
590 string msg(asString());
591 msg += functions::asText(" | IdMessage: ", idMessage);
592 msg += " | Message not registered at session";
593 throw RuntimeException(msg, ANNA_FILE_LOCATION);
599 std::string Session::asString() const
601 string result("ldap::Session { ");
602 result += comm::Handler::asString();
603 result += " | State: ";
604 result += asText(a_state);
605 result += " | URL: ";
607 result += " | User: ";
608 result += Request::asText(a_user);
610 if(a_externalID != -1)
611 result += functions::asText(" | ExternalID: ", a_externalID);
613 if(hasNetworkTimeout()) {
614 result += functions::asText(" | Network timeout { Sec: ", (int) a_networkTimeout.tv_sec);
615 result += functions::asText(" | uSec: ", (int) a_networkTimeout.tv_usec);
619 return result += " }";
622 xml::Node* Session::asXML(xml::Node* parent) const
624 parent = comm::Handler::asXML(parent);
625 xml::Node* result = parent->createChild("ldap.Session");
626 result->createAttribute("State", asText(a_state));
627 result->createAttribute("User", Request::asText(a_user));
628 result->createAttribute("N", a_responses.size());
629 result->createChild("URL")->createAttribute("Value", a_url);
631 if(a_externalID != -1)
632 result->createChild("ExternalID")->createAttribute("Value", a_externalID);
634 if(hasNetworkTimeout())
635 result->createChild("NetworkTimeout")->createAttribute("Value", getNetworkTimeout());
637 xml::Node* requests = result->createChild("ldap.Requests");
638 const Response* response;
639 const Request* request;
641 for(const_response_iterator ii = response_begin(), maxii = response_end(); ii != maxii; ii ++) {
642 if((request = Session::response(ii)->getRequest()) != NULL)
643 request->asXML(requests);
649 int Session::getDangerousFileDescriptor() const
650 throw(RuntimeException) {
651 ResultCode resultCode;
653 resultCode = ldap_get_option((LDAP*) a_ldap, LDAP_OPT_DESC, &result);
655 if(resultCode.isOk() == false)
656 throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
659 string msg("Session::getDangerousFileDescriptor | Result: ");
660 msg += functions::asString(result);
661 Logger::warning(msg, ANNA_FILE_LOCATION);
666 const char* Session::asText(const State::_v state)
668 static const char* states [] = { "Closed", "WaitingBind", "Bound" };
669 return states [state];
672 IdMessage Session::SortById::value(const Response* response)
674 return response->getIdMessage();