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_SUCCESS) {
110 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
111 ldap_err2string(i), i);
117 * Create an OU node representing a Citadel host.
119 void CtdlCreateHostOU(char *host) {
121 char *objectClass_values[3];
122 LDAPMod dc, objectClass;
127 /* The DN is this OU plus the base. */
128 snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
130 /* Set up the transaction */
131 dc.mod_op = LDAP_MOD_ADD;
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;
143 mods[1] = &objectClass;
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);
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);
167 void CtdlConnectToLdap(void) {
169 int ldap_version = 3;
171 lprintf(CTDL_INFO, "Connecting to LDAP server %s:%d...\n",
172 config.c_ldap_host, config.c_ldap_port);
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",
183 ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
185 lprintf(CTDL_INFO, "Binding to %s\n", config.c_ldap_bind_dn);
187 i = ldap_simple_bind_s(dirserver,
188 config.c_ldap_bind_dn,
189 config.c_ldap_bind_pw
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 */
197 CtdlCreateLdapRoot();
202 * vCard-to-LDAP conversions.
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.
208 * If 'op' is set to V2L_DELETE, then delete the entry from LDAP.
210 void ctdl_vcard_to_ldap(struct CtdlMessage *msg, int op) {
211 struct vCard *v = NULL;
214 LDAPMod **attrs = NULL;
217 int alias_attr = (-1);
219 int phone_attr = (-1);
232 if (dirserver == NULL) return;
233 if (msg == NULL) return;
234 if (msg->cm_fields['M'] == NULL) return;
235 if (msg->cm_fields['A'] == NULL) return;
236 if (msg->cm_fields['N'] == NULL) return;
238 /* Initialize variables */
239 strcpy(givenname, "");
241 strcpy(calFBURL, "");
243 sprintf(this_dn, "cn=%s,ou=%s,%s",
246 config.c_ldap_base_dn
249 sprintf(uid, "%s@%s",
254 /* Are we just deleting? If so, it's simple... */
255 if (op == V2L_DELETE) {
256 lprintf(CTDL_DEBUG, "Calling ldap_delete_s()\n");
257 begin_critical_section(S_LDAP);
258 i = ldap_delete_s(dirserver, this_dn);
259 end_critical_section(S_LDAP);
260 if (i != LDAP_SUCCESS) {
261 lprintf(CTDL_ERR, "ldap_delete_s() failed: %s (%d)\n",
262 ldap_err2string(i), i);
268 * If we get to this point then it must be a V2L_WRITE operation.
271 /* First make sure the OU for the user's home Citadel host is created */
272 CtdlCreateHostOU(msg->cm_fields['N']);
274 /* The first LDAP attribute will be an 'objectclass' list. Citadel
275 * doesn't do anything with this. It's just there for compatibility
279 attrs = malloc( (sizeof(LDAPMod *) * num_attrs) );
280 attrs[0] = malloc(sizeof(LDAPMod));
281 memset(attrs[0], 0, sizeof(LDAPMod));
282 attrs[0]->mod_op = LDAP_MOD_ADD;
283 attrs[0]->mod_type = "objectclass";
284 attrs[0]->mod_values = malloc(3 * sizeof(char *));
285 attrs[0]->mod_values[0] = strdup("inetOrgPerson");
286 attrs[0]->mod_values[1] = NULL;
288 /* Convert the vCard fields to LDAP properties */
289 v = vcard_load(msg->cm_fields['M']);
290 if (v->numprops) for (i=0; i<(v->numprops); ++i) if (striplt(v->prop[i].value), strlen(v->prop[i].value) > 0) {
292 if (!strcasecmp(v->prop[i].name, "n")) {
293 extract_token(sn, v->prop[i].value, 0, ';');
294 extract_token(givenname, v->prop[i].value, 1, ';');
297 if (!strcasecmp(v->prop[i].name, "fn")) {
298 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
299 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
300 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
301 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
302 attrs[num_attrs-1]->mod_type = "cn";
303 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
304 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
305 attrs[num_attrs-1]->mod_values[1] = NULL;
309 if (!strcasecmp(v->prop[i].name, "title")) {
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 = "title";
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;
320 if (!strcasecmp(v->prop[i].name, "org")) {
321 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
322 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
323 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
324 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
325 attrs[num_attrs-1]->mod_type = "o";
326 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
327 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
328 attrs[num_attrs-1]->mod_values[1] = NULL;
331 if ( (!strcasecmp(v->prop[i].name, "adr"))
332 ||(!strncasecmp(v->prop[i].name, "adr;", 4)) ) {
333 /* Unfortunately, we can only do a single address */
337 extract_token(&street[strlen(street)],
338 v->prop[i].value, 0, ';'); /* po box */
340 extract_token(&street[strlen(street)],
341 v->prop[i].value, 1, ';'); /* extend addr */
343 extract_token(&street[strlen(street)],
344 v->prop[i].value, 2, ';'); /* street */
346 extract_token(city, v->prop[i].value, 3, ';');
347 extract_token(state, v->prop[i].value, 4, ';');
348 extract_token(zipcode, v->prop[i].value, 5, ';');
350 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
351 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
352 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
353 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
354 attrs[num_attrs-1]->mod_type = "street";
355 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
356 attrs[num_attrs-1]->mod_values[0] = strdup(street);
357 attrs[num_attrs-1]->mod_values[1] = NULL;
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 = "l";
364 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
365 attrs[num_attrs-1]->mod_values[0] = strdup(city);
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 = "st";
373 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
374 attrs[num_attrs-1]->mod_values[0] = strdup(state);
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 = "postalcode";
382 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
383 attrs[num_attrs-1]->mod_values[0] = strdup(zipcode);
384 attrs[num_attrs-1]->mod_values[1] = NULL;
388 if ( (!strcasecmp(v->prop[i].name, "tel"))
389 ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
391 /* The first 'tel' property creates the 'telephoneNumber' attribute */
392 if (num_phones == 1) {
393 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
394 phone_attr = num_attrs-1;
395 attrs[phone_attr] = malloc(sizeof(LDAPMod));
396 memset(attrs[phone_attr], 0, sizeof(LDAPMod));
397 attrs[phone_attr]->mod_op = LDAP_MOD_ADD;
398 attrs[phone_attr]->mod_type = "telephoneNumber";
399 attrs[phone_attr]->mod_values = malloc(2 * sizeof(char *));
400 attrs[phone_attr]->mod_values[0] = strdup(v->prop[i].value);
401 attrs[phone_attr]->mod_values[1] = NULL;
403 /* Subsequent 'tel' properties *add to* the 'telephoneNumber' attribute */
405 attrs[phone_attr]->mod_values = realloc(attrs[phone_attr]->mod_values,
406 num_phones * sizeof(char *));
407 attrs[phone_attr]->mod_values[num_phones-1]
408 = strdup(v->prop[i].value);
409 attrs[phone_attr]->mod_values[num_phones]
415 if ( (!strcasecmp(v->prop[i].name, "email"))
416 ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
419 lprintf(CTDL_DEBUG, "email addr %d\n", num_emails);
421 /* The first email address creates the 'mail' attribute */
422 if (num_emails == 1) {
423 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
424 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
425 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
426 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
427 attrs[num_attrs-1]->mod_type = "mail";
428 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
429 attrs[num_attrs-1]->mod_values[0] = strdup(v->prop[i].value);
430 attrs[num_attrs-1]->mod_values[1] = NULL;
432 /* The second email address creates the 'alias' attribute */
433 else if (num_emails == 2) {
434 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
435 alias_attr = num_attrs-1;
436 attrs[alias_attr] = malloc(sizeof(LDAPMod));
437 memset(attrs[alias_attr], 0, sizeof(LDAPMod));
438 attrs[alias_attr]->mod_op = LDAP_MOD_ADD;
439 attrs[alias_attr]->mod_type = "alias";
440 attrs[alias_attr]->mod_values = malloc(2 * sizeof(char *));
441 attrs[alias_attr]->mod_values[0] = strdup(v->prop[i].value);
442 attrs[alias_attr]->mod_values[1] = NULL;
444 /* Subsequent email addresses *add to* the 'alias' attribute */
445 else if (num_emails > 2) {
446 attrs[alias_attr]->mod_values = realloc(attrs[alias_attr]->mod_values,
447 num_emails * sizeof(char *));
448 attrs[alias_attr]->mod_values[num_emails-2]
449 = strdup(v->prop[i].value);
450 attrs[alias_attr]->mod_values[num_emails-1]
457 /* Calendar free/busy URL (take the first one we find, but if a subsequent
458 * one contains the "pref" designation then we go with that instead.)
460 if ( (!strcasecmp(v->prop[i].name, "fburl"))
461 ||(!strncasecmp(v->prop[i].name, "fburl;", 6)) ) {
462 if ( (strlen(calFBURL) == 0)
463 || (!strncasecmp(v->prop[i].name, "fburl;pref", 10)) ) {
464 safestrncpy(calFBURL, v->prop[i].value, sizeof calFBURL);
469 vcard_free(v); /* Don't need this anymore. */
471 /* "sn" (surname) based on info in vCard */
472 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
473 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
474 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
475 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
476 attrs[num_attrs-1]->mod_type = "sn";
477 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
478 attrs[num_attrs-1]->mod_values[0] = strdup(sn);
479 attrs[num_attrs-1]->mod_values[1] = NULL;
481 /* "givenname" (first name) based on info in vCard */
482 if (strlen(givenname) == 0) strcpy(givenname, "_");
483 if (strlen(sn) == 0) strcpy(sn, "_");
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 = "givenname";
489 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
490 attrs[num_attrs-1]->mod_values[0] = strdup(givenname);
491 attrs[num_attrs-1]->mod_values[1] = NULL;
493 /* "uid" is a Kolab compatibility thing. We just do cituser@citnode */
494 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
495 attrs[num_attrs-1] = malloc(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 = "uid";
499 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
500 attrs[num_attrs-1]->mod_values[0] = strdup(uid);
501 attrs[num_attrs-1]->mod_values[1] = NULL;
503 /* Add a "cn" (Common Name) attribute based on the user's screen name,
504 * but only there was no 'fn' (full name) property in the vCard
507 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
508 attrs[num_attrs-1] = malloc(sizeof(LDAPMod));
509 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
510 attrs[num_attrs-1]->mod_op = LDAP_MOD_ADD;
511 attrs[num_attrs-1]->mod_type = "cn";
512 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
513 attrs[num_attrs-1]->mod_values[0] = strdup(msg->cm_fields['A']);
514 attrs[num_attrs-1]->mod_values[1] = NULL;
517 /* Add a "calFBURL" attribute if a calendar free/busy URL exists */
518 if (strlen(calFBURL) > 0) {
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 = "calFBURL";
524 attrs[num_attrs-1]->mod_values = malloc(2 * sizeof(char *));
525 attrs[num_attrs-1]->mod_values[0] = strdup(calFBURL);
526 attrs[num_attrs-1]->mod_values[1] = NULL;
529 /* The last attribute must be a NULL one. */
530 attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
531 attrs[num_attrs - 1] = NULL;
533 lprintf(CTDL_DEBUG, "Calling ldap_add_s()\n");
534 begin_critical_section(S_LDAP);
535 i = ldap_add_s(dirserver, this_dn, attrs);
536 end_critical_section(S_LDAP);
538 /* If the entry already exists, repopulate it instead */
539 if (i == LDAP_ALREADY_EXISTS) {
540 for (j=0; j<(num_attrs-1); ++j) {
541 attrs[j]->mod_op = LDAP_MOD_REPLACE;
543 lprintf(CTDL_DEBUG, "Calling ldap_modify_s()\n");
544 begin_critical_section(S_LDAP);
545 i = ldap_modify_s(dirserver, this_dn, attrs);
546 end_critical_section(S_LDAP);
549 if (i != LDAP_SUCCESS) {
550 lprintf(CTDL_ERR, "ldap_add_s() failed: %s (%d)\n",
551 ldap_err2string(i), i);
554 lprintf(CTDL_DEBUG, "Freeing attributes\n");
555 /* Free the attributes */
556 for (i=0; i<num_attrs; ++i) {
557 if (attrs[i] != NULL) {
559 /* First, free the value strings */
560 if (attrs[i]->mod_values != NULL) {
561 for (j=0; attrs[i]->mod_values[j] != NULL; ++j) {
562 free(attrs[i]->mod_values[j]);
566 /* Free the value strings pointer list */
567 if (attrs[i]->mod_values != NULL) {
568 free(attrs[i]->mod_values);
571 /* Now free the LDAPMod struct itself. */
576 lprintf(CTDL_DEBUG, "LDAP write operation complete.\n");
580 #endif /* HAVE_LDAP */
584 * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
586 char *serv_ldap_init(void)
589 CtdlRegisterCleanupHook(serv_ldap_cleanup);
591 if (strlen(config.c_ldap_host) > 0) {
595 #endif /* HAVE_LDAP */