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