4 * A module which implements the LDAP connector for Citadel.
16 #include <sys/types.h>
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
34 #include "citserver.h"
41 #include "serv_ldap.h"
46 #include "ctdl_module.h"
54 LDAP *dirserver = NULL;
57 * LDAP connector cleanup function
59 void serv_ldap_cleanup(void)
61 if (!dirserver) return;
63 lprintf(CTDL_INFO, "Unbinding from directory server\n");
64 ldap_unbind(dirserver);
71 * Create the root node. If it's already there, so what?
73 void CtdlCreateLdapRoot(void) {
75 char *objectClass_values[3];
76 LDAPMod dc, objectClass;
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;
86 for (i=0; i<strlen(topdc); ++i) {
87 if (topdc[i] == '=') strcpy(topdc, &topdc[i+1]);
90 /* Set up the transaction */
91 dc.mod_op = LDAP_MOD_ADD;
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;
103 mods[1] = &objectClass;
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);
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");
115 else if (i != LDAP_SUCCESS) {
116 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
117 ldap_err2string(i), i);
123 * Create an OU node representing a Citadel host.
125 void CtdlCreateHostOU(char *host) {
127 char *objectClass_values[3];
128 LDAPMod dc, objectClass;
133 /* The DN is this OU plus the base. */
134 snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
136 /* Set up the transaction */
137 dc.mod_op = LDAP_MOD_ADD;
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;
149 mods[1] = &objectClass;
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);
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");
161 else if (i != LDAP_SUCCESS) {
162 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
163 ldap_err2string(i), i);
174 void CtdlConnectToLdap(void) {
176 int ldap_version = 3;
178 lprintf(CTDL_INFO, "Connecting to LDAP server %s:%d...\n",
179 config.c_ldap_host, config.c_ldap_port);
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",
190 ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
192 lprintf(CTDL_INFO, "Binding to %s\n", config.c_ldap_bind_dn);
194 i = ldap_simple_bind_s(dirserver,
195 config.c_ldap_bind_dn,
196 config.c_ldap_bind_pw
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 */
204 CtdlCreateLdapRoot();
209 * vCard-to-LDAP conversions.
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.
215 * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
217 void ctdl_vcard_to_ldap(struct CtdlMessage *msg, int op) {
218 struct vCard *v = NULL;
221 LDAPMod **attrs = NULL;
224 int alias_attr = (-1);
226 int phone_attr = (-1);
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;
245 /* Initialize variables */
246 strcpy(givenname, "");
248 strcpy(calFBURL, "");
250 sprintf(this_dn, "cn=%s,ou=%s,%s",
253 config.c_ldap_base_dn
256 sprintf(uid, "%s@%s",
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);
275 * If we get to this point then it must be a V2L_WRITE operation.
278 /* First make sure the OU for the user's home Citadel host is created */
279 CtdlCreateHostOU(msg->cm_fields['N']);
281 /* The first LDAP attribute will be an 'objectclass' list. Citadel
282 * doesn't do anything with this. It's just there for compatibility
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;
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) {
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);
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;
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;
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;
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 */
344 extract_token(&street[strlen(street)],
345 v->prop[i].value, 0, ';', (sizeof street - strlen(street))); /* po box */
347 extract_token(&street[strlen(street)],
348 v->prop[i].value, 1, ';', (sizeof street - strlen(street))); /* extend addr */
350 extract_token(&street[strlen(street)],
351 v->prop[i].value, 2, ';', (sizeof street - strlen(street))); /* 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);
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;
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;
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;
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;
395 if ( (!strcasecmp(v->prop[i].name, "tel"))
396 ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
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;
410 /* Subsequent 'tel' properties *add to* the 'telephoneNumber' attribute */
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]
422 if ( (!strcasecmp(v->prop[i].name, "email"))
423 ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
426 lprintf(CTDL_DEBUG, "email addr %d\n", num_emails);
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;
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;
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]
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.)
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);
476 vcard_free(v); /* Don't need this anymore. */
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;
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;
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;
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
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;
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;
536 /* The last attribute must be a NULL one. */
537 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
538 attrs[num_attrs - 1] = NULL;
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);
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;
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);
556 if (i != LDAP_SUCCESS) {
557 lprintf(CTDL_ERR, "ldap_add_s() failed: %s (%d)\n",
558 ldap_err2string(i), i);
561 lprintf(CTDL_DEBUG, "Freeing attributes\n");
562 /* Free the attributes */
563 for (i=0; i<num_attrs; ++i) {
564 if (attrs[i] != NULL) {
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]);
573 /* Free the value strings pointer list */
574 if (attrs[i]->mod_values != NULL) {
575 free(attrs[i]->mod_values);
578 /* Now free the LDAPMod struct itself. */
583 lprintf(CTDL_DEBUG, "LDAP write operation complete.\n");
587 #endif /* HAVE_LDAP */
591 * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
593 CTDL_MODULE_INIT(ldap)
596 CtdlRegisterCleanupHook(serv_ldap_cleanup);
598 if (strlen(config.c_ldap_host) > 0) {
602 #endif /* HAVE_LDAP */
604 /* return our Subversion id for the Log */