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