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"
52 #define LDAP_DEPRECATED 1 /* to stop warnings with newer libraries */
56 LDAP *dirserver = NULL;
59 * LDAP connector cleanup function
61 void serv_ldap_cleanup(void)
63 if (!dirserver) return;
65 lprintf(CTDL_INFO, "Unbinding from directory server\n");
66 ldap_unbind(dirserver);
73 * Create the root node. If it's already there, so what?
75 void CtdlCreateLdapRoot(void) {
77 char *objectClass_values[3];
78 LDAPMod dc, objectClass;
83 /* We just want the top-level dc, not the whole hierarchy */
84 strcpy(topdc, config.c_ldap_base_dn);
85 for (i=0; i<strlen(topdc); ++i) {
86 if (topdc[i] == ',') topdc[i] = 0;
88 for (i=0; i<strlen(topdc); ++i) {
89 if (topdc[i] == '=') strcpy(topdc, &topdc[i+1]);
92 /* Set up the transaction */
93 dc.mod_op = LDAP_MOD_ADD;
97 dc.mod_values = dc_values;
98 objectClass.mod_op = LDAP_MOD_ADD;
99 objectClass.mod_type = "objectClass";
100 objectClass_values[0] = "top";
101 objectClass_values[1] = "domain";
102 objectClass_values[2] = NULL;
103 objectClass.mod_values = objectClass_values;
105 mods[1] = &objectClass;
108 /* Perform the transaction */
109 lprintf(CTDL_DEBUG, "Setting up Base DN node...\n");
110 begin_critical_section(S_LDAP);
111 i = ldap_add_s(dirserver, config.c_ldap_base_dn, mods);
112 end_critical_section(S_LDAP);
114 if (i == LDAP_ALREADY_EXISTS) {
115 lprintf(CTDL_INFO, "Base DN is already present in the directory; no need to add it again.\n");
117 else if (i != LDAP_SUCCESS) {
118 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
119 ldap_err2string(i), i);
125 * Create an OU node representing a Citadel host.
127 void CtdlCreateHostOU(char *host) {
129 char *objectClass_values[3];
130 LDAPMod dc, objectClass;
135 /* The DN is this OU plus the base. */
136 snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
138 /* Set up the transaction */
139 dc.mod_op = LDAP_MOD_ADD;
143 dc.mod_values = dc_values;
144 objectClass.mod_op = LDAP_MOD_ADD;
145 objectClass.mod_type = "objectClass";
146 objectClass_values[0] = "top";
147 objectClass_values[1] = "organizationalUnit";
148 objectClass_values[2] = NULL;
149 objectClass.mod_values = objectClass_values;
151 mods[1] = &objectClass;
154 /* Perform the transaction */
155 lprintf(CTDL_DEBUG, "Setting up Host OU node...\n");
156 begin_critical_section(S_LDAP);
157 i = ldap_add_s(dirserver, dn, mods);
158 end_critical_section(S_LDAP);
160 if (i == LDAP_ALREADY_EXISTS) {
161 lprintf(CTDL_INFO, "Host OU is already present in the directory; no need to add it again.\n");
163 else if (i != LDAP_SUCCESS) {
164 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
165 ldap_err2string(i), i);
176 void CtdlConnectToLdap(void) {
178 int ldap_version = 3;
180 lprintf(CTDL_INFO, "Connecting to LDAP server %s:%d...\n",
181 config.c_ldap_host, config.c_ldap_port);
183 dirserver = ldap_init(config.c_ldap_host, config.c_ldap_port);
184 if (dirserver == NULL) {
185 lprintf(CTDL_CRIT, "Could not connect to %s:%d : %s\n",
192 ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
194 lprintf(CTDL_INFO, "Binding to %s\n", config.c_ldap_bind_dn);
196 i = ldap_simple_bind_s(dirserver,
197 config.c_ldap_bind_dn,
198 config.c_ldap_bind_pw
200 if (i != LDAP_SUCCESS) {
201 lprintf(CTDL_CRIT, "Cannot bind: %s (%d)\n", ldap_err2string(i), i);
202 dirserver = NULL; /* FIXME disconnect from ldap */
206 CtdlCreateLdapRoot();
211 * vCard-to-LDAP conversions.
213 * If 'op' is set to V2L_WRITE, then write
214 * (add, or change if already exists) a directory entry to the
215 * LDAP server, based on the information supplied in a vCard.
217 * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
219 void ctdl_vcard_to_ldap(struct CtdlMessage *msg, int op) {
220 struct vCard *v = NULL;
223 LDAPMod **attrs = NULL;
226 int alias_attr = (-1);
228 int phone_attr = (-1);
241 if (dirserver == NULL) return;
242 if (msg == NULL) return;
243 if (msg->cm_fields['M'] == NULL) return;
244 if (msg->cm_fields['A'] == NULL) return;
245 if (msg->cm_fields['N'] == NULL) return;
247 /* Initialize variables */
248 strcpy(givenname, "");
250 strcpy(calFBURL, "");
252 sprintf(this_dn, "cn=%s,ou=%s,%s",
255 config.c_ldap_base_dn
258 sprintf(uid, "%s@%s",
263 /* Are we just deleting? If so, it's simple... */
264 if (op == V2L_DELETE) {
265 lprintf(CTDL_DEBUG, "Calling ldap_delete_s()\n");
266 begin_critical_section(S_LDAP);
267 i = ldap_delete_s(dirserver, this_dn);
268 end_critical_section(S_LDAP);
269 if (i != LDAP_SUCCESS) {
270 lprintf(CTDL_ERR, "ldap_delete_s() failed: %s (%d)\n",
271 ldap_err2string(i), i);
277 * If we get to this point then it must be a V2L_WRITE operation.
280 /* First make sure the OU for the user's home Citadel host is created */
281 CtdlCreateHostOU(msg->cm_fields['N']);
283 /* The first LDAP attribute will be an 'objectclass' list. Citadel
284 * doesn't do anything with this. It's just there for compatibility
288 attrs = malloc( (sizeof(LDAPMod *) * num_attrs) );
289 attrs[0] = malloc(sizeof(LDAPMod));
290 memset(attrs[0], 0, sizeof(LDAPMod));
291 attrs[0]->mod_op = LDAP_MOD_ADD;
292 attrs[0]->mod_type = "objectclass";
293 attrs[0]->mod_values = malloc(3 * sizeof(char *));
294 attrs[0]->mod_values[0] = strdup("citadelInetOrgPerson");
295 attrs[0]->mod_values[1] = NULL;
297 /* Convert the vCard fields to LDAP properties */
298 v = vcard_load(msg->cm_fields['M']);
299 if (v->numprops) for (i=0; i<(v->numprops); ++i) if (striplt(v->prop[i].value), strlen(v->prop[i].value) > 0) {
301 if (!strcasecmp(v->prop[i].name, "n")) {
302 extract_token(sn, v->prop[i].value, 0, ';', sizeof sn);
303 extract_token(givenname, v->prop[i].value, 1, ';', sizeof givenname);
306 if (!strcasecmp(v->prop[i].name, "fn")) {
307 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
308 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
309 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
310 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
311 attrs[num_attrs-1]->mod_type = "cn";
312 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
313 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
314 attrs[num_attrs-1]->mod_values[1] = NULL;
318 if (!strcasecmp(v->prop[i].name, "title")) {
319 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
320 attrs[num_attrs-1] = malloc(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 = "title";
324 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
325 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
326 attrs[num_attrs-1]->mod_values[1] = NULL;
329 if (!strcasecmp(v->prop[i].name, "org")) {
330 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
331 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
332 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
333 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
334 attrs[num_attrs-1]->mod_type = "o";
335 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
336 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
337 attrs[num_attrs-1]->mod_values[1] = NULL;
340 if ( (!strcasecmp(v->prop[i].name, "adr"))
341 ||(!strncasecmp(v->prop[i].name, "adr;", 4)) ) {
342 /* Unfortunately, we can only do a single address */
346 extract_token(&street[strlen(street)],
347 v->prop[i].value, 0, ';', (sizeof street - strlen(street))); /* po box */
349 extract_token(&street[strlen(street)],
350 v->prop[i].value, 1, ';', (sizeof street - strlen(street))); /* extend addr */
352 extract_token(&street[strlen(street)],
353 v->prop[i].value, 2, ';', (sizeof street - strlen(street))); /* street */
355 extract_token(city, v->prop[i].value, 3, ';', sizeof city);
356 extract_token(state, v->prop[i].value, 4, ';', sizeof state);
357 extract_token(zipcode, v->prop[i].value, 5, ';', sizeof zipcode);
359 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
360 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
361 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
362 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
363 attrs[num_attrs-1]->mod_type = "street";
364 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
365 attrs[num_attrs-1]->mod_values[0] = strdup(street);
366 attrs[num_attrs-1]->mod_values[1] = NULL;
368 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
369 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
370 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
371 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
372 attrs[num_attrs-1]->mod_type = "l";
373 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
374 attrs[num_attrs-1]->mod_values[0] = strdup(city);
375 attrs[num_attrs-1]->mod_values[1] = NULL;
377 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
378 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
379 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
380 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
381 attrs[num_attrs-1]->mod_type = "st";
382 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
383 attrs[num_attrs-1]->mod_values[0] = strdup(state);
384 attrs[num_attrs-1]->mod_values[1] = NULL;
386 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
387 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
388 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
389 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
390 attrs[num_attrs-1]->mod_type = "postalcode";
391 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
392 attrs[num_attrs-1]->mod_values[0] = strdup(zipcode);
393 attrs[num_attrs-1]->mod_values[1] = NULL;
397 if ( (!strcasecmp(v->prop[i].name, "tel"))
398 ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
400 /* The first 'tel' property creates the 'telephoneNumber' attribute */
401 if (num_phones == 1) {
402 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
403 phone_attr = num_attrs-1;
404 attrs[phone_attr] = malloc(sizeof(LDAPMod));
405 memset(attrs[phone_attr], 0, sizeof(LDAPMod));
406 attrs[phone_attr]->mod_op = LDAP_MOD_ADD;
407 attrs[phone_attr]->mod_type = "telephoneNumber";
408 attrs[phone_attr]->mod_values = malloc(2 * sizeof(char *));
409 attrs[phone_attr]->mod_values[0] = strdup(v->prop[i].value);
410 attrs[phone_attr]->mod_values[1] = NULL;
412 /* Subsequent 'tel' properties *add to* the 'telephoneNumber' attribute */
414 attrs[phone_attr]->mod_values = realloc(attrs[phone_attr]->mod_values,
415 num_phones * sizeof(char *));
416 attrs[phone_attr]->mod_values[num_phones-1]
417 = strdup(v->prop[i].value);
418 attrs[phone_attr]->mod_values[num_phones]
424 if ( (!strcasecmp(v->prop[i].name, "email"))
425 ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
428 lprintf(CTDL_DEBUG, "email addr %d\n", num_emails);
430 /* The first email address creates the 'mail' attribute */
431 if (num_emails == 1) {
432 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
433 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
434 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
435 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
436 attrs[num_attrs-1]->mod_type = "mail";
437 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
438 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
439 attrs[num_attrs-1]->mod_values[1] = NULL;
441 /* The second email address creates the 'alias' attribute */
442 else if (num_emails == 2) {
443 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
444 alias_attr = num_attrs-1;
445 attrs[alias_attr] = malloc(sizeof(LDAPMod));
446 memset(attrs[alias_attr], 0, sizeof(LDAPMod));
447 attrs[alias_attr]->mod_op = LDAP_MOD_ADD;
448 attrs[alias_attr]->mod_type = "alias";
449 attrs[alias_attr]->mod_values = malloc(2 * sizeof(char *));
450 attrs[alias_attr]->mod_values[0] = strdup(v->prop[i].value);
451 attrs[alias_attr]->mod_values[1] = NULL;
453 /* Subsequent email addresses *add to* the 'alias' attribute */
454 else if (num_emails > 2) {
455 attrs[alias_attr]->mod_values = realloc(attrs[alias_attr]->mod_values,
456 num_emails * sizeof(char *));
457 attrs[alias_attr]->mod_values[num_emails-2]
458 = strdup(v->prop[i].value);
459 attrs[alias_attr]->mod_values[num_emails-1]
466 /* Calendar free/busy URL (take the first one we find, but if a subsequent
467 * one contains the "pref" designation then we go with that instead.)
469 if ( (!strcasecmp(v->prop[i].name, "fburl"))
470 ||(!strncasecmp(v->prop[i].name, "fburl;", 6)) ) {
471 if ( (IsEmptyStr(calFBURL))
472 || (!strncasecmp(v->prop[i].name, "fburl;pref", 10)) ) {
473 safestrncpy(calFBURL, v->prop[i].value, sizeof calFBURL);
478 vcard_free(v); /* Don't need this anymore. */
480 /* "sn" (surname) based on info in vCard */
481 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
482 attrs[num_attrs-1] = malloc(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 = "sn";
486 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
487 attrs[num_attrs-1]->mod_values[0] = strdup(sn);
488 attrs[num_attrs-1]->mod_values[1] = NULL;
490 /* "givenname" (first name) based on info in vCard */
491 if (IsEmptyStr(givenname)) strcpy(givenname, "_");
492 if (IsEmptyStr(sn)) strcpy(sn, "_");
493 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
494 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
495 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
496 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
497 attrs[num_attrs-1]->mod_type = "givenname";
498 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
499 attrs[num_attrs-1]->mod_values[0] = strdup(givenname);
500 attrs[num_attrs-1]->mod_values[1] = NULL;
502 /* "uid" is a Kolab compatibility thing. We just do cituser@citnode */
503 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
504 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
505 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
506 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
507 attrs[num_attrs-1]->mod_type = "uid";
508 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
509 attrs[num_attrs-1]->mod_values[0] = strdup(uid);
510 attrs[num_attrs-1]->mod_values[1] = NULL;
512 /* Add a "cn" (Common Name) attribute based on the user's screen name,
513 * but only there was no 'fn' (full name) property in the vCard
516 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
517 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
518 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
519 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
520 attrs[num_attrs-1]->mod_type = "cn";
521 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
522 attrs[num_attrs-1]->mod_values[0] = strdup(msg->cm_fields['A']);
523 attrs[num_attrs-1]->mod_values[1] = NULL;
526 /* Add a "calFBURL" attribute if a calendar free/busy URL exists */
527 if (!IsEmptyStr(calFBURL)) {
528 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
529 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
530 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
531 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
532 attrs[num_attrs-1]->mod_type = "calFBURL";
533 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
534 attrs[num_attrs-1]->mod_values[0] = strdup(calFBURL);
535 attrs[num_attrs-1]->mod_values[1] = NULL;
538 /* The last attribute must be a NULL one. */
539 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
540 attrs[num_attrs - 1] = NULL;
542 lprintf(CTDL_DEBUG, "Calling ldap_add_s() for '%s'\n", this_dn);
543 begin_critical_section(S_LDAP);
544 i = ldap_add_s(dirserver, this_dn, attrs);
545 end_critical_section(S_LDAP);
547 /* If the entry already exists, repopulate it instead */
548 if (i == LDAP_ALREADY_EXISTS) {
549 for (j=0; j<(num_attrs-1); ++j) {
550 attrs[j]->mod_op = LDAP_MOD_REPLACE;
552 lprintf(CTDL_DEBUG, "Calling ldap_modify_s() for '%s'\n", this_dn);
553 begin_critical_section(S_LDAP);
554 i = ldap_modify_s(dirserver, this_dn, attrs);
555 end_critical_section(S_LDAP);
558 if (i != LDAP_SUCCESS) {
559 lprintf(CTDL_ERR, "ldap_add_s() failed: %s (%d)\n",
560 ldap_err2string(i), i);
563 lprintf(CTDL_DEBUG, "Freeing attributes\n");
564 /* Free the attributes */
565 for (i=0; i<num_attrs; ++i) {
566 if (attrs[i] != NULL) {
568 /* First, free the value strings */
569 if (attrs[i]->mod_values != NULL) {
570 for (j=0; attrs[i]->mod_values[j] != NULL; ++j) {
571 free(attrs[i]->mod_values[j]);
575 /* Free the value strings pointer list */
576 if (attrs[i]->mod_values != NULL) {
577 free(attrs[i]->mod_values);
580 /* Now free the LDAPMod struct itself. */
585 lprintf(CTDL_DEBUG, "LDAP write operation complete.\n");
589 #endif /* HAVE_LDAP */
593 * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
595 CTDL_MODULE_INIT(ldap)
598 CtdlRegisterCleanupHook(serv_ldap_cleanup);
600 if (!IsEmptyStr(config.c_ldap_host)) {
604 #endif /* HAVE_LDAP */
606 /* return our Subversion id for the Log */