]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_ldap.cpp
Merge pull request #1222 from SaberUK/master+warnings
[user/henk/code/inspircd.git] / src / modules / extra / m_ldap.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2013-2015 Adam <Adam@anope.org>
5  *   Copyright (C) 2003-2015 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 /// $LinkerFlags: -llber -lldap_r
21 /// $PackageInfo: require_system("ubuntu") libldap2-dev
22
23 #include "inspircd.h"
24 #include "modules/ldap.h"
25
26 // Ignore OpenLDAP deprecation warnings on OS X Yosemite and newer.
27 #if defined __APPLE__
28 # pragma GCC diagnostic ignored "-Wdeprecated-declarations"
29 #endif
30
31 #include <ldap.h>
32
33 #ifdef _WIN32
34 # pragma comment(lib, "libldap_r.lib")
35 # pragma comment(lib, "liblber.lib")
36 #endif
37
38 class LDAPService;
39
40 class LDAPRequest
41 {
42  public:
43         LDAPService* service;
44         LDAPInterface* inter;
45         LDAPMessage* message; /* message returned by ldap_ */
46         LDAPResult* result; /* final result */
47         struct timeval tv;
48         QueryType type;
49
50         LDAPRequest(LDAPService* s, LDAPInterface* i)
51                 : service(s)
52                 , inter(i)
53                 , message(NULL)
54                 , result(NULL)
55         {
56                 type = QUERY_UNKNOWN;
57                 tv.tv_sec = 0;
58                 tv.tv_usec = 100000;
59         }
60
61         virtual ~LDAPRequest()
62         {
63                 delete result;
64                 if (message != NULL)
65                         ldap_msgfree(message);
66         }
67
68         virtual int run() = 0;
69 };
70
71 class LDAPBind : public LDAPRequest
72 {
73         std::string who, pass;
74
75  public:
76         LDAPBind(LDAPService* s, LDAPInterface* i, const std::string& w, const std::string& p)
77                 : LDAPRequest(s, i)
78                 , who(w)
79                 , pass(p)
80         {
81                 type = QUERY_BIND;
82         }
83
84         int run() CXX11_OVERRIDE;
85 };
86
87 class LDAPSearch : public LDAPRequest
88 {
89         std::string base;
90         int searchscope;
91         std::string filter;
92
93  public:
94         LDAPSearch(LDAPService* s, LDAPInterface* i, const std::string& b, int se, const std::string& f)
95                 : LDAPRequest(s, i)
96                 , base(b)
97                 , searchscope(se)
98                 , filter(f)
99         {
100                 type = QUERY_SEARCH;
101         }
102
103         int run() CXX11_OVERRIDE;
104 };
105
106 class LDAPAdd : public LDAPRequest
107 {
108         std::string dn;
109         LDAPMods attributes;
110
111  public:
112         LDAPAdd(LDAPService* s, LDAPInterface* i, const std::string& d, const LDAPMods& attr)
113                 : LDAPRequest(s, i)
114                 , dn(d)
115                 , attributes(attr)
116         {
117                 type = QUERY_ADD;
118         }
119
120         int run() CXX11_OVERRIDE;
121 };
122
123 class LDAPDel : public LDAPRequest
124 {
125         std::string dn;
126
127  public:
128         LDAPDel(LDAPService* s, LDAPInterface* i, const std::string& d)
129                 : LDAPRequest(s, i)
130                 , dn(d)
131         {
132                 type = QUERY_DELETE;
133         }
134
135         int run() CXX11_OVERRIDE;
136 };
137
138 class LDAPModify : public LDAPRequest
139 {
140         std::string base;
141         LDAPMods attributes;
142
143  public:
144         LDAPModify(LDAPService* s, LDAPInterface* i, const std::string& b, const LDAPMods& attr)
145                 : LDAPRequest(s, i)
146                 , base(b)
147                 , attributes(attr)
148         {
149                 type = QUERY_MODIFY;
150         }
151
152         int run() CXX11_OVERRIDE;
153 };
154
155 class LDAPCompare : public LDAPRequest
156 {
157         std::string dn, attr, val;
158
159  public:
160         LDAPCompare(LDAPService* s, LDAPInterface* i, const std::string& d, const std::string& a, const std::string& v)
161                 : LDAPRequest(s, i)
162                 , dn(d)
163                 , attr(a)
164                 , val(v)
165         {
166                 type = QUERY_COMPARE;
167         }
168
169         int run() CXX11_OVERRIDE;
170 };
171
172 class LDAPService : public LDAPProvider, public SocketThread
173 {
174         LDAP* con;
175         reference<ConfigTag> config;
176         time_t last_connect;
177         int searchscope;
178         time_t timeout;
179
180  public:
181         static LDAPMod** BuildMods(const LDAPMods& attributes)
182         {
183                 LDAPMod** mods = new LDAPMod*[attributes.size() + 1];
184                 memset(mods, 0, sizeof(LDAPMod*) * (attributes.size() + 1));
185                 for (unsigned int x = 0; x < attributes.size(); ++x)
186                 {
187                         const LDAPModification& l = attributes[x];
188                         LDAPMod* mod = new LDAPMod;
189                         mods[x] = mod;
190
191                         if (l.op == LDAPModification::LDAP_ADD)
192                                 mod->mod_op = LDAP_MOD_ADD;
193                         else if (l.op == LDAPModification::LDAP_DEL)
194                                 mod->mod_op = LDAP_MOD_DELETE;
195                         else if (l.op == LDAPModification::LDAP_REPLACE)
196                                 mod->mod_op = LDAP_MOD_REPLACE;
197                         else if (l.op != 0)
198                         {
199                                 FreeMods(mods);
200                                 throw LDAPException("Unknown LDAP operation");
201                         }
202                         mod->mod_type = strdup(l.name.c_str());
203                         mod->mod_values = new char*[l.values.size() + 1];
204                         memset(mod->mod_values, 0, sizeof(char*) * (l.values.size() + 1));
205                         for (unsigned int j = 0, c = 0; j < l.values.size(); ++j)
206                                 if (!l.values[j].empty())
207                                         mod->mod_values[c++] = strdup(l.values[j].c_str());
208                 }
209                 return mods;
210         }
211
212         static void FreeMods(LDAPMod** mods)
213         {
214                 for (unsigned int i = 0; mods[i] != NULL; ++i)
215                 {
216                         LDAPMod* mod = mods[i];
217                         if (mod->mod_type != NULL)
218                                 free(mod->mod_type);
219                         if (mod->mod_values != NULL)
220                         {
221                                 for (unsigned int j = 0; mod->mod_values[j] != NULL; ++j)
222                                         free(mod->mod_values[j]);
223                                 delete[] mod->mod_values;
224                         }
225                 }
226                 delete[] mods;
227         }
228
229  private:
230         void Reconnect()
231         {
232                 // Only try one connect a minute. It is an expensive blocking operation
233                 if (last_connect > ServerInstance->Time() - 60)
234                         throw LDAPException("Unable to connect to LDAP service " + this->name + ": reconnecting too fast");
235                 last_connect = ServerInstance->Time();
236
237                 ldap_unbind_ext(this->con, NULL, NULL);
238                 Connect();
239         }
240
241         void QueueRequest(LDAPRequest* r)
242         {
243                 this->LockQueue();
244                 this->queries.push_back(r);
245                 this->UnlockQueueWakeup();
246         }
247
248  public:
249         typedef std::vector<LDAPRequest*> query_queue;
250         query_queue queries, results;
251         Mutex process_mutex; /* held when processing requests not in either queue */
252
253         LDAPService(Module* c, ConfigTag* tag)
254                 : LDAPProvider(c, "LDAP/" + tag->getString("id"))
255                 , con(NULL), config(tag), last_connect(0)
256         {
257                 std::string scope = config->getString("searchscope");
258                 if (scope == "base")
259                         searchscope = LDAP_SCOPE_BASE;
260                 else if (scope == "onelevel")
261                         searchscope = LDAP_SCOPE_ONELEVEL;
262                 else
263                         searchscope = LDAP_SCOPE_SUBTREE;
264                 timeout = config->getInt("timeout", 5);
265
266                 Connect();
267         }
268
269         ~LDAPService()
270         {
271                 this->LockQueue();
272
273                 for (unsigned int i = 0; i < this->queries.size(); ++i)
274                 {
275                         LDAPRequest* req = this->queries[i];
276
277                         /* queries have no results yet */
278                         req->result = new LDAPResult();
279                         req->result->type = req->type;
280                         req->result->error = "LDAP Interface is going away";
281                         req->inter->OnError(*req->result);
282
283                         delete req;
284                 }
285                 this->queries.clear();
286
287                 for (unsigned int i = 0; i < this->results.size(); ++i)
288                 {
289                         LDAPRequest* req = this->results[i];
290
291                         /* even though this may have already finished successfully we return that it didn't */
292                         req->result->error = "LDAP Interface is going away";
293                         req->inter->OnError(*req->result);
294
295                         delete req;
296                 }
297                 this->results.clear();
298
299                 this->UnlockQueue();
300
301                 ldap_unbind_ext(this->con, NULL, NULL);
302         }
303
304         void Connect()
305         {
306                 std::string server = config->getString("server");
307                 int i = ldap_initialize(&this->con, server.c_str());
308                 if (i != LDAP_SUCCESS)
309                         throw LDAPException("Unable to connect to LDAP service " + this->name + ": " + ldap_err2string(i));
310
311                 const int version = LDAP_VERSION3;
312                 i = ldap_set_option(this->con, LDAP_OPT_PROTOCOL_VERSION, &version);
313                 if (i != LDAP_OPT_SUCCESS)
314                 {
315                         ldap_unbind_ext(this->con, NULL, NULL);
316                         this->con = NULL;
317                         throw LDAPException("Unable to set protocol version for " + this->name + ": " + ldap_err2string(i));
318                 }
319
320                 const struct timeval tv = { 0, 0 };
321                 i = ldap_set_option(this->con, LDAP_OPT_NETWORK_TIMEOUT, &tv);
322                 if (i != LDAP_OPT_SUCCESS)
323                 {
324                         ldap_unbind_ext(this->con, NULL, NULL);
325                         this->con = NULL;
326                         throw LDAPException("Unable to set timeout for " + this->name + ": " + ldap_err2string(i));
327                 }
328         }
329
330         void BindAsManager(LDAPInterface* i) CXX11_OVERRIDE
331         {
332                 std::string binddn = config->getString("binddn");
333                 std::string bindauth = config->getString("bindauth");
334                 this->Bind(i, binddn, bindauth);
335         }
336
337         void Bind(LDAPInterface* i, const std::string& who, const std::string& pass) CXX11_OVERRIDE
338         {
339                 LDAPBind* b = new LDAPBind(this, i, who, pass);
340                 QueueRequest(b);
341         }
342
343         void Search(LDAPInterface* i, const std::string& base, const std::string& filter) CXX11_OVERRIDE
344         {
345                 if (i == NULL)
346                         throw LDAPException("No interface");
347
348                 LDAPSearch* s = new LDAPSearch(this, i, base, searchscope, filter);
349                 QueueRequest(s);
350         }
351
352         void Add(LDAPInterface* i, const std::string& dn, LDAPMods& attributes) CXX11_OVERRIDE
353         {
354                 LDAPAdd* add = new LDAPAdd(this, i, dn, attributes);
355                 QueueRequest(add);
356         }
357
358         void Del(LDAPInterface* i, const std::string& dn) CXX11_OVERRIDE
359         {
360                 LDAPDel* del = new LDAPDel(this, i, dn);
361                 QueueRequest(del);
362         }
363
364         void Modify(LDAPInterface* i, const std::string& base, LDAPMods& attributes) CXX11_OVERRIDE
365         {
366                 LDAPModify* mod = new LDAPModify(this, i, base, attributes);
367                 QueueRequest(mod);
368         }
369
370         void Compare(LDAPInterface* i, const std::string& dn, const std::string& attr, const std::string& val) CXX11_OVERRIDE
371         {
372                 LDAPCompare* comp = new LDAPCompare(this, i, dn, attr, val);
373                 QueueRequest(comp);
374         }
375
376  private:
377         void BuildReply(int res, LDAPRequest* req)
378         {
379                 LDAPResult* ldap_result = req->result = new LDAPResult();
380                 req->result->type = req->type;
381
382                 if (res != LDAP_SUCCESS)
383                 {
384                         ldap_result->error = ldap_err2string(res);
385                         return;
386                 }
387
388                 if (req->message == NULL)
389                 {
390                         return;
391                 }
392
393                 /* a search result */
394
395                 for (LDAPMessage* cur = ldap_first_message(this->con, req->message); cur; cur = ldap_next_message(this->con, cur))
396                 {
397                         LDAPAttributes attributes;
398
399                         char* dn = ldap_get_dn(this->con, cur);
400                         if (dn != NULL)
401                         {
402                                 attributes["dn"].push_back(dn);
403                                 ldap_memfree(dn);
404                                 dn = NULL;
405                         }
406
407                         BerElement* ber = NULL;
408
409                         for (char* attr = ldap_first_attribute(this->con, cur, &ber); attr; attr = ldap_next_attribute(this->con, cur, ber))
410                         {
411                                 berval** vals = ldap_get_values_len(this->con, cur, attr);
412                                 int count = ldap_count_values_len(vals);
413
414                                 std::vector<std::string> attrs;
415                                 for (int j = 0; j < count; ++j)
416                                         attrs.push_back(vals[j]->bv_val);
417                                 attributes[attr] = attrs;
418
419                                 ldap_value_free_len(vals);
420                                 ldap_memfree(attr);
421                         }
422                         if (ber != NULL)
423                                 ber_free(ber, 0);
424
425                         ldap_result->messages.push_back(attributes);
426                 }
427         }
428
429         void SendRequests()
430         {
431                 process_mutex.Lock();
432
433                 query_queue q;
434                 this->LockQueue();
435                 queries.swap(q);
436                 this->UnlockQueue();
437
438                 if (q.empty())
439                 {
440                         process_mutex.Unlock();
441                         return;
442                 }
443
444                 for (unsigned int i = 0; i < q.size(); ++i)
445                 {
446                         LDAPRequest* req = q[i];
447                         int ret = req->run();
448
449                         if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT)
450                         {
451                                 /* try again */
452                                 try
453                                 {
454                                         Reconnect();
455                                 }
456                                 catch (const LDAPException &)
457                                 {
458                                 }
459
460                                 ret = req->run();
461                         }
462
463                         BuildReply(ret, req);
464
465                         this->LockQueue();
466                         this->results.push_back(req);
467                         this->UnlockQueue();
468                 }
469
470                 this->NotifyParent();
471
472                 process_mutex.Unlock();
473         }
474
475  public:
476         void Run() CXX11_OVERRIDE
477         {
478                 while (!this->GetExitFlag())
479                 {
480                         this->LockQueue();
481                         if (this->queries.empty())
482                                 this->WaitForQueue();
483                         this->UnlockQueue();
484
485                         SendRequests();
486                 }
487         }
488
489         void OnNotify() CXX11_OVERRIDE
490         {
491                 query_queue r;
492
493                 this->LockQueue();
494                 this->results.swap(r);
495                 this->UnlockQueue();
496
497                 for (unsigned int i = 0; i < r.size(); ++i)
498                 {
499                         LDAPRequest* req = r[i];
500                         LDAPInterface* li = req->inter;
501                         LDAPResult* res = req->result;
502
503                         if (!res->error.empty())
504                                 li->OnError(*res);
505                         else
506                                 li->OnResult(*res);
507
508                         delete req;
509                 }
510         }
511
512         LDAP* GetConnection()
513         {
514                 return con;
515         }
516 };
517
518 class ModuleLDAP : public Module
519 {
520         typedef insp::flat_map<std::string, LDAPService*> ServiceMap;
521         ServiceMap LDAPServices;
522
523  public:
524         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
525         {
526                 ServiceMap conns;
527
528                 ConfigTagList tags = ServerInstance->Config->ConfTags("database");
529                 for (ConfigIter i = tags.first; i != tags.second; i++)
530                 {
531                         const reference<ConfigTag>& tag = i->second;
532
533                         if (tag->getString("module") != "ldap")
534                                 continue;
535
536                         std::string id = tag->getString("id");
537
538                         ServiceMap::iterator curr = LDAPServices.find(id);
539                         if (curr == LDAPServices.end())
540                         {
541                                 LDAPService* conn = new LDAPService(this, tag);
542                                 conns[id] = conn;
543
544                                 ServerInstance->Modules->AddService(*conn);
545                                 ServerInstance->Threads.Start(conn);
546                         }
547                         else
548                         {
549                                 conns.insert(*curr);
550                                 LDAPServices.erase(curr);
551                         }
552                 }
553
554                 for (ServiceMap::iterator i = LDAPServices.begin(); i != LDAPServices.end(); ++i)
555                 {
556                         LDAPService* conn = i->second;
557                         ServerInstance->Modules->DelService(*conn);
558                         conn->join();
559                         conn->OnNotify();
560                         delete conn;
561                 }
562
563                 LDAPServices.swap(conns);
564         }
565
566         void OnUnloadModule(Module* m) CXX11_OVERRIDE
567         {
568                 for (ServiceMap::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it)
569                 {
570                         LDAPService* s = it->second;
571
572                         s->process_mutex.Lock();
573                         s->LockQueue();
574
575                         for (unsigned int i = s->queries.size(); i > 0; --i)
576                         {
577                                 LDAPRequest* req = s->queries[i - 1];
578                                 LDAPInterface* li = req->inter;
579
580                                 if (li->creator == m)
581                                 {
582                                         s->queries.erase(s->queries.begin() + i - 1);
583                                         delete req;
584                                 }
585                         }
586
587                         for (unsigned int i = s->results.size(); i > 0; --i)
588                         {
589                                 LDAPRequest* req = s->results[i - 1];
590                                 LDAPInterface* li = req->inter;
591
592                                 if (li->creator == m)
593                                 {
594                                         s->results.erase(s->results.begin() + i - 1);
595                                         delete req;
596                                 }
597                         }
598
599                         s->UnlockQueue();
600                         s->process_mutex.Unlock();
601                 }
602         }
603
604         ~ModuleLDAP()
605         {
606                 for (ServiceMap::iterator i = LDAPServices.begin(); i != LDAPServices.end(); ++i)
607                 {
608                         LDAPService* conn = i->second;
609                         conn->join();
610                         conn->OnNotify();
611                         delete conn;
612                 }
613         }
614
615         Version GetVersion() CXX11_OVERRIDE
616         {
617                 return Version("LDAP support", VF_VENDOR);
618         }
619 };
620
621 int LDAPBind::run()
622 {
623         berval cred;
624         cred.bv_val = strdup(pass.c_str());
625         cred.bv_len = pass.length();
626
627         int i = ldap_sasl_bind_s(service->GetConnection(), who.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
628
629         free(cred.bv_val);
630
631         return i;
632 }
633
634 int LDAPSearch::run()
635 {
636         return ldap_search_ext_s(service->GetConnection(), base.c_str(), searchscope, filter.c_str(), NULL, 0, NULL, NULL, &tv, 0, &message);
637 }
638
639 int LDAPAdd::run()
640 {
641         LDAPMod** mods = LDAPService::BuildMods(attributes);
642         int i = ldap_add_ext_s(service->GetConnection(), dn.c_str(), mods, NULL, NULL);
643         LDAPService::FreeMods(mods);
644         return i;
645 }
646
647 int LDAPDel::run()
648 {
649         return ldap_delete_ext_s(service->GetConnection(), dn.c_str(), NULL, NULL);
650 }
651
652 int LDAPModify::run()
653 {
654         LDAPMod** mods = LDAPService::BuildMods(attributes);
655         int i = ldap_modify_ext_s(service->GetConnection(), base.c_str(), mods, NULL, NULL);
656         LDAPService::FreeMods(mods);
657         return i;
658 }
659
660 int LDAPCompare::run()
661 {
662         berval cred;
663         cred.bv_val = strdup(val.c_str());
664         cred.bv_len = val.length();
665
666         int ret = ldap_compare_ext_s(service->GetConnection(), dn.c_str(), attr.c_str(), &cred, NULL, NULL);
667
668         free(cred.bv_val);
669
670         return ret;
671
672 }
673
674 MODULE_INIT(ModuleLDAP)