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; !IsEmptyStr(&topdc[i]); ++i) {
86 if (topdc[i] == ',') {
91 for (i=0; !IsEmptyStr(&topdc[i]); ++i) {
92 if (topdc[i] == '=') strcpy(topdc, &topdc[i+1]);
95 /* Set up the transaction */
96 dc.mod_op = LDAP_MOD_ADD;
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;
108 mods[1] = &objectClass;
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);
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");
120 else if (i != LDAP_SUCCESS) {
121 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
122 ldap_err2string(i), i);
128 * Create an OU node representing a Citadel host.
130 void CtdlCreateHostOU(char *host) {
132 char *objectClass_values[3];
133 LDAPMod dc, objectClass;
138 /* The DN is this OU plus the base. */
139 snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
141 /* Set up the transaction */
142 dc.mod_op = LDAP_MOD_ADD;
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;
154 mods[1] = &objectClass;
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);
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");
166 else if (i != LDAP_SUCCESS) {
167 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
168 ldap_err2string(i), i);
179 void CtdlConnectToLdap(void) {
181 int ldap_version = 3;
183 lprintf(CTDL_INFO, "Connecting to LDAP server %s:%d...\n",
184 config.c_ldap_host, config.c_ldap_port);
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",
195 ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
197 lprintf(CTDL_INFO, "Binding to %s\n", config.c_ldap_bind_dn);
199 i = ldap_simple_bind_s(dirserver,
200 config.c_ldap_bind_dn,
201 config.c_ldap_bind_pw
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 */
209 CtdlCreateLdapRoot();
214 * vCard-to-LDAP conversions.
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.
220 * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
222 void ctdl_vcard_to_ldap(struct CtdlMessage *msg, int op) {
223 struct vCard *v = NULL;
226 LDAPMod **attrs = NULL;
229 int alias_attr = (-1);
231 int phone_attr = (-1);
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;
250 /* Initialize variables */
251 strcpy(givenname, "");
253 strcpy(calFBURL, "");
255 sprintf(this_dn, "cn=%s,ou=%s,%s",
258 config.c_ldap_base_dn
261 sprintf(uid, "%s@%s",
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);
280 * If we get to this point then it must be a V2L_WRITE operation.
283 /* First make sure the OU for the user's home Citadel host is created */
284 CtdlCreateHostOU(msg->cm_fields['N']);
286 /* The first LDAP attribute will be an 'objectclass' list. Citadel
287 * doesn't do anything with this. It's just there for compatibility
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;
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) {
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);
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;
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;
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;
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 */
349 extract_token(&street[strlen(street)],
350 v->prop[i].value, 0, ';', (sizeof street - strlen(street))); /* po box */
352 extract_token(&street[strlen(street)],
353 v->prop[i].value, 1, ';', (sizeof street - strlen(street))); /* extend addr */
355 extract_token(&street[strlen(street)],
356 v->prop[i].value, 2, ';', (sizeof street - strlen(street))); /* 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);
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;
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;
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;
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;
400 if ( (!strcasecmp(v->prop[i].name, "tel"))
401 ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
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;
415 /* Subsequent 'tel' properties *add to* the 'telephoneNumber' attribute */
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]
427 if ( (!strcasecmp(v->prop[i].name, "email"))
428 ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
431 lprintf(CTDL_DEBUG, "email addr %d\n", num_emails);
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;
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;
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]
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.)
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);
481 vcard_free(v); /* Don't need this anymore. */
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;
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;
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;
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
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;
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;
541 /* The last attribute must be a NULL one. */
542 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
543 attrs[num_attrs - 1] = NULL;
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);
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;
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);
561 if (i != LDAP_SUCCESS) {
562 lprintf(CTDL_ERR, "ldap_add_s() failed: %s (%d)\n",
563 ldap_err2string(i), i);
566 lprintf(CTDL_DEBUG, "Freeing attributes\n");
567 /* Free the attributes */
568 for (i=0; i<num_attrs; ++i) {
569 if (attrs[i] != NULL) {
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]);
578 /* Free the value strings pointer list */
579 if (attrs[i]->mod_values != NULL) {
580 free(attrs[i]->mod_values);
583 /* Now free the LDAPMod struct itself. */
588 lprintf(CTDL_DEBUG, "LDAP write operation complete.\n");
592 #endif /* HAVE_LDAP */
596 * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
598 CTDL_MODULE_INIT(ldap)
601 CtdlRegisterCleanupHook(serv_ldap_cleanup);
603 if (!IsEmptyStr(config.c_ldap_host)) {
607 #endif /* HAVE_LDAP */
609 /* return our Subversion id for the Log */