]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ldap.cpp
Merge pull request #1041 from SaberUK/master+ipv6-nameserver
[user/henk/code/inspircd.git] / src / modules / extra / m_ldap.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2013-2014 Adam <Adam@anope.org>
5  *   Copyright (C) 2003-2014 Anope Team <team@anope.org>
6  *
7  * This file is part of InspIRCd.  InspIRCd is free software: you can
8  * redistribute it and/or modify it under the terms of the GNU General Public
9  * License as published by the Free Software Foundation, version 2.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "inspircd.h"
21 #include "modules/ldap.h"
22
23 #include <ldap.h>
24
25 #ifdef _WIN32
26 # pragma comment(lib, "libldap_r.lib")
27 # pragma comment(lib, "liblber.lib")
28 #endif
29
30 /* $LinkerFlags: -lldap_r */
31
32 class LDAPService : public LDAPProvider, public SocketThread
33 {
34         LDAP* con;
35         reference<ConfigTag> config;
36         time_t last_connect;
37         int searchscope;
38         time_t timeout;
39         time_t last_timeout_check;
40
41         LDAPMod** BuildMods(const LDAPMods& attributes)
42         {
43                 LDAPMod** mods = new LDAPMod*[attributes.size() + 1];
44                 memset(mods, 0, sizeof(LDAPMod*) * (attributes.size() + 1));
45                 for (unsigned int x = 0; x < attributes.size(); ++x)
46                 {
47                         const LDAPModification& l = attributes[x];
48                         LDAPMod* mod = new LDAPMod;
49                         mods[x] = mod;
50
51                         if (l.op == LDAPModification::LDAP_ADD)
52                                 mod->mod_op = LDAP_MOD_ADD;
53                         else if (l.op == LDAPModification::LDAP_DEL)
54                                 mod->mod_op = LDAP_MOD_DELETE;
55                         else if (l.op == LDAPModification::LDAP_REPLACE)
56                                 mod->mod_op = LDAP_MOD_REPLACE;
57                         else if (l.op != 0)
58                         {
59                                 FreeMods(mods);
60                                 throw LDAPException("Unknown LDAP operation");
61                         }
62                         mod->mod_type = strdup(l.name.c_str());
63                         mod->mod_values = new char*[l.values.size() + 1];
64                         memset(mod->mod_values, 0, sizeof(char*) * (l.values.size() + 1));
65                         for (unsigned int j = 0, c = 0; j < l.values.size(); ++j)
66                                 if (!l.values[j].empty())
67                                         mod->mod_values[c++] = strdup(l.values[j].c_str());
68                 }
69                 return mods;
70         }
71
72         void FreeMods(LDAPMod** mods)
73         {
74                 for (unsigned int i = 0; mods[i] != NULL; ++i)
75                 {
76                         LDAPMod* mod = mods[i];
77                         if (mod->mod_type != NULL)
78                                 free(mod->mod_type);
79                         if (mod->mod_values != NULL)
80                         {
81                                 for (unsigned int j = 0; mod->mod_values[j] != NULL; ++j)
82                                         free(mod->mod_values[j]);
83                                 delete[] mod->mod_values;
84                         }
85                 }
86                 delete[] mods;
87         }
88
89         void Reconnect()
90         {
91                 // Only try one connect a minute. It is an expensive blocking operation
92                 if (last_connect > ServerInstance->Time() - 60)
93                         throw LDAPException("Unable to connect to LDAP service " + this->name + ": reconnecting too fast");
94                 last_connect = ServerInstance->Time();
95
96                 ldap_unbind_ext(this->con, NULL, NULL);
97                 Connect();
98         }
99
100         void SaveInterface(LDAPInterface* i, LDAPQuery msgid)
101         {
102                 if (i != NULL)
103                 {
104                         this->LockQueue();
105                         this->queries[msgid] = std::make_pair(ServerInstance->Time(), i);
106                         this->UnlockQueueWakeup();
107                 }
108         }
109
110         void Timeout()
111         {
112                 if (last_timeout_check == ServerInstance->Time())
113                         return;
114                 last_timeout_check = ServerInstance->Time();
115
116                 for (query_queue::iterator it = this->queries.begin(); it != this->queries.end(); )
117                 {
118                         LDAPQuery msgid = it->first;
119                         time_t created = it->second.first;
120                         LDAPInterface* i = it->second.second;
121                         ++it;
122
123                         if (ServerInstance->Time() > created + timeout)
124                         {
125                                 LDAPResult* ldap_result = new LDAPResult();
126                                 ldap_result->id = msgid;
127                                 ldap_result->error = "Query timed out";
128
129                                 this->queries.erase(msgid);
130                                 this->results.push_back(std::make_pair(i, ldap_result));
131
132                                 this->NotifyParent();
133                         }
134                 }
135         }
136
137  public:
138         typedef std::map<LDAPQuery, std::pair<time_t, LDAPInterface*> > query_queue;
139         typedef std::vector<std::pair<LDAPInterface*, LDAPResult*> > result_queue;
140         query_queue queries;
141         result_queue results;
142
143         LDAPService(Module* c, ConfigTag* tag)
144                 : LDAPProvider(c, "LDAP/" + tag->getString("id"))
145                 , con(NULL), config(tag), last_connect(0), last_timeout_check(0)
146         {
147                 std::string scope = config->getString("searchscope");
148                 if (scope == "base")
149                         searchscope = LDAP_SCOPE_BASE;
150                 else if (scope == "onelevel")
151                         searchscope = LDAP_SCOPE_ONELEVEL;
152                 else
153                         searchscope = LDAP_SCOPE_SUBTREE;
154                 timeout = config->getInt("timeout", 5);
155
156                 Connect();
157         }
158
159         ~LDAPService()
160         {
161                 this->LockQueue();
162
163                 for (query_queue::iterator i = this->queries.begin(); i != this->queries.end(); ++i)
164                 {
165                         LDAPQuery msgid = i->first;
166                         LDAPInterface* inter = i->second.second;
167
168                         ldap_abandon_ext(this->con, msgid, NULL, NULL);
169
170                         if (inter)
171                         {
172                                 LDAPResult r;
173                                 r.error = "LDAP Interface is going away";
174                                 inter->OnError(r);
175                         }
176                 }
177                 this->queries.clear();
178
179                 for (result_queue::iterator i = this->results.begin(); i != this->results.end(); ++i)
180                 {
181                         LDAPInterface* inter = i->first;
182                         LDAPResult* r = i->second;
183
184                         r->error = "LDAP Interface is going away";
185                         if (inter)
186                                 inter->OnError(*r);
187                 }
188                 this->results.clear();
189
190                 this->UnlockQueue();
191
192                 ldap_unbind_ext(this->con, NULL, NULL);
193         }
194
195         void Connect()
196         {
197                 std::string server = config->getString("server");
198                 int i = ldap_initialize(&this->con, server.c_str());
199                 if (i != LDAP_SUCCESS)
200                         throw LDAPException("Unable to connect to LDAP service " + this->name + ": " + ldap_err2string(i));
201
202                 const int version = LDAP_VERSION3;
203                 i = ldap_set_option(this->con, LDAP_OPT_PROTOCOL_VERSION, &version);
204                 if (i != LDAP_OPT_SUCCESS)
205                 {
206                         ldap_unbind_ext(this->con, NULL, NULL);
207                         this->con = NULL;
208                         throw LDAPException("Unable to set protocol version for " + this->name + ": " + ldap_err2string(i));
209                 }
210
211                 const struct timeval tv = { 0, 0 };
212                 i = ldap_set_option(this->con, LDAP_OPT_NETWORK_TIMEOUT, &tv);
213                 if (i != LDAP_OPT_SUCCESS)
214                 {
215                         ldap_unbind_ext(this->con, NULL, NULL);
216                         this->con = NULL;
217                         throw LDAPException("Unable to set timeout for " + this->name + ": " + ldap_err2string(i));
218                 }
219         }
220
221         LDAPQuery BindAsManager(LDAPInterface* i) CXX11_OVERRIDE
222         {
223                 std::string binddn = config->getString("binddn");
224                 std::string bindauth = config->getString("bindauth");
225                 return this->Bind(i, binddn, bindauth);
226         }
227
228         LDAPQuery Bind(LDAPInterface* i, const std::string& who, const std::string& pass) CXX11_OVERRIDE
229         {
230                 berval cred;
231                 cred.bv_val = strdup(pass.c_str());
232                 cred.bv_len = pass.length();
233
234                 LDAPQuery msgid;
235                 int ret = ldap_sasl_bind(con, who.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, &msgid);
236                 free(cred.bv_val);
237                 if (ret != LDAP_SUCCESS)
238                 {
239                         if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT)
240                         {
241                                 this->Reconnect();
242                                 return this->Bind(i, who, pass);
243                         }
244                         else
245                                 throw LDAPException(ldap_err2string(ret));
246                 }
247
248                 SaveInterface(i, msgid);
249                 return msgid;
250         }
251
252         LDAPQuery Search(LDAPInterface* i, const std::string& base, const std::string& filter) CXX11_OVERRIDE
253         {
254                 if (i == NULL)
255                         throw LDAPException("No interface");
256
257                 LDAPQuery msgid;
258                 int ret = ldap_search_ext(this->con, base.c_str(), searchscope, filter.c_str(), NULL, 0, NULL, NULL, NULL, 0, &msgid);
259                 if (ret != LDAP_SUCCESS)
260                 {
261                         if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT)
262                         {
263                                 this->Reconnect();
264                                 return this->Search(i, base, filter);
265                         }
266                         else
267                                 throw LDAPException(ldap_err2string(ret));
268                 }
269
270                 SaveInterface(i, msgid);
271                 return msgid;
272         }
273
274         LDAPQuery Add(LDAPInterface* i, const std::string& dn, LDAPMods& attributes) CXX11_OVERRIDE
275         {
276                 LDAPMod** mods = this->BuildMods(attributes);
277                 LDAPQuery msgid;
278                 int ret = ldap_add_ext(this->con, dn.c_str(), mods, NULL, NULL, &msgid);
279                 this->FreeMods(mods);
280
281                 if (ret != LDAP_SUCCESS)
282                 {
283                         if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT)
284                         {
285                                 this->Reconnect();
286                                 return this->Add(i, dn, attributes);
287                         }
288                         else
289                                 throw LDAPException(ldap_err2string(ret));
290                 }
291
292                 SaveInterface(i, msgid);
293                 return msgid;
294         }
295
296         LDAPQuery Del(LDAPInterface* i, const std::string& dn) CXX11_OVERRIDE
297         {
298                 LDAPQuery msgid;
299                 int ret = ldap_delete_ext(this->con, dn.c_str(), NULL, NULL, &msgid);
300
301                 if (ret != LDAP_SUCCESS)
302                 {
303                         if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT)
304                         {
305                                 this->Reconnect();
306                                 return this->Del(i, dn);
307                         }
308                         else
309                                 throw LDAPException(ldap_err2string(ret));
310                 }
311
312                 SaveInterface(i, msgid);
313                 return msgid;
314         }
315
316         LDAPQuery Modify(LDAPInterface* i, const std::string& base, LDAPMods& attributes) CXX11_OVERRIDE
317         {
318                 LDAPMod** mods = this->BuildMods(attributes);
319                 LDAPQuery msgid;
320                 int ret = ldap_modify_ext(this->con, base.c_str(), mods, NULL, NULL, &msgid);
321                 this->FreeMods(mods);
322
323                 if (ret != LDAP_SUCCESS)
324                 {
325                         if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT)
326                         {
327                                 this->Reconnect();
328                                 return this->Modify(i, base, attributes);
329                         }
330                         else
331                                 throw LDAPException(ldap_err2string(ret));
332                 }
333
334                 SaveInterface(i, msgid);
335                 return msgid;
336         }
337
338         LDAPQuery Compare(LDAPInterface* i, const std::string& dn, const std::string& attr, const std::string& val) CXX11_OVERRIDE
339         {
340                 berval cred;
341                 cred.bv_val = strdup(val.c_str());
342                 cred.bv_len = val.length();
343
344                 LDAPQuery msgid;
345                 int ret = ldap_compare_ext(con, dn.c_str(), attr.c_str(), &cred, NULL, NULL, &msgid);
346                 free(cred.bv_val);
347
348                 if (ret != LDAP_SUCCESS)
349                 {
350                         if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT)
351                         {
352                                 this->Reconnect();
353                                 return this->Compare(i, dn, attr, val);
354                         }
355                         else
356                                 throw LDAPException(ldap_err2string(ret));
357                 }
358
359                 SaveInterface(i, msgid);
360                 return msgid;
361         }
362
363         void Run() CXX11_OVERRIDE
364         {
365                 while (!this->GetExitFlag())
366                 {
367                         this->LockQueue();
368                         if (this->queries.empty())
369                         {
370                                 this->WaitForQueue();
371                                 this->UnlockQueue();
372                                 continue;
373                         }
374                         this->Timeout();
375                         this->UnlockQueue();
376
377                         struct timeval tv = { 1, 0 };
378                         LDAPMessage* result;
379                         int rtype = ldap_result(this->con, LDAP_RES_ANY, 1, &tv, &result);
380                         if (rtype <= 0 || this->GetExitFlag())
381                                 continue;
382
383                         int cur_id = ldap_msgid(result);
384
385                         this->LockQueue();
386
387                         query_queue::iterator it = this->queries.find(cur_id);
388                         if (it == this->queries.end())
389                         {
390                                 this->UnlockQueue();
391                                 ldap_msgfree(result);
392                                 continue;
393                         }
394                         LDAPInterface* i = it->second.second;
395                         this->queries.erase(it);
396
397                         this->UnlockQueue();
398
399                         LDAPResult* ldap_result = new LDAPResult();
400                         ldap_result->id = cur_id;
401
402                         for (LDAPMessage* cur = ldap_first_message(this->con, result); cur; cur = ldap_next_message(this->con, cur))
403                         {
404                                 int cur_type = ldap_msgtype(cur);
405
406                                 LDAPAttributes attributes;
407
408                                 {
409                                         char* dn = ldap_get_dn(this->con, cur);
410                                         if (dn != NULL)
411                                         {
412                                                 attributes["dn"].push_back(dn);
413                                                 ldap_memfree(dn);
414                                         }
415                                 }
416
417                                 switch (cur_type)
418                                 {
419                                         case LDAP_RES_BIND:
420                                                 ldap_result->type = LDAPResult::QUERY_BIND;
421                                                 break;
422                                         case LDAP_RES_SEARCH_ENTRY:
423                                                 ldap_result->type = LDAPResult::QUERY_SEARCH;
424                                                 break;
425                                         case LDAP_RES_ADD:
426                                                 ldap_result->type = LDAPResult::QUERY_ADD;
427                                                 break;
428                                         case LDAP_RES_DELETE:
429                                                 ldap_result->type = LDAPResult::QUERY_DELETE;
430                                                 break;
431                                         case LDAP_RES_MODIFY:
432                                                 ldap_result->type = LDAPResult::QUERY_MODIFY;
433                                                 break;
434                                         case LDAP_RES_SEARCH_RESULT:
435                                                 // If we get here and ldap_result->type is LDAPResult::QUERY_UNKNOWN
436                                                 // then the result set is empty
437                                                 ldap_result->type = LDAPResult::QUERY_SEARCH;
438                                                 break;
439                                         case LDAP_RES_COMPARE:
440                                                 ldap_result->type = LDAPResult::QUERY_COMPARE;
441                                                 break;
442                                         default:
443                                                 continue;
444                                 }
445
446                                 switch (cur_type)
447                                 {
448                                         case LDAP_RES_SEARCH_ENTRY:
449                                         {
450                                                 BerElement* ber = NULL;
451                                                 for (char* attr = ldap_first_attribute(this->con, cur, &ber); attr; attr = ldap_next_attribute(this->con, cur, ber))
452                                                 {
453                                                         berval** vals = ldap_get_values_len(this->con, cur, attr);
454                                                         int count = ldap_count_values_len(vals);
455
456                                                         std::vector<std::string> attrs;
457                                                         for (int j = 0; j < count; ++j)
458                                                                 attrs.push_back(vals[j]->bv_val);
459                                                         attributes[attr] = attrs;
460
461                                                         ldap_value_free_len(vals);
462                                                         ldap_memfree(attr);
463                                                 }
464                                                 if (ber != NULL)
465                                                         ber_free(ber, 0);
466
467                                                 break;
468                                         }
469                                         case LDAP_RES_BIND:
470                                         case LDAP_RES_ADD:
471                                         case LDAP_RES_DELETE:
472                                         case LDAP_RES_MODIFY:
473                                         case LDAP_RES_COMPARE:
474                                         {
475                                                 int errcode = -1;
476                                                 int parse_result = ldap_parse_result(this->con, cur, &errcode, NULL, NULL, NULL, NULL, 0);
477                                                 if (parse_result != LDAP_SUCCESS)
478                                                 {
479                                                         ldap_result->error = ldap_err2string(parse_result);
480                                                 }
481                                                 else
482                                                 {
483                                                         if (cur_type == LDAP_RES_COMPARE)
484                                                         {
485                                                                 if (errcode != LDAP_COMPARE_TRUE)
486                                                                         ldap_result->error = ldap_err2string(errcode);
487                                                         }
488                                                         else if (errcode != LDAP_SUCCESS)
489                                                                 ldap_result->error = ldap_err2string(errcode);
490                                                 }
491                                                 break;
492                                         }
493                                         default:
494                                                 continue;
495                                 }
496
497                                 ldap_result->messages.push_back(attributes);
498                         }
499
500                         ldap_msgfree(result);
501
502                         this->LockQueue();
503                         this->results.push_back(std::make_pair(i, ldap_result));
504                         this->UnlockQueueWakeup();
505
506                         this->NotifyParent();
507                 }
508         }
509
510         void OnNotify() CXX11_OVERRIDE
511         {
512                 LDAPService::result_queue r;
513
514                 this->LockQueue();
515                 this->results.swap(r);
516                 this->UnlockQueue();
517
518                 for (LDAPService::result_queue::iterator i = r.begin(); i != r.end(); ++i)
519                 {
520                         LDAPInterface* li = i->first;
521                         LDAPResult* res = i->second;
522
523                         if (!res->error.empty())
524                                 li->OnError(*res);
525                         else
526                                 li->OnResult(*res);
527
528                         delete res;
529                 }
530         }
531 };
532
533 class ModuleLDAP : public Module
534 {
535         typedef insp::flat_map<std::string, LDAPService*> ServiceMap;
536         ServiceMap LDAPServices;
537
538  public:
539         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
540         {
541                 ServiceMap conns;
542
543                 ConfigTagList tags = ServerInstance->Config->ConfTags("database");
544                 for (ConfigIter i = tags.first; i != tags.second; i++)
545                 {
546                         const reference<ConfigTag>& tag = i->second;
547
548                         if (tag->getString("module") != "ldap")
549                                 continue;
550
551                         std::string id = tag->getString("id");
552
553                         ServiceMap::iterator curr = LDAPServices.find(id);
554                         if (curr == LDAPServices.end())
555                         {
556                                 LDAPService* conn = new LDAPService(this, tag);
557                                 conns[id] = conn;
558
559                                 ServerInstance->Modules->AddService(*conn);
560                                 ServerInstance->Threads.Start(conn);
561                         }
562                         else
563                         {
564                                 conns.insert(*curr);
565                                 LDAPServices.erase(curr);
566                         }
567                 }
568
569                 for (ServiceMap::iterator i = LDAPServices.begin(); i != LDAPServices.end(); ++i)
570                 {
571                         LDAPService* conn = i->second;
572                         ServerInstance->Modules->DelService(*conn);
573                         conn->join();
574                         conn->OnNotify();
575                         delete conn;
576                 }
577
578                 LDAPServices.swap(conns);
579         }
580
581         void OnUnloadModule(Module* m) CXX11_OVERRIDE
582         {
583                 for (ServiceMap::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it)
584                 {
585                         LDAPService* s = it->second;
586                         s->LockQueue();
587                         for (LDAPService::query_queue::iterator it2 = s->queries.begin(); it2 != s->queries.end();)
588                         {
589                                 int msgid = it2->first;
590                                 LDAPInterface* i = it2->second.second;
591                                 ++it2;
592
593                                 if (i->creator == m)
594                                         s->queries.erase(msgid);
595                         }
596                         for (unsigned int i = s->results.size(); i > 0; --i)
597                         {
598                                 LDAPInterface* li = s->results[i - 1].first;
599                                 LDAPResult* r = s->results[i - 1].second;
600
601                                 if (li->creator == m)
602                                 {
603                                         s->results.erase(s->results.begin() + i - 1);
604                                         delete r;
605                                 }
606                         }
607                         s->UnlockQueue();
608                 }
609         }
610
611         ~ModuleLDAP()
612         {
613                 for (ServiceMap::iterator i = LDAPServices.begin(); i != LDAPServices.end(); ++i)
614                 {
615                         LDAPService* conn = i->second;
616                         conn->join();
617                         conn->OnNotify();
618                         delete conn;
619                 }
620         }
621
622         Version GetVersion() CXX11_OVERRIDE
623         {
624                 return Version("LDAP support", VF_VENDOR);
625         }
626 };
627
628 MODULE_INIT(ModuleLDAP)