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