6d8acc1f77156adea325f90114ce3670456f20d8
[citadel.git] / citadel / 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 "sysdep_decls.h"
35 #include "citserver.h"
36 #include "support.h"
37 #include "config.h"
38 #include "serv_extensions.h"
39 #include "room_ops.h"
40 #include "policy.h"
41 #include "database.h"
42 #include "msgbase.h"
43 #include "serv_ldap.h"
44 #include "vcard.h"
45 #include "tools.h"
46
47 #ifdef HAVE_LDAP
48
49 #include <ldap.h>
50
51 LDAP *dirserver = NULL;
52
53 /*
54  * LDAP connector cleanup function
55  */
56 void serv_ldap_cleanup(void)
57 {
58         if (!dirserver) return;
59
60         lprintf(CTDL_INFO, "Unbinding from directory server\n");
61         ldap_unbind(dirserver);
62         dirserver = NULL;
63 }
64
65
66
67 /*
68  * Create the root node.  If it's already there, so what?
69  */
70 void CtdlCreateLdapRoot(void) {
71         char *dc_values[2];
72         char *objectClass_values[3];
73         LDAPMod dc, objectClass;
74         LDAPMod *mods[3];
75         char topdc[SIZ];
76         int i;
77
78         /* We just want the top-level dc, not the whole hierarchy */
79         strcpy(topdc, config.c_ldap_base_dn);
80         for (i=0; i<strlen(topdc); ++i) {
81                 if (topdc[i] == ',') topdc[i] = 0;
82         }
83         for (i=0; i<strlen(topdc); ++i) {
84                 if (topdc[i] == '=') strcpy(topdc, &topdc[i+1]);
85         }
86
87         /* Set up the transaction */
88         dc.mod_op               = LDAP_MOD_ADD;
89         dc.mod_type             = "dc";
90         dc_values[0]            = topdc;
91         dc_values[1]            = NULL;
92         dc.mod_values           = dc_values;
93         objectClass.mod_op      = LDAP_MOD_ADD;
94         objectClass.mod_type    = "objectClass";
95         objectClass_values[0]   = "top";
96         objectClass_values[1]   = "domain";
97         objectClass_values[2]   = NULL;
98         objectClass.mod_values  = objectClass_values;
99         mods[0] = &dc;
100         mods[1] = &objectClass;
101         mods[2] = NULL;
102
103         /* Perform the transaction */
104         lprintf(CTDL_DEBUG, "Setting up Base DN node...\n");
105         begin_critical_section(S_LDAP);
106         i = ldap_add_s(dirserver, config.c_ldap_base_dn, mods);
107         end_critical_section(S_LDAP);
108
109         if (i == LDAP_ALREADY_EXISTS) {
110                 lprintf(CTDL_INFO, "Base DN is already present in the directory; no need to add it again.\n");
111         }
112         else if (i != LDAP_SUCCESS) {
113                 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
114                         ldap_err2string(i), i);
115         }
116 }
117
118
119 /*
120  * Create an OU node representing a Citadel host.
121  */
122 void CtdlCreateHostOU(char *host) {
123         char *dc_values[2];
124         char *objectClass_values[3];
125         LDAPMod dc, objectClass;
126         LDAPMod *mods[3];
127         int i;
128         char dn[SIZ];
129
130         /* The DN is this OU plus the base. */
131         snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
132
133         /* Set up the transaction */
134         dc.mod_op               = LDAP_MOD_ADD;
135         dc.mod_type             = "ou";
136         dc_values[0]            = host;
137         dc_values[1]            = NULL;
138         dc.mod_values           = dc_values;
139         objectClass.mod_op      = LDAP_MOD_ADD;
140         objectClass.mod_type    = "objectClass";
141         objectClass_values[0]   = "top";
142         objectClass_values[1]   = "organizationalUnit";
143         objectClass_values[2]   = NULL;
144         objectClass.mod_values  = objectClass_values;
145         mods[0] = &dc;
146         mods[1] = &objectClass;
147         mods[2] = NULL;
148
149         /* Perform the transaction */
150         lprintf(CTDL_DEBUG, "Setting up Host OU node...\n");
151         begin_critical_section(S_LDAP);
152         i = ldap_add_s(dirserver, dn, mods);
153         end_critical_section(S_LDAP);
154
155         if (i == LDAP_ALREADY_EXISTS) {
156                 lprintf(CTDL_INFO, "Host OU is already present in the directory; no need to add it again.\n");
157         }
158         else if (i != LDAP_SUCCESS) {
159                 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
160                         ldap_err2string(i), i);
161         }
162 }
163
164
165
166
167
168
169
170
171 void CtdlConnectToLdap(void) {
172         int i;
173         int ldap_version = 3;
174
175         lprintf(CTDL_INFO, "Connecting to LDAP server %s:%d...\n",
176                 config.c_ldap_host, config.c_ldap_port);
177
178         dirserver = ldap_init(config.c_ldap_host, config.c_ldap_port);
179         if (dirserver == NULL) {
180                 lprintf(CTDL_CRIT, "Could not connect to %s:%d : %s\n",
181                         config.c_ldap_host,
182                         config.c_ldap_port,
183                         strerror(errno));
184                 return;
185         }
186
187         ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
188
189         lprintf(CTDL_INFO, "Binding to %s\n", config.c_ldap_bind_dn);
190
191         i = ldap_simple_bind_s(dirserver,
192                                 config.c_ldap_bind_dn,
193                                 config.c_ldap_bind_pw
194         );
195         if (i != LDAP_SUCCESS) {
196                 lprintf(CTDL_CRIT, "Cannot bind: %s (%d)\n", ldap_err2string(i), i);
197                 dirserver = NULL;       /* FIXME disconnect from ldap */
198                 return;
199         }
200
201         CtdlCreateLdapRoot();
202 }
203
204
205 /* 
206  * vCard-to-LDAP conversions.
207  *
208  * If 'op' is set to V2L_WRITE, then write
209  * (add, or change if already exists) a directory entry to the
210  * LDAP server, based on the information supplied in a vCard.
211  *
212  * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
213  */
214 void ctdl_vcard_to_ldap(struct CtdlMessage *msg, int op) {
215         struct vCard *v = NULL;
216         int i, j;
217         char this_dn[SIZ];
218         LDAPMod **attrs = NULL;
219         int num_attrs = 0;
220         int num_emails = 0;
221         int alias_attr = (-1);
222         int num_phones = 0;
223         int phone_attr = (-1);
224         int have_addr = 0;
225         int have_cn = 0;
226
227         char givenname[128];
228         char sn[128];
229         char uid[256];
230         char street[256];
231         char city[128];
232         char state[3];
233         char zipcode[10];
234         char calFBURL[256];
235
236         if (dirserver == NULL) return;
237         if (msg == NULL) return;
238         if (msg->cm_fields['M'] == NULL) return;
239         if (msg->cm_fields['A'] == NULL) return;
240         if (msg->cm_fields['N'] == NULL) return;
241
242         /* Initialize variables */
243         strcpy(givenname, "");
244         strcpy(sn, "");
245         strcpy(calFBURL, "");
246
247         sprintf(this_dn, "cn=%s,ou=%s,%s",
248                 msg->cm_fields['A'],
249                 msg->cm_fields['N'],
250                 config.c_ldap_base_dn
251         );
252                 
253         sprintf(uid, "%s@%s",
254                 msg->cm_fields['A'],
255                 msg->cm_fields['N']
256         );
257
258         /* Are we just deleting?  If so, it's simple... */
259         if (op == V2L_DELETE) {
260                 lprintf(CTDL_DEBUG, "Calling ldap_delete_s()\n");
261                 begin_critical_section(S_LDAP);
262                 i = ldap_delete_s(dirserver, this_dn);
263                 end_critical_section(S_LDAP);
264                 if (i != LDAP_SUCCESS) {
265                         lprintf(CTDL_ERR, "ldap_delete_s() failed: %s (%d)\n",
266                                 ldap_err2string(i), i);
267                 }
268                 return;
269         }
270
271         /*
272          * If we get to this point then it must be a V2L_WRITE operation.
273          */
274
275         /* First make sure the OU for the user's home Citadel host is created */
276         CtdlCreateHostOU(msg->cm_fields['N']);
277
278         /* The first LDAP attribute will be an 'objectclass' list.  Citadel
279          * doesn't do anything with this.  It's just there for compatibility
280          * with Kolab.
281          */
282         num_attrs = 1;
283         attrs = malloc( (sizeof(LDAPMod *) * num_attrs) );
284         attrs[0] = malloc(sizeof(LDAPMod));
285         memset(attrs[0], 0, sizeof(LDAPMod));
286         attrs[0]->mod_op        = LDAP_MOD_ADD;
287         attrs[0]->mod_type      = "objectclass";
288         attrs[0]->mod_values    = malloc(3 * sizeof(char *));
289         attrs[0]->mod_values[0] = strdup("citadelInetOrgPerson");
290         attrs[0]->mod_values[1] = NULL;
291
292         /* Convert the vCard fields to LDAP properties */
293         v = vcard_load(msg->cm_fields['M']);
294         if (v->numprops) for (i=0; i<(v->numprops); ++i) if (striplt(v->prop[i].value), strlen(v->prop[i].value) > 0) {
295
296                 if (!strcasecmp(v->prop[i].name, "n")) {
297                         extract_token(sn,               v->prop[i].value, 0, ';', sizeof sn);
298                         extract_token(givenname,        v->prop[i].value, 1, ';', sizeof givenname);
299                 }
300
301                 if (!strcasecmp(v->prop[i].name, "fn")) {
302                         attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
303                         attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
304                         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
305                         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
306                         attrs[num_attrs-1]->mod_type            = "cn";
307                         attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
308                         attrs[num_attrs-1]->mod_values[0]       = strdup(v->prop[i].value);
309                         attrs[num_attrs-1]->mod_values[1]       = NULL;
310                         have_cn = 1;
311                 }
312
313                 if (!strcasecmp(v->prop[i].name, "title")) {
314                         attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
315                         attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
316                         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
317                         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
318                         attrs[num_attrs-1]->mod_type            = "title";
319                         attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
320                         attrs[num_attrs-1]->mod_values[0]       = strdup(v->prop[i].value);
321                         attrs[num_attrs-1]->mod_values[1]       = NULL;
322                 }
323
324                 if (!strcasecmp(v->prop[i].name, "org")) {
325                         attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
326                         attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
327                         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
328                         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
329                         attrs[num_attrs-1]->mod_type            = "o";
330                         attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
331                         attrs[num_attrs-1]->mod_values[0]       = strdup(v->prop[i].value);
332                         attrs[num_attrs-1]->mod_values[1]       = NULL;
333                 }
334
335                 if ( (!strcasecmp(v->prop[i].name, "adr"))
336                    ||(!strncasecmp(v->prop[i].name, "adr;", 4)) ) {
337                         /* Unfortunately, we can only do a single address */
338                         if (!have_addr) {
339                                 have_addr = 1;
340                                 strcpy(street, "");
341                                 extract_token(&street[strlen(street)],
342                                         v->prop[i].value, 0, ';', (sizeof street - strlen(street))); /* po box */
343                                 strcat(street, " ");
344                                 extract_token(&street[strlen(street)],
345                                         v->prop[i].value, 1, ';', (sizeof street - strlen(street))); /* extend addr */
346                                 strcat(street, " ");
347                                 extract_token(&street[strlen(street)],
348                                         v->prop[i].value, 2, ';', (sizeof street - strlen(street))); /* street */
349                                 striplt(street);
350                                 extract_token(city, v->prop[i].value, 3, ';', sizeof city);
351                                 extract_token(state, v->prop[i].value, 4, ';', sizeof state);
352                                 extract_token(zipcode, v->prop[i].value, 5, ';', sizeof zipcode);
353
354                                 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
355                                 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
356                                 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
357                                 attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
358                                 attrs[num_attrs-1]->mod_type            = "street";
359                                 attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
360                                 attrs[num_attrs-1]->mod_values[0]       = strdup(street);
361                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
362
363                                 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
364                                 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
365                                 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
366                                 attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
367                                 attrs[num_attrs-1]->mod_type            = "l";
368                                 attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
369                                 attrs[num_attrs-1]->mod_values[0]       = strdup(city);
370                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
371
372                                 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
373                                 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
374                                 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
375                                 attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
376                                 attrs[num_attrs-1]->mod_type            = "st";
377                                 attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
378                                 attrs[num_attrs-1]->mod_values[0]       = strdup(state);
379                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
380
381                                 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
382                                 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
383                                 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
384                                 attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
385                                 attrs[num_attrs-1]->mod_type            = "postalcode";
386                                 attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
387                                 attrs[num_attrs-1]->mod_values[0]       = strdup(zipcode);
388                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
389                         }
390                 }
391
392                 if ( (!strcasecmp(v->prop[i].name, "tel"))
393                    ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
394                         ++num_phones;
395                         /* The first 'tel' property creates the 'telephoneNumber' attribute */
396                         if (num_phones == 1) {
397                                 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
398                                 phone_attr = num_attrs-1;
399                                 attrs[phone_attr] = malloc(sizeof(LDAPMod));
400                                 memset(attrs[phone_attr], 0, sizeof(LDAPMod));
401                                 attrs[phone_attr]->mod_op               = LDAP_MOD_ADD;
402                                 attrs[phone_attr]->mod_type             = "telephoneNumber";
403                                 attrs[phone_attr]->mod_values           = malloc(2 * sizeof(char *));
404                                 attrs[phone_attr]->mod_values[0]        = strdup(v->prop[i].value);
405                                 attrs[phone_attr]->mod_values[1]        = NULL;
406                         }
407                         /* Subsequent 'tel' properties *add to* the 'telephoneNumber' attribute */
408                         else {
409                                 attrs[phone_attr]->mod_values = realloc(attrs[phone_attr]->mod_values,
410                                                                      num_phones * sizeof(char *));
411                                 attrs[phone_attr]->mod_values[num_phones-1]
412                                                                         = strdup(v->prop[i].value);
413                                 attrs[phone_attr]->mod_values[num_phones]
414                                                                         = NULL;
415                         }
416                 }
417
418
419                 if ( (!strcasecmp(v->prop[i].name, "email"))
420                    ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
421         
422                         ++num_emails;
423                         lprintf(CTDL_DEBUG, "email addr %d\n", num_emails);
424
425                         /* The first email address creates the 'mail' attribute */
426                         if (num_emails == 1) {
427                                 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
428                                 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
429                                 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
430                                 attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
431                                 attrs[num_attrs-1]->mod_type            = "mail";
432                                 attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
433                                 attrs[num_attrs-1]->mod_values[0]       = strdup(v->prop[i].value);
434                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
435                         }
436                         /* The second email address creates the 'alias' attribute */
437                         else if (num_emails == 2) {
438                                 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
439                                 alias_attr = num_attrs-1;
440                                 attrs[alias_attr] = malloc(sizeof(LDAPMod));
441                                 memset(attrs[alias_attr], 0, sizeof(LDAPMod));
442                                 attrs[alias_attr]->mod_op               = LDAP_MOD_ADD;
443                                 attrs[alias_attr]->mod_type             = "alias";
444                                 attrs[alias_attr]->mod_values           = malloc(2 * sizeof(char *));
445                                 attrs[alias_attr]->mod_values[0]        = strdup(v->prop[i].value);
446                                 attrs[alias_attr]->mod_values[1]        = NULL;
447                         }
448                         /* Subsequent email addresses *add to* the 'alias' attribute */
449                         else if (num_emails > 2) {
450                                 attrs[alias_attr]->mod_values = realloc(attrs[alias_attr]->mod_values,
451                                                                      num_emails * sizeof(char *));
452                                 attrs[alias_attr]->mod_values[num_emails-2]
453                                                                         = strdup(v->prop[i].value);
454                                 attrs[alias_attr]->mod_values[num_emails-1]
455                                                                         = NULL;
456                         }
457
458
459                 }
460
461                 /* Calendar free/busy URL (take the first one we find, but if a subsequent
462                  * one contains the "pref" designation then we go with that instead.)
463                  */
464                 if ( (!strcasecmp(v->prop[i].name, "fburl"))
465                    ||(!strncasecmp(v->prop[i].name, "fburl;", 6)) ) {
466                         if ( (strlen(calFBURL) == 0)
467                            || (!strncasecmp(v->prop[i].name, "fburl;pref", 10)) ) {
468                                 safestrncpy(calFBURL, v->prop[i].value, sizeof calFBURL);
469                         }
470                 }
471
472         }
473         vcard_free(v);  /* Don't need this anymore. */
474
475         /* "sn" (surname) based on info in vCard */
476         attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
477         attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
478         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
479         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
480         attrs[num_attrs-1]->mod_type            = "sn";
481         attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
482         attrs[num_attrs-1]->mod_values[0]       = strdup(sn);
483         attrs[num_attrs-1]->mod_values[1]       = NULL;
484
485         /* "givenname" (first name) based on info in vCard */
486         if (strlen(givenname) == 0) strcpy(givenname, "_");
487         if (strlen(sn) == 0) strcpy(sn, "_");
488         attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
489         attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
490         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
491         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
492         attrs[num_attrs-1]->mod_type            = "givenname";
493         attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
494         attrs[num_attrs-1]->mod_values[0]       = strdup(givenname);
495         attrs[num_attrs-1]->mod_values[1]       = NULL;
496
497         /* "uid" is a Kolab compatibility thing.  We just do cituser@citnode */
498         attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
499         attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
500         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
501         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
502         attrs[num_attrs-1]->mod_type            = "uid";
503         attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
504         attrs[num_attrs-1]->mod_values[0]       = strdup(uid);
505         attrs[num_attrs-1]->mod_values[1]       = NULL;
506
507         /* Add a "cn" (Common Name) attribute based on the user's screen name,
508          * but only there was no 'fn' (full name) property in the vCard 
509          */
510         if (!have_cn) {
511                 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
512                 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
513                 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
514                 attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
515                 attrs[num_attrs-1]->mod_type            = "cn";
516                 attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
517                 attrs[num_attrs-1]->mod_values[0]       = strdup(msg->cm_fields['A']);
518                 attrs[num_attrs-1]->mod_values[1]       = NULL;
519         }
520
521         /* Add a "calFBURL" attribute if a calendar free/busy URL exists */
522         if (strlen(calFBURL) > 0) {
523                 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
524                 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
525                 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
526                 attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
527                 attrs[num_attrs-1]->mod_type            = "calFBURL";
528                 attrs[num_attrs-1]->mod_values          = malloc(2 * sizeof(char *));
529                 attrs[num_attrs-1]->mod_values[0]       = strdup(calFBURL);
530                 attrs[num_attrs-1]->mod_values[1]       = NULL;
531         }
532         
533         /* The last attribute must be a NULL one. */
534         attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
535         attrs[num_attrs - 1] = NULL;
536         
537         lprintf(CTDL_DEBUG, "Calling ldap_add_s() for '%s'\n", this_dn);
538         begin_critical_section(S_LDAP);
539         i = ldap_add_s(dirserver, this_dn, attrs);
540         end_critical_section(S_LDAP);
541
542         /* If the entry already exists, repopulate it instead */
543         if (i == LDAP_ALREADY_EXISTS) {
544                 for (j=0; j<(num_attrs-1); ++j) {
545                         attrs[j]->mod_op = LDAP_MOD_REPLACE;
546                 }
547                 lprintf(CTDL_DEBUG, "Calling ldap_modify_s() for '%s'\n", this_dn);
548                 begin_critical_section(S_LDAP);
549                 i = ldap_modify_s(dirserver, this_dn, attrs);
550                 end_critical_section(S_LDAP);
551         }
552
553         if (i != LDAP_SUCCESS) {
554                 lprintf(CTDL_ERR, "ldap_add_s() failed: %s (%d)\n",
555                         ldap_err2string(i), i);
556         }
557
558         lprintf(CTDL_DEBUG, "Freeing attributes\n");
559         /* Free the attributes */
560         for (i=0; i<num_attrs; ++i) {
561                 if (attrs[i] != NULL) {
562
563                         /* First, free the value strings */
564                         if (attrs[i]->mod_values != NULL) {
565                                 for (j=0; attrs[i]->mod_values[j] != NULL; ++j) {
566                                         free(attrs[i]->mod_values[j]);
567                                 }
568                         }
569
570                         /* Free the value strings pointer list */       
571                         if (attrs[i]->mod_values != NULL) {
572                                 free(attrs[i]->mod_values);
573                         }
574
575                         /* Now free the LDAPMod struct itself. */
576                         free(attrs[i]);
577                 }
578         }
579         free(attrs);
580         lprintf(CTDL_DEBUG, "LDAP write operation complete.\n");
581 }
582
583
584 #endif                          /* HAVE_LDAP */
585
586
587 /*
588  * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
589  */
590 char *serv_ldap_init(void)
591 {
592 #ifdef HAVE_LDAP
593         CtdlRegisterCleanupHook(serv_ldap_cleanup);
594
595         if (strlen(config.c_ldap_host) > 0) {
596                 CtdlConnectToLdap();
597         }
598
599 #endif                          /* HAVE_LDAP */
600
601         /* return our Subversion id for the Log */
602         return "$Id$";
603 }