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 "sysdep_decls.h"
35 #include "citserver.h"
38 #include "serv_extensions.h"
43 #include "serv_ldap.h"
51 LDAP *dirserver = NULL;
54 * LDAP connector cleanup function
56 void serv_ldap_cleanup(void)
58 if (!dirserver) return;
60 lprintf(CTDL_INFO, "Unbinding from directory server\n");
61 ldap_unbind(dirserver);
68 * Create the root node. If it's already there, so what?
70 void CtdlCreateLdapRoot(void) {
72 char *objectClass_values[3];
73 LDAPMod dc, objectClass;
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;
83 for (i=0; i<strlen(topdc); ++i) {
84 if (topdc[i] == '=') strcpy(topdc, &topdc[i+1]);
87 /* Set up the transaction */
88 dc.mod_op = LDAP_MOD_ADD;
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;
100 mods[1] = &objectClass;
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);
109 if (i == LDAP_ALREADY_EXISTS) {
110 lprintf(CTDL_INFO, "Base DN is already present in the directory; no need to add it again.\n");
112 else if (i != LDAP_SUCCESS) {
113 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
114 ldap_err2string(i), i);
120 * Create an OU node representing a Citadel host.
122 void CtdlCreateHostOU(char *host) {
124 char *objectClass_values[3];
125 LDAPMod dc, objectClass;
130 /* The DN is this OU plus the base. */
131 snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
133 /* Set up the transaction */
134 dc.mod_op = LDAP_MOD_ADD;
138 dc.mod_values = dc_values;
139 objectClass.mod_op = LDAP_MOD_ADD;
140 objectClass.mod_type = "objectClass";
141 objectClass_values[0] = "top";
142 objectClass_values[1] = "organizationalUnit";
143 objectClass_values[2] = NULL;
144 objectClass.mod_values = objectClass_values;
146 mods[1] = &objectClass;
149 /* Perform the transaction */
150 lprintf(CTDL_DEBUG, "Setting up Host OU node...\n");
151 begin_critical_section(S_LDAP);
152 i = ldap_add_s(dirserver, dn, mods);
153 end_critical_section(S_LDAP);
155 if (i == LDAP_ALREADY_EXISTS) {
156 lprintf(CTDL_INFO, "Host OU is already present in the directory; no need to add it again.\n");
158 else if (i != LDAP_SUCCESS) {
159 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
160 ldap_err2string(i), i);
171 void CtdlConnectToLdap(void) {
173 int ldap_version = 3;
175 lprintf(CTDL_INFO, "Connecting to LDAP server %s:%d...\n",
176 config.c_ldap_host, config.c_ldap_port);
178 dirserver = ldap_init(config.c_ldap_host, config.c_ldap_port);
179 if (dirserver == NULL) {
180 lprintf(CTDL_CRIT, "Could not connect to %s:%d : %s\n",
187 ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
189 lprintf(CTDL_INFO, "Binding to %s\n", config.c_ldap_bind_dn);
191 i = ldap_simple_bind_s(dirserver,
192 config.c_ldap_bind_dn,
193 config.c_ldap_bind_pw
195 if (i != LDAP_SUCCESS) {
196 lprintf(CTDL_CRIT, "Cannot bind: %s (%d)\n", ldap_err2string(i), i);
197 dirserver = NULL; /* FIXME disconnect from ldap */
201 CtdlCreateLdapRoot();
206 * vCard-to-LDAP conversions.
208 * If 'op' is set to V2L_WRITE, then write
209 * (add, or change if already exists) a directory entry to the
210 * LDAP server, based on the information supplied in a vCard.
212 * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
214 void ctdl_vcard_to_ldap(struct CtdlMessage *msg, int op) {
215 struct vCard *v = NULL;
218 LDAPMod **attrs = NULL;
221 int alias_attr = (-1);
223 int phone_attr = (-1);
236 if (dirserver == NULL) return;
237 if (msg == NULL) return;
238 if (msg->cm_fields['M'] == NULL) return;
239 if (msg->cm_fields['A'] == NULL) return;
240 if (msg->cm_fields['N'] == NULL) return;
242 /* Initialize variables */
243 strcpy(givenname, "");
245 strcpy(calFBURL, "");
247 sprintf(this_dn, "cn=%s,ou=%s,%s",
250 config.c_ldap_base_dn
253 sprintf(uid, "%s@%s",
258 /* Are we just deleting? If so, it's simple... */
259 if (op == V2L_DELETE) {
260 lprintf(CTDL_DEBUG, "Calling ldap_delete_s()\n");
261 begin_critical_section(S_LDAP);
262 i = ldap_delete_s(dirserver, this_dn);
263 end_critical_section(S_LDAP);
264 if (i != LDAP_SUCCESS) {
265 lprintf(CTDL_ERR, "ldap_delete_s() failed: %s (%d)\n",
266 ldap_err2string(i), i);
272 * If we get to this point then it must be a V2L_WRITE operation.
275 /* First make sure the OU for the user's home Citadel host is created */
276 CtdlCreateHostOU(msg->cm_fields['N']);
278 /* The first LDAP attribute will be an 'objectclass' list. Citadel
279 * doesn't do anything with this. It's just there for compatibility
283 attrs = malloc( (sizeof(LDAPMod *) * num_attrs) );
284 attrs[0] = malloc(sizeof(LDAPMod));
285 memset(attrs[0], 0, sizeof(LDAPMod));
286 attrs[0]->mod_op = LDAP_MOD_ADD;
287 attrs[0]->mod_type = "objectclass";
288 attrs[0]->mod_values = malloc(3 * sizeof(char *));
289 attrs[0]->mod_values[0] = strdup("citadelInetOrgPerson");
290 attrs[0]->mod_values[1] = NULL;
292 /* Convert the vCard fields to LDAP properties */
293 v = vcard_load(msg->cm_fields['M']);
294 if (v->numprops) for (i=0; i<(v->numprops); ++i) if (striplt(v->prop[i].value), strlen(v->prop[i].value) > 0) {
296 if (!strcasecmp(v->prop[i].name, "n")) {
297 extract_token(sn, v->prop[i].value, 0, ';', sizeof sn);
298 extract_token(givenname, v->prop[i].value, 1, ';', sizeof givenname);
301 if (!strcasecmp(v->prop[i].name, "fn")) {
302 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
303 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
304 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
305 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
306 attrs[num_attrs-1]->mod_type = "cn";
307 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
308 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
309 attrs[num_attrs-1]->mod_values[1] = NULL;
313 if (!strcasecmp(v->prop[i].name, "title")) {
314 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
315 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
316 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
317 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
318 attrs[num_attrs-1]->mod_type = "title";
319 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
320 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
321 attrs[num_attrs-1]->mod_values[1] = NULL;
324 if (!strcasecmp(v->prop[i].name, "org")) {
325 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
326 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
327 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
328 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
329 attrs[num_attrs-1]->mod_type = "o";
330 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
331 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
332 attrs[num_attrs-1]->mod_values[1] = NULL;
335 if ( (!strcasecmp(v->prop[i].name, "adr"))
336 ||(!strncasecmp(v->prop[i].name, "adr;", 4)) ) {
337 /* Unfortunately, we can only do a single address */
341 extract_token(&street[strlen(street)],
342 v->prop[i].value, 0, ';', (sizeof street - strlen(street))); /* po box */
344 extract_token(&street[strlen(street)],
345 v->prop[i].value, 1, ';', (sizeof street - strlen(street))); /* extend addr */
347 extract_token(&street[strlen(street)],
348 v->prop[i].value, 2, ';', (sizeof street - strlen(street))); /* street */
350 extract_token(city, v->prop[i].value, 3, ';', sizeof city);
351 extract_token(state, v->prop[i].value, 4, ';', sizeof state);
352 extract_token(zipcode, v->prop[i].value, 5, ';', sizeof zipcode);
354 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
355 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
356 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
357 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
358 attrs[num_attrs-1]->mod_type = "street";
359 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
360 attrs[num_attrs-1]->mod_values[0] = strdup(street);
361 attrs[num_attrs-1]->mod_values[1] = NULL;
363 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
364 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
365 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
366 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
367 attrs[num_attrs-1]->mod_type = "l";
368 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
369 attrs[num_attrs-1]->mod_values[0] = strdup(city);
370 attrs[num_attrs-1]->mod_values[1] = NULL;
372 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
373 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
374 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
375 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
376 attrs[num_attrs-1]->mod_type = "st";
377 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
378 attrs[num_attrs-1]->mod_values[0] = strdup(state);
379 attrs[num_attrs-1]->mod_values[1] = NULL;
381 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
382 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
383 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
384 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
385 attrs[num_attrs-1]->mod_type = "postalcode";
386 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
387 attrs[num_attrs-1]->mod_values[0] = strdup(zipcode);
388 attrs[num_attrs-1]->mod_values[1] = NULL;
392 if ( (!strcasecmp(v->prop[i].name, "tel"))
393 ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
395 /* The first 'tel' property creates the 'telephoneNumber' attribute */
396 if (num_phones == 1) {
397 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
398 phone_attr = num_attrs-1;
399 attrs[phone_attr] = malloc(sizeof(LDAPMod));
400 memset(attrs[phone_attr], 0, sizeof(LDAPMod));
401 attrs[phone_attr]->mod_op = LDAP_MOD_ADD;
402 attrs[phone_attr]->mod_type = "telephoneNumber";
403 attrs[phone_attr]->mod_values = malloc(2 * sizeof(char *));
404 attrs[phone_attr]->mod_values[0] = strdup(v->prop[i].value);
405 attrs[phone_attr]->mod_values[1] = NULL;
407 /* Subsequent 'tel' properties *add to* the 'telephoneNumber' attribute */
409 attrs[phone_attr]->mod_values = realloc(attrs[phone_attr]->mod_values,
410 num_phones * sizeof(char *));
411 attrs[phone_attr]->mod_values[num_phones-1]
412 = strdup(v->prop[i].value);
413 attrs[phone_attr]->mod_values[num_phones]
419 if ( (!strcasecmp(v->prop[i].name, "email"))
420 ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
423 lprintf(CTDL_DEBUG, "email addr %d\n", num_emails);
425 /* The first email address creates the 'mail' attribute */
426 if (num_emails == 1) {
427 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
428 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
429 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
430 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
431 attrs[num_attrs-1]->mod_type = "mail";
432 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
433 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
434 attrs[num_attrs-1]->mod_values[1] = NULL;
436 /* The second email address creates the 'alias' attribute */
437 else if (num_emails == 2) {
438 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
439 alias_attr = num_attrs-1;
440 attrs[alias_attr] = malloc(sizeof(LDAPMod));
441 memset(attrs[alias_attr], 0, sizeof(LDAPMod));
442 attrs[alias_attr]->mod_op = LDAP_MOD_ADD;
443 attrs[alias_attr]->mod_type = "alias";
444 attrs[alias_attr]->mod_values = malloc(2 * sizeof(char *));
445 attrs[alias_attr]->mod_values[0] = strdup(v->prop[i].value);
446 attrs[alias_attr]->mod_values[1] = NULL;
448 /* Subsequent email addresses *add to* the 'alias' attribute */
449 else if (num_emails > 2) {
450 attrs[alias_attr]->mod_values = realloc(attrs[alias_attr]->mod_values,
451 num_emails * sizeof(char *));
452 attrs[alias_attr]->mod_values[num_emails-2]
453 = strdup(v->prop[i].value);
454 attrs[alias_attr]->mod_values[num_emails-1]
461 /* Calendar free/busy URL (take the first one we find, but if a subsequent
462 * one contains the "pref" designation then we go with that instead.)
464 if ( (!strcasecmp(v->prop[i].name, "fburl"))
465 ||(!strncasecmp(v->prop[i].name, "fburl;", 6)) ) {
466 if ( (strlen(calFBURL) == 0)
467 || (!strncasecmp(v->prop[i].name, "fburl;pref", 10)) ) {
468 safestrncpy(calFBURL, v->prop[i].value, sizeof calFBURL);
473 vcard_free(v); /* Don't need this anymore. */
475 /* "sn" (surname) based on info in vCard */
476 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
477 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
478 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
479 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
480 attrs[num_attrs-1]->mod_type = "sn";
481 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
482 attrs[num_attrs-1]->mod_values[0] = strdup(sn);
483 attrs[num_attrs-1]->mod_values[1] = NULL;
485 /* "givenname" (first name) based on info in vCard */
486 if (strlen(givenname) == 0) strcpy(givenname, "_");
487 if (strlen(sn) == 0) strcpy(sn, "_");
488 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
489 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
490 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
491 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
492 attrs[num_attrs-1]->mod_type = "givenname";
493 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
494 attrs[num_attrs-1]->mod_values[0] = strdup(givenname);
495 attrs[num_attrs-1]->mod_values[1] = NULL;
497 /* "uid" is a Kolab compatibility thing. We just do cituser@citnode */
498 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
499 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
500 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
501 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
502 attrs[num_attrs-1]->mod_type = "uid";
503 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
504 attrs[num_attrs-1]->mod_values[0] = strdup(uid);
505 attrs[num_attrs-1]->mod_values[1] = NULL;
507 /* Add a "cn" (Common Name) attribute based on the user's screen name,
508 * but only there was no 'fn' (full name) property in the vCard
511 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
512 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
513 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
514 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
515 attrs[num_attrs-1]->mod_type = "cn";
516 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
517 attrs[num_attrs-1]->mod_values[0] = strdup(msg->cm_fields['A']);
518 attrs[num_attrs-1]->mod_values[1] = NULL;
521 /* Add a "calFBURL" attribute if a calendar free/busy URL exists */
522 if (strlen(calFBURL) > 0) {
523 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
524 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
525 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
526 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
527 attrs[num_attrs-1]->mod_type = "calFBURL";
528 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
529 attrs[num_attrs-1]->mod_values[0] = strdup(calFBURL);
530 attrs[num_attrs-1]->mod_values[1] = NULL;
533 /* The last attribute must be a NULL one. */
534 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
535 attrs[num_attrs - 1] = NULL;
537 lprintf(CTDL_DEBUG, "Calling ldap_add_s() for '%s'\n", this_dn);
538 begin_critical_section(S_LDAP);
539 i = ldap_add_s(dirserver, this_dn, attrs);
540 end_critical_section(S_LDAP);
542 /* If the entry already exists, repopulate it instead */
543 if (i == LDAP_ALREADY_EXISTS) {
544 for (j=0; j<(num_attrs-1); ++j) {
545 attrs[j]->mod_op = LDAP_MOD_REPLACE;
547 lprintf(CTDL_DEBUG, "Calling ldap_modify_s() for '%s'\n", this_dn);
548 begin_critical_section(S_LDAP);
549 i = ldap_modify_s(dirserver, this_dn, attrs);
550 end_critical_section(S_LDAP);
553 if (i != LDAP_SUCCESS) {
554 lprintf(CTDL_ERR, "ldap_add_s() failed: %s (%d)\n",
555 ldap_err2string(i), i);
558 lprintf(CTDL_DEBUG, "Freeing attributes\n");
559 /* Free the attributes */
560 for (i=0; i<num_attrs; ++i) {
561 if (attrs[i] != NULL) {
563 /* First, free the value strings */
564 if (attrs[i]->mod_values != NULL) {
565 for (j=0; attrs[i]->mod_values[j] != NULL; ++j) {
566 free(attrs[i]->mod_values[j]);
570 /* Free the value strings pointer list */
571 if (attrs[i]->mod_values != NULL) {
572 free(attrs[i]->mod_values);
575 /* Now free the LDAPMod struct itself. */
580 lprintf(CTDL_DEBUG, "LDAP write operation complete.\n");
584 #endif /* HAVE_LDAP */
588 * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
590 char *serv_ldap_init(void)
593 CtdlRegisterCleanupHook(serv_ldap_cleanup);
595 if (strlen(config.c_ldap_host) > 0) {
599 #endif /* HAVE_LDAP */