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