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