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