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