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