f684344c459bf497ec7150ba5e25a50e7901dccf
[citadel.git] / citadel / modules / ldap / serv_ldap.c
1 /*
2  * $Id$
3  *
4  * A module which implements the LDAP connector for Citadel.
5  *
6  */
7
8 #include "sysdep.h"
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13 #include <signal.h>
14 #include <pwd.h>
15 #include <errno.h>
16 #include <sys/types.h>
17
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
20 # include <time.h>
21 #else
22 # if HAVE_SYS_TIME_H
23 #  include <sys/time.h>
24 # else
25 #  include <time.h>
26 # endif
27 #endif
28
29 #include <sys/wait.h>
30 #include <string.h>
31 #include <limits.h>
32 #include "citadel.h"
33 #include "server.h"
34 #include "citserver.h"
35 #include "support.h"
36 #include "config.h"
37 #include "room_ops.h"
38 #include "policy.h"
39 #include "database.h"
40 #include "serv_ldap.h"
41 #include "tools.h"
42
43
44 #include "ctdl_module.h"
45
46
47
48 #ifdef HAVE_LDAP
49
50 #define LDAP_DEPRECATED 1       /* to stop warnings with newer libraries */
51
52 #include <ldap.h>
53
54 LDAP *dirserver = NULL;
55 int ldap_time_disconnect = 0;
56
57
58
59 /* There is a forward referance so.... */
60 int delete_from_ldap(char *cn, char *ou, void **object);
61
62
63 /*
64  * LDAP connector cleanup function
65  */
66 void serv_ldap_cleanup(void)
67 {
68         if (dirserver) {
69                 CtdlLogPrintf(CTDL_INFO,
70                         "LDAP: Unbinding from directory server\n");
71                 ldap_unbind(dirserver);
72         }
73         dirserver = NULL;
74         ldap_time_disconnect = 0;
75 }
76
77
78 /*
79  * connect_to_ldap
80  *
81  * BIG FAT WARNING
82  * Make sure this function is only called from within a begin_critical_section(S_LDAP)
83  * If you don't things will break!!!!!.
84  */
85
86
87 int connect_to_ldap(void)
88 {
89         int i;
90         int ldap_version = 3;
91
92         if (dirserver) {        // Already connected
93                 ldap_time_disconnect = 1 ;      // reset the timer.
94                 return 0;
95         }
96
97         CtdlLogPrintf(CTDL_INFO, "LDAP: Connecting to LDAP server %s:%d...\n",
98                 config.c_ldap_host, config.c_ldap_port);
99
100         dirserver = ldap_init(config.c_ldap_host, config.c_ldap_port);
101         if (dirserver == NULL) {
102                 CtdlLogPrintf(CTDL_CRIT,
103                         "LDAP: Could not connect to %s:%d : %s\n",
104                         config.c_ldap_host, config.c_ldap_port,
105                         strerror(errno));
106                 CtdlAideMessage(strerror(errno),
107                              "LDAP: Could not connect to server.");
108                 return -1;
109         }
110
111         ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION,
112                         &ldap_version);
113
114         CtdlLogPrintf(CTDL_INFO, "LDAP: Binding to %s\n", config.c_ldap_bind_dn);
115
116         i = ldap_simple_bind_s(dirserver,
117                                config.c_ldap_bind_dn,
118                                config.c_ldap_bind_pw);
119         if (i != LDAP_SUCCESS) {
120                 CtdlLogPrintf(CTDL_CRIT, "LDAP: Cannot bind: %s (%d)\n",
121                         ldap_err2string(i), i);
122                 dirserver = NULL;       /* FIXME disconnect from ldap */
123                 CtdlAideMessage(ldap_err2string(i),
124                              "LDAP: Cannot bind to server");
125                 return -1;
126         }
127         ldap_time_disconnect = 1;
128         return 0;
129 }
130
131
132
133 /*
134  * Create the root node.  If it's already there, so what?
135  */
136 void create_ldap_root(void)
137 {
138         char *dc_values[2];
139         char *objectClass_values[3];
140         LDAPMod dc, objectClass;
141         LDAPMod *mods[3];
142         char topdc[SIZ];
143         int i;
144
145         /* We just want the top-level dc, not the whole hierarchy */
146         strcpy(topdc, config.c_ldap_base_dn);
147         for (i = 0; topdc[i]; ++i) {
148                 if (topdc[i] == ',') {
149                         topdc[i] = 0;
150                         break;
151                 }
152         }
153         for (i = 0; topdc[i]; ++i) {
154                 if (topdc[i] == '=')
155                         strcpy(topdc, &topdc[i + 1]);
156         }
157
158         /* Set up the transaction */
159         dc.mod_op = LDAP_MOD_ADD;
160         dc.mod_type = "dc";
161         dc_values[0] = topdc;
162         dc_values[1] = NULL;
163         dc.mod_values = dc_values;
164         objectClass.mod_op = LDAP_MOD_ADD;
165         objectClass.mod_type = "objectClass";
166         objectClass_values[0] = "top";
167         objectClass_values[1] = "domain";
168         objectClass_values[2] = NULL;
169         objectClass.mod_values = objectClass_values;
170         mods[0] = &dc;
171         mods[1] = &objectClass;
172         mods[2] = NULL;
173
174         /* Perform the transaction */
175         CtdlLogPrintf(CTDL_DEBUG, "LDAP: Setting up Base DN node...\n");
176
177         begin_critical_section(S_LDAP);
178         if (connect_to_ldap()) {
179                 end_critical_section(S_LDAP);
180                 return;
181         }
182         i = ldap_add_s(dirserver, config.c_ldap_base_dn, mods);
183         end_critical_section(S_LDAP);
184
185         if (i == LDAP_ALREADY_EXISTS) {
186                 CtdlLogPrintf(CTDL_INFO,
187                         "LDAP: Base DN is already present in the directory; no need to add it again.\n");
188         } else if (i != LDAP_SUCCESS) {
189                 CtdlLogPrintf(CTDL_CRIT, "LDAP: ldap_add_s() failed: %s (%d)\n",
190                         ldap_err2string(i), i);
191         }
192 }
193
194
195 /*
196  * Create an OU node representing a Citadel host.
197  * parameter cn is not used, its just there to keep the hook interface consistant
198  * parameter object not used here, present for interface compatability
199  */
200 int create_ldap_host_OU(char *cn, char *host, void **object)
201 {
202         char *dc_values[2];
203         char *objectClass_values[3];
204         LDAPMod dc, objectClass;
205         LDAPMod *mods[3];
206         int i;
207         char dn[SIZ];
208
209         /* The DN is this OU plus the base. */
210         snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
211
212         /* Set up the transaction */
213         dc.mod_op = LDAP_MOD_ADD;
214         dc.mod_type = "ou";
215         dc_values[0] = host;
216         dc_values[1] = NULL;
217         dc.mod_values = dc_values;
218         objectClass.mod_op = LDAP_MOD_ADD;
219         objectClass.mod_type = "objectClass";
220         objectClass_values[0] = "top";
221         objectClass_values[1] = "organizationalUnit";
222         objectClass_values[2] = NULL;
223         objectClass.mod_values = objectClass_values;
224         mods[0] = &dc;
225         mods[1] = &objectClass;
226         mods[2] = NULL;
227
228         /* Perform the transaction */
229         CtdlLogPrintf(CTDL_DEBUG, "LDAP: Setting up Host OU node...\n");
230
231         begin_critical_section(S_LDAP);
232         if (connect_to_ldap()) {
233                 end_critical_section(S_LDAP);
234                 return -1;
235         }
236         i = ldap_add_s(dirserver, dn, mods);
237         end_critical_section(S_LDAP);
238
239         if (i == LDAP_ALREADY_EXISTS) {
240                 CtdlLogPrintf(CTDL_INFO,
241                         "LDAP: Host OU is already present in the directory; no need to add it again.\n");
242         } else if (i != LDAP_SUCCESS) {
243                 CtdlLogPrintf(CTDL_CRIT, "LDAP: ldap_add_s() failed: %s (%d)\n",
244                         ldap_err2string(i), i);
245                 return -1;
246         }
247         return 0;
248 }
249
250
251
252
253
254
255 /*
256  * Create a base LDAP object for the interface
257  */
258
259 int create_ldap_object(char *cn, char *ou, void **object)
260 {
261         // We do nothing here, this just gets the base structure created by the interface.
262         CtdlLogPrintf(CTDL_DEBUG, "LDAP: Created ldap object\n");
263         return 0;
264 }
265
266
267 /*
268  * Add an attribute to the ldap object
269  */
270
271 int add_ldap_object(char *cn, char *ou, void **object)
272 {
273         LDAPMod **attrs;
274         int num_attrs = 0;
275         int num_values = 0;
276         int cur_attr;
277
278
279         CtdlLogPrintf(CTDL_DEBUG,
280                 "LDAP: Adding ldap attribute name:\"%s\" value:\"%s\"\n",
281                 cn, ou);
282
283         attrs = *object;
284         if (attrs) {
285                 while (attrs[num_attrs])
286                         num_attrs++;
287         }
288
289         for (cur_attr = 0; cur_attr < num_attrs; cur_attr++) {
290                 if (!strcmp(attrs[cur_attr]->mod_type, cn)) {   // Adding a value to the attribute
291                         if (attrs[cur_attr]->mod_values) {
292                                 while (attrs[cur_attr]->
293                                        mod_values[num_values]) {
294                                         if (!strcmp
295                                             (ou,
296                                              attrs[cur_attr]->
297                                              mod_values[num_values])) {
298                                                 CtdlLogPrintf(CTDL_DEBUG,
299                                                         "LDAP: Ignoring duplicate attribute/value pair\n");
300                                                 return 0;
301                                         }
302                                         num_values++;
303                                 }
304                         }
305                         attrs[cur_attr]->mod_values =
306                             realloc(attrs[cur_attr]->mod_values,
307                                     (num_values + 2) * (sizeof(char *)));
308                         attrs[cur_attr]->mod_values[num_values] =
309                             strdup(ou);
310                         attrs[cur_attr]->mod_values[num_values + 1] = NULL;
311                         return 0;
312                 }
313         }
314         if (num_attrs)
315                 attrs =
316                     realloc(attrs, (sizeof(LDAPMod *)) * (num_attrs + 2));
317         else
318                 attrs = malloc((sizeof(LDAPMod *)) * (num_attrs + 2));
319         attrs[num_attrs] = malloc(sizeof(LDAPMod));
320         memset(attrs[num_attrs], 0, sizeof(LDAPMod));
321         attrs[num_attrs + 1] = NULL;
322         attrs[num_attrs]->mod_op = LDAP_MOD_ADD;
323         attrs[num_attrs]->mod_type = strdup(cn);
324         attrs[num_attrs]->mod_values = malloc(2 * sizeof(char *));
325         attrs[num_attrs]->mod_values[0] = strdup(ou);
326         attrs[num_attrs]->mod_values[1] = NULL;
327         *object = attrs;
328         return 0;
329 }
330
331
332 /*
333  * SAve the object to the LDAP server
334  */
335 int save_ldap_object(char *cn, char *ou, void **object)
336 {
337         int i;
338
339         char this_dn[SIZ];
340         LDAPMod **attrs;
341         int num_attrs = 0;
342         int count = 0;
343
344         if (dirserver == NULL)
345                 return -1;
346         if (cn == NULL)
347                 return -1;
348
349         sprintf(this_dn, "%s,%s", cn, config.c_ldap_base_dn);
350
351         CtdlLogPrintf(CTDL_INFO, "LDAP: Calling ldap_add_s() for dn of '%s'\n",
352                 this_dn);
353
354         /* The last attribute must be a NULL one. */
355         attrs = (LDAPMod **) * object;
356         if (attrs) {
357                 while (attrs[num_attrs]) {
358                         count = 0;
359                         while (attrs[num_attrs]->mod_values[count]) {
360                                 CtdlLogPrintf(CTDL_DEBUG,
361                                         "LDAP: attribute %d, value %d = \'%s=%s\'\n",
362                                         num_attrs, count,
363                                         attrs[num_attrs]->mod_type,
364                                         attrs[num_attrs]->
365                                         mod_values[count]);
366                                 count++;
367                         }
368                         num_attrs++;
369                 }
370         } else {
371                 CtdlLogPrintf(CTDL_ERR,
372                         "LDAP: no attributes in save_ldap_object\n");
373                 return -1;
374         }
375
376         begin_critical_section(S_LDAP);
377         if (connect_to_ldap()) {
378                 end_critical_section(S_LDAP);
379                 return -1;
380         }
381
382         i = ldap_add_s(dirserver, this_dn, attrs);
383
384         if (i == LDAP_SERVER_DOWN) {
385                 CtdlAideMessage
386                     ("The LDAP server appears to be down.\nThe save to LDAP did not occurr.\n",
387                      "LDAP: save failed");
388                 end_critical_section(S_LDAP);
389                 return -1;
390         }
391
392         /* If the entry already exists, repopulate it instead */
393         /* repopulating doesn't work as Citadel may want some attributes to be deleted.
394          * we have no way of knowing which attributes to delete and LDAP won't work it out for us
395          * so now we delete the old entry and create a new one.
396          */
397         if (i == LDAP_ALREADY_EXISTS) {
398                 end_critical_section(S_LDAP);
399                 CtdlLogPrintf(CTDL_INFO,
400                         "LDAP: Create, already exists, deleteing first.\n");
401                 if (delete_from_ldap(cn, ou, NULL))
402                         return -1;
403                 begin_critical_section(S_LDAP);
404                 CtdlLogPrintf(CTDL_INFO,
405                         "LDAP: Calling ldap_add_s() to recreate for dn of '%s'\n",
406                         this_dn);
407                 i = ldap_add_s(dirserver, this_dn, attrs);
408         }
409
410         if (i != LDAP_SUCCESS) {
411                 CtdlLogPrintf(CTDL_ERR, "LDAP: ldap_add_s() failed: %s (%d)\n",
412                         ldap_err2string(i), i);
413                 CtdlAideMessage
414                     ("The LDAP server refused the save command.\nDid you update the schema?\n",
415                      "LDAP: save failed (schema?)");
416                 end_critical_section(S_LDAP);
417                 return -1;
418         }
419         end_critical_section(S_LDAP);
420         return 0;
421 }
422
423
424 /*
425  * Free the object
426  */
427 int free_ldap_object(char *cn, char *ou, void **object)
428 {
429         int i, j;
430
431         LDAPMod **attrs;
432         int num_attrs = 0;
433
434         attrs = (LDAPMod **) * object;
435         if (attrs) {
436                 while (attrs[num_attrs])
437                         num_attrs++;
438         }
439
440         CtdlLogPrintf(CTDL_DEBUG, "LDAP: Freeing attributes\n");
441         /* Free the attributes */
442         for (i = 0; i < num_attrs; ++i) {
443                 if (attrs[i] != NULL) {
444
445                         /* First, free the value strings */
446                         if (attrs[i]->mod_values != NULL) {
447                                 for (j = 0;
448                                      attrs[i]->mod_values[j] != NULL;
449                                      ++j) {
450                                         free(attrs[i]->mod_values[j]);
451                                 }
452                         }
453
454                         /* Free the value strings pointer list */
455                         if (attrs[i]->mod_values != NULL) {
456                                 free(attrs[i]->mod_values);
457                         }
458
459                         /* Now free the LDAPMod struct itself. */
460                         free(attrs[i]);
461                 }
462         }
463         free(attrs[i]);
464         free(attrs);
465         *object = NULL;
466         return 0;
467 }
468
469
470 /*
471  * Delete a record from the LDAP
472  *
473  * parameter object not used here, present for hook interface compatability
474  */
475 int delete_from_ldap(char *cn, char *ou, void **object)
476 {
477         int i;
478
479         char this_dn[SIZ];
480
481         if (dirserver == NULL)
482                 return -1;
483         if (cn == NULL)
484                 return -1;
485
486         sprintf(this_dn, "%s,%s", cn, config.c_ldap_base_dn);
487
488         CtdlLogPrintf(CTDL_DEBUG, "LDAP: Calling ldap_delete_s()\n");
489
490         begin_critical_section(S_LDAP);
491         if (connect_to_ldap()) {
492                 end_critical_section(S_LDAP);
493                 return -1;
494         }
495
496         i = ldap_delete_s(dirserver, this_dn);
497
498         if (i == LDAP_SERVER_DOWN) {
499                 end_critical_section(S_LDAP);
500                 CtdlAideMessage
501                     ("The LDAP server appears to be down.\nThe delete from LDAP did not occurr.\n",
502                      "LDAP: delete failed");
503                 return -1;
504         }
505
506         if (i != LDAP_SUCCESS) {
507                 CtdlLogPrintf(CTDL_ERR,
508                         "LDAP: ldap_delete_s() failed: %s (%d)\n",
509                         ldap_err2string(i), i);
510                 end_critical_section(S_LDAP);
511                 CtdlAideMessage(ldap_err2string(i), "LDAP: delete failed");
512                 return -1;
513         }
514         end_critical_section(S_LDAP);
515         return 0;
516 }
517
518
519
520
521 void ldap_disconnect_timer(void)
522 {
523         begin_critical_section(S_LDAP);
524         if (ldap_time_disconnect) {
525                 ldap_time_disconnect--;
526                 end_critical_section(S_LDAP);
527                 return;
528         }
529         serv_ldap_cleanup();
530         end_critical_section(S_LDAP);
531 }
532
533
534 #endif                          /* HAVE_LDAP */
535
536
537 /*
538  * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
539  */
540 CTDL_MODULE_INIT(ldap)
541 {
542 #ifdef HAVE_LDAP
543         if (!IsEmptyStr(config.c_ldap_base_dn)) {
544                 CtdlRegisterCleanupHook(serv_ldap_cleanup);
545                 CtdlRegisterSessionHook(ldap_disconnect_timer, EVT_TIMER);
546                 CtdlRegisterDirectoryServiceFunc(delete_from_ldap,
547                                                  DIRECTORY_USER_DEL,
548                                                  "ldap");
549                 CtdlRegisterDirectoryServiceFunc(create_ldap_host_OU,
550                                                  DIRECTORY_CREATE_HOST,
551                                                  "ldap");
552                 CtdlRegisterDirectoryServiceFunc(create_ldap_object,
553                                                  DIRECTORY_CREATE_OBJECT,
554                                                  "ldap");
555                 CtdlRegisterDirectoryServiceFunc(add_ldap_object,
556                                                  DIRECTORY_ATTRIB_ADD,
557                                                  "ldap");
558                 CtdlRegisterDirectoryServiceFunc(save_ldap_object,
559                                                  DIRECTORY_SAVE_OBJECT,
560                                                  "ldap");
561                 CtdlRegisterDirectoryServiceFunc(free_ldap_object,
562                                                  DIRECTORY_FREE_OBJECT,
563                                                  "ldap");
564                 create_ldap_root();
565         }
566 #endif                          /* HAVE_LDAP */
567
568         /* return our Subversion id for the Log */
569         return "$Id$";
570 }