fdf6196d0e1940f54ca1ab0be54338400985f29d
[anna.git] / source / ldap / Session.cpp
1 // ANNA - Anna is Not Nothingness Anymore                                                         //
2 //                                                                                                //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo                         //
4 //                                                                                                //
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 //
7
8
9 #include <time.h>
10 #include <ldap.h>
11 #include <poll.h>
12 #include <signal.h>
13
14 #include <anna/core/tracing/Logger.hpp>
15 #include <anna/core/functions.hpp>
16 #include <anna/core/tracing/TraceMethod.hpp>
17
18 #include <anna/xml/Node.hpp>
19 #include <anna/xml/Attribute.hpp>
20
21 #include <anna/app/functions.hpp>
22
23 #include <anna/comm/Communicator.hpp>
24
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>
30
31 #include <anna/ldap/internal/Exception.hpp>
32
33 using namespace std;
34 using namespace anna;
35 using namespace anna::ldap;
36
37 //static
38 const Millisecond Session::DefaultTimeout(1000);
39
40 Session::Session() :
41   comm::Handler(comm::Handler::Type::Custom, comm::Handler::Support::None),
42   a_state(State::Closed),
43   a_ldap(NULL),
44   a_defer(Option::Defer::Never),
45   a_referral(Option::Referral::Off),
46   a_externalID(-1) {
47   comm::Handler::a_communicator = app::functions::component <comm::Communicator> (ANNA_FILE_LOCATION);
48
49   for(int i = ClassCode::Min; i < ClassCode::Max; i ++)
50     a_timeouts [i] = DefaultTimeout;
51
52   a_networkTimeout.tv_sec = -1; a_networkTimeout.tv_usec = 0;
53 }
54
55 //---------------------------------------------------------------------------------
56 // Se invoca desde el ldap::Engine
57 //---------------------------------------------------------------------------------
58 void Session::bind()
59 throw(RuntimeException) {
60   if(a_state != State::Closed)
61     return;
62
63   LOGDEBUG(
64     string msg("ldap::Session::bind | ");
65     msg += asString();
66     Logger::debug(msg, ANNA_FILE_LOCATION);
67   );
68   Guard guard(this, "ldap::Session::bind");
69   LDAP* handle = NULL;
70   int aux;
71   ResultCode resultCode;
72   Response* response(NULL);
73
74   try {
75     resultCode = ldap_initialize(&handle, a_url.c_str());
76
77     if(resultCode != LDAP_SUCCESS)
78       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
79
80     aux = LDAP_VERSION3;
81     resultCode = ldap_set_option(handle, LDAP_OPT_PROTOCOL_VERSION, &aux);
82
83     if(resultCode.isOk() == false)
84       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
85
86     aux = 1;
87     resultCode = ldap_set_option(handle, LDAP_OPT_RESULT_CODE, &aux);
88
89     if(resultCode.isOk() == false)
90       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
91
92     switch(a_defer) {
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;
97     }
98
99     resultCode = ldap_set_option(handle, LDAP_OPT_DEREF, &aux);
100
101     if(resultCode.isOk() == false)
102       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
103
104     resultCode = ldap_set_option(handle, LDAP_OPT_REFERRALS, (a_referral == Session::Option::Referral::On) ? LDAP_OPT_ON : LDAP_OPT_OFF);
105
106     if(resultCode.isOk() == false)
107       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
108
109     if(a_networkTimeout.tv_sec != -1) {
110       resultCode = ldap_set_option(handle, LDAP_OPT_NETWORK_TIMEOUT, &a_networkTimeout);
111
112       if(resultCode.isOk() == false)
113         throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
114     }
115   } catch(RuntimeException&) {
116     if(handle != NULL)
117       ldap_memfree(&handle);
118
119     throw;
120   }
121
122   try {
123     resultCode = ldap_simple_bind(handle, Request::asCString(a_user), Request::asCString(a_password));
124
125     if(resultCode < LDAP_SUCCESS)
126       throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
127
128     int fd = -1;
129     ldap_get_option(handle, LDAP_OPT_DESC, &fd);
130     setfd(fd);
131     app::functions::component <comm::Communicator> (ANNA_FILE_LOCATION)->attach(this);
132     a_ldap = handle;
133     a_state = State::WaitingBind;
134     response_add(Response::instance(ClassCode::Bind, resultCode.getValue()));
135   } catch(RuntimeException&) {
136     ldap_unbind(handle);
137     throw;
138   }
139 }
140
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);
147   }
148
149   if(a_state == State::WaitingBind) {
150     string msg(asString());
151     msg += " | Waiting for connection ack";
152     throw RuntimeException(msg, ANNA_FILE_LOCATION);
153   }
154
155   Response* result(NULL);
156   LOGDEBUG(
157     string msg("ldap::Session::send | ");
158     msg += asString();
159     msg += " | ";
160     msg += request->asString();
161     Logger::debug(msg, ANNA_FILE_LOCATION);
162   );
163   Guard guard(this, "ldap::Session::send");
164   ResultCode resultCode = request->send(*this);
165
166   if(resultCode < 0) {
167     resultCode.extractResultCode(this);
168     throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
169   }
170
171   result = Response::instance(request->getClassCode(), resultCode.getValue());
172   result->setRequest(request);
173   response_add(result);
174   return result;
175 }
176
177 void Session::unbind()
178 throw(RuntimeException) {
179   if(a_state == State::Closed)
180     return;
181
182   /**
183    * Esta invocación terminará invocando al método ldap::Session::finalize
184    */
185   a_communicator->detach(this);
186 }
187
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
199   ualarm(100000, 0);
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);
206   int xerrno = errno;
207   ualarm(0, 0);
208
209   if(resultCode.getValue() == -1) {
210     bool disconnect(false);
211
212     if(resultCode.extractResultCode(this) == false)
213       disconnect = true;
214     else if(resultCode.isServerDown() == true)
215       disconnect = true;
216     else if(resultCode.isConnectError() == true)
217       disconnect = true;
218
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)
222       disconnect = false;
223
224     ldap::Exception lex(this, resultCode, ANNA_FILE_LOCATION);
225
226     if(disconnect == true)
227       lex.trace();
228
229     eventResponseError(resultCode, disconnect);
230
231     if(disconnect == true)
232       a_communicator->detach(this);
233
234     return;
235   }
236
237   IdMessage idMessage = ldap_msgid(hmessage);
238
239   try {
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;
245     default:
246       LOGWARNING(
247         string msg(asString());
248         msg += functions::asText(" | IdMessage: ", idMessage);
249         msg += " | ";
250         msg += resultCode.asString();
251         Logger::warning(msg, ANNA_FILE_LOCATION);
252       );
253       break;
254     }
255
256     ldap_msgfree(hmessage);
257   } catch(RuntimeException&) {
258     ldap_msgfree(hmessage);
259     throw;
260   }
261 }
262
263 /**
264  * invocado por el comm::Communicator::detach.
265  */
266 void Session::finalize()
267 throw() {
268   LDAP* handle = (LDAP*) a_ldap;
269   a_state = State::Closed;
270
271   if(handle != NULL) {
272     ldap_unbind(handle);
273     a_ldap = NULL;
274   }
275
276   eventServerShutdown();
277   ResultCode resultCode;
278   Response* response;
279   /*
280    * Notifica la finalización de las respuestas pendientes de recibir
281    */
282   resultCode = LDAP_UNAVAILABLE;
283
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();
288
289     try {
290       eventResponse(*response);
291     } catch(RuntimeException& ex) {
292       ex.trace();
293     }
294
295     Response::release(response);
296   }
297
298   a_responses.clear();
299 }
300
301 void Session::receiveBind(const IdMessage idMessage, Session::HandleMessage _hmessage)
302 throw(RuntimeException) {
303   LDAP* handle = (LDAP*) a_ldap;
304   LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
305
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 | ");
309     msg += asString();
310     msg += " | Unexpected Bind-response";
311     throw RuntimeException(msg, ANNA_FILE_LOCATION);
312   }
313
314   ResultCode resultCode;
315   int error = LDAP_SUCCESS;
316   Response* response = response_find(idMessage);
317   LOGDEBUG(
318     string msg("ldap::Session::receiveBind | ");
319     msg += asString();
320     msg += " | ";
321     msg += response->asString();
322     Logger::debug(msg, ANNA_FILE_LOCATION);
323   );
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);
328   LOGDEBUG(
329     string msg("ldap::Session::receiveBind | ");
330     msg += asString();
331     msg += " | ";
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);
336   )
337
338   if(resultCode.isOk() == false) {
339     Exception ldapex(this, resultCode = error, ANNA_FILE_LOCATION);
340     ldapex.trace();
341     response->setResultCode(resultCode);
342     unbindAfterDone = true;
343   } else
344     a_state = State::Bound;
345
346   try {
347     eventResponse(*response);
348   } catch(RuntimeException& ex) {
349     ex.trace();
350   }
351
352   response_erase(response);
353
354   if(unbindAfterDone == true)
355     unbind();
356 }
357
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);
366   BerValue name;
367   Response* response = response_find(idMessage);
368   LOGDEBUG(
369     string msg("ldap::Session::receiveEntry | ");
370     msg += response->asString();
371     Logger::debug(msg, ANNA_FILE_LOCATION);
372   );
373   ResultCode resultCode(ldap_get_dn_ber(handle, hmessage, &ber, &name));
374
375   if(resultCode.isOk() == false) {
376     Exception ldapex(this, resultCode , ANNA_FILE_LOCATION);
377     ldapex.trace();
378     response->setResultCode(resultCode);
379     eventIntermediateResponseError(*response);
380     return;
381   }
382
383   BerValue* values;
384   string value;
385   Attribute* attribute;
386   response->setName(value.assign(name.bv_val, name.bv_len));
387   LOGDEBUG(
388     string msg("ldap::Session::receiveEntry | DN: ");
389     msg += value;
390     Logger::debug(msg, ANNA_FILE_LOCATION);
391   );
392
393   while(ldap_get_attribute_ber(handle, hmessage, ber, &name, &values) == LDAP_SUCCESS) {
394     if(name.bv_val == NULL)                             // (1)
395       break;
396
397     attribute = response->createAttribute(value.assign(name.bv_val, name.bv_len));
398     LOGDEBUG(
399       string msg("ldap::Session::receiveEntry | Attribute: ");
400       msg += value;
401       Logger::debug(msg, ANNA_FILE_LOCATION);
402     );
403
404     if(values) {
405       for(int i = 0; values [i].bv_val != NULL; i ++) {
406         attribute->add(value.assign(values [i].bv_val, values [i].bv_len));
407         LOGDEBUG(
408           string msg("ldap::Session::receiveEntry | Value: ");
409           msg += value;
410           Logger::debug(msg, ANNA_FILE_LOCATION);
411         );
412       }
413
414       ber_memfree(values);
415     }
416   }
417
418   ber_free(ber, 0);
419 }
420
421 void Session::receiveReference(const IdMessage idMessage, Session::HandleMessage _hmessage)
422 throw(RuntimeException) {
423   LDAP* handle = (LDAP*) a_ldap;
424   LDAPMessage* hmessage = (LDAPMessage*) _hmessage;
425   char **values(NULL);
426   Response* response = response_find(idMessage);
427   LOGDEBUG(
428     string msg("ldap::Session::receiveReference | ");
429     msg += response->asString();
430     Logger::debug(msg, ANNA_FILE_LOCATION);
431   );
432   ResultCode resultCode(ldap_parse_reference(handle, hmessage, &values, NULL, 0));
433
434   if(resultCode.isOk() == false) {
435     Exception ldapex(this, resultCode , ANNA_FILE_LOCATION);
436     ldapex.trace();
437     response->setResultCode(resultCode);
438     eventIntermediateResponseError(*response);
439     return;
440   }
441
442   if(values != NULL) {
443     for(int i = 0; values [i] != NULL; i ++)
444       response->createReferral(values [i]);
445
446     ber_memvfree((void**) values);
447   }
448 }
449
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);
455   LOGDEBUG(
456     string msg("ldap::Session::receiveResult | ");
457     msg += asString();
458     msg += " | ";
459     msg += response->asString();
460     Logger::debug(msg, ANNA_FILE_LOCATION);
461   );
462   int error = LDAP_SUCCESS;
463   char **values(NULL);
464   ResultCode resultCode;
465   bool isOk = true;
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);
469   LOGDEBUG(
470     string msg("ldap::Session::receiveResult | ");
471     msg += asString();
472     msg += " | ";
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);
477   )
478
479   if(resultCode.isOk() == false) {
480     Exception ldapex(this, resultCode = error, ANNA_FILE_LOCATION);
481     ldapex.trace();
482     response->setResultCode(resultCode);
483   } else if(values != NULL) {
484     for(int i = 0; values [i] != NULL; i ++)
485       response->createReferral(values [i]);
486
487     ldap_value_free(values);
488   }
489
490   try {
491     eventResponse(*response);
492   } catch(RuntimeException& ex) {
493     ex.trace();
494   }
495
496   response_erase(response);
497 }
498
499 //-------------------------------------------------------------------------
500 // Se invoca desde ldap::timer::Prototype::expire
501 //-------------------------------------------------------------------------
502 void Session::expireResponse(ldap::Response* response)
503 throw() {
504   LDAP* handle = (LDAP*) a_ldap;
505   ResultCode resultCode;
506   /*
507    * Si lo que ha caducado es una petición de Bind hay que cerrar la conexión
508    * y liberar los recursos.
509    */
510   bool unbindAfterDone(true);
511
512   if(response->getClassCode() != ClassCode::Bind) {
513     if(response->getRequest()->getOnExpiry() == Request::OnExpiry::Abandon) {
514       resultCode = ldap_abandon(handle, response->getIdMessage());
515
516       if(resultCode.isOk() == false) {
517         Exception ldapex(this, resultCode, ANNA_FILE_LOCATION);
518         ldapex.trace();
519       }
520     }
521
522     unbindAfterDone = false;
523   }
524
525   response->setResultCode(resultCode = LDAP_TIMEOUT);
526
527   try {
528     eventResponse(*response);
529   } catch(RuntimeException& ex) {
530     ex.trace();
531   }
532
533   response_erase(response);
534
535   if(unbindAfterDone == true)
536     unbind();
537 }
538
539 void Session::response_add(Response* response)
540 throw() {
541   a_responses.add(response);
542   response->setSession(this);
543
544   try {
545     response->activateTimer();
546   } catch(anna::Exception& ex) {
547     ex.trace();
548   }
549 }
550
551 void Session::response_erase(Response* response)
552 throw() {
553   a_responses.erase(response);
554   Response::release(response);
555 }
556
557 Response* Session::response_find(const IdMessage idMessage)
558 throw(RuntimeException) {
559   ldap::Response* result = a_responses.find(idMessage);
560
561   if(result == NULL) {
562     string msg(asString());
563     msg += functions::asText(" | IdMessage: ", idMessage);
564     msg += " | Message not registered at session";
565     throw RuntimeException(msg, ANNA_FILE_LOCATION);
566   }
567
568   return result;
569 }
570
571 std::string Session::asString() const
572 throw() {
573   string result("ldap::Session { ");
574   result += comm::Handler::asString();
575   result += " | State: ";
576   result += asText(a_state);
577   result += " | URL: ";
578   result += a_url;
579   result += " | User: ";
580   result += Request::asText(a_user);
581
582   if(a_externalID != -1)
583     result += functions::asText(" | ExternalID: ", a_externalID);
584
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);
588     result += " }";
589   }
590
591   return result += " }";
592 }
593
594 xml::Node* Session::asXML(xml::Node* parent) const
595 throw() {
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);
602
603   if(a_externalID != -1)
604     result->createChild("ExternalID")->createAttribute("Value", a_externalID);
605
606   if(hasNetworkTimeout())
607     result->createChild("NetworkTimeout")->createAttribute("Value", getNetworkTimeout());
608
609   xml::Node* requests = result->createChild("ldap.Requests");
610   const Response* response;
611   const Request* request;
612
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);
616   }
617
618   return result;
619 }
620
621 int Session::getDangerousFileDescriptor() const
622 throw(RuntimeException) {
623   ResultCode resultCode;
624   int result = -1;
625   resultCode = ldap_get_option((LDAP*) a_ldap, LDAP_OPT_DESC, &result);
626
627   if(resultCode.isOk() == false)
628     throw RuntimeException(ldap::Exception(this, resultCode, ANNA_FILE_LOCATION));
629
630   LOGWARNING(
631     string msg("Session::getDangerousFileDescriptor | Result: ");
632     msg += functions::asString(result);
633     Logger::warning(msg, ANNA_FILE_LOCATION);
634   );
635   return result;
636 }
637
638 const char* Session::asText(const State::_v state)
639 throw() {
640   static const char* states [] = { "Closed", "WaitingBind", "Bound" };
641   return states [state];
642 }
643
644 IdMessage Session::SortById::value(const Response* response)
645 throw() {
646   return response->getIdMessage();
647 }