]> code.citadel.org Git - citadel.git/blob - citadel/serv_ldap.c
* Completed the per-user initialization of LDAP entries.
[citadel.git] / citadel / serv_ldap.c
1 /*
2  * $Id$
3  *
4  * A module which implements the LDAP connector for Citadel.
5  *
6  */
7
8 #include "sysdep.h"
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13 #include <signal.h>
14 #include <pwd.h>
15 #include <errno.h>
16 #include <sys/types.h>
17
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
20 # include <time.h>
21 #else
22 # if HAVE_SYS_TIME_H
23 #  include <sys/time.h>
24 # else
25 #  include <time.h>
26 # endif
27 #endif
28
29 #include <sys/wait.h>
30 #include <string.h>
31 #include <limits.h>
32 #include "citadel.h"
33 #include "server.h"
34 #include "sysdep_decls.h"
35 #include "citserver.h"
36 #include "support.h"
37 #include "config.h"
38 #include "serv_extensions.h"
39 #include "room_ops.h"
40 #include "policy.h"
41 #include "database.h"
42 #include "msgbase.h"
43 #include "serv_ldap.h"
44 #include "vcard.h"
45 #include "tools.h"
46
47 #ifdef HAVE_LDAP
48
49 #include <ldap.h>
50
51 LDAP *dirserver = NULL;
52
53 /*
54  * LDAP connector cleanup function
55  */
56 void serv_ldap_cleanup(void)
57 {
58         if (!dirserver) return;
59
60         lprintf(7, "Unbinding from directory server\n");
61         ldap_unbind(dirserver);
62         dirserver = NULL;
63 }
64
65
66
67 /*
68  * Create the root node.  If it's already there, so what?
69  */
70 void CtdlCreateLdapRoot(void) {
71         char *dc_values[2];
72         char *objectClass_values[3];
73         LDAPMod dc, objectClass;
74         LDAPMod *mods[3];
75         char topdc[SIZ];
76         int i;
77
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;
82         }
83         for (i=0; i<strlen(topdc); ++i) {
84                 if (topdc[i] == '=') strcpy(topdc, &topdc[i+1]);
85         }
86
87         /* Set up the transaction */
88         dc.mod_op               = LDAP_MOD_ADD;
89         dc.mod_type             = "dc";
90         dc_values[0]            = topdc;
91         dc_values[1]            = NULL;
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;
99         mods[0] = &dc;
100         mods[1] = &objectClass;
101         mods[2] = NULL;
102
103         /* Perform the transaction */
104         lprintf(9, "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);
108
109         if (i != LDAP_SUCCESS) {
110                 lprintf(3, "ldap_add_s() failed: %s (%d)\n",
111                         ldap_err2string(i), i);
112         }
113 }
114
115
116 /*
117  * Create an OU node representing a Citadel host.
118  */
119 void CtdlCreateHostOU(char *host) {
120         char *dc_values[2];
121         char *objectClass_values[3];
122         LDAPMod dc, objectClass;
123         LDAPMod *mods[3];
124         int i;
125         char dn[SIZ];
126
127         /* The DN is this OU plus the base. */
128         snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
129
130         /* Set up the transaction */
131         dc.mod_op               = LDAP_MOD_ADD;
132         dc.mod_type             = "ou";
133         dc_values[0]            = host;
134         dc_values[1]            = NULL;
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;
142         mods[0] = &dc;
143         mods[1] = &objectClass;
144         mods[2] = NULL;
145
146         /* Perform the transaction */
147         lprintf(9, "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);
151
152         /* ignore the error -- it's ok if it already exists
153         if (i != LDAP_SUCCESS) {
154                 lprintf(3, "ldap_add_s() failed: %s (%d)\n",
155                         ldap_err2string(i), i);
156         }
157         */
158 }
159
160
161
162
163
164
165
166
167 void CtdlConnectToLdap(void) {
168         int i;
169         int ldap_version = 3;
170
171         lprintf(7, "Connecting to LDAP server %s:%d...\n",
172                 config.c_ldap_host, config.c_ldap_port);
173
174         dirserver = ldap_init(config.c_ldap_host, config.c_ldap_port);
175         if (dirserver == NULL) {
176                 lprintf(3, "Could not connect to %s:%d : %s\n",
177                         config.c_ldap_host,
178                         config.c_ldap_port,
179                         strerror(errno));
180                 return;
181         }
182
183         ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
184
185         lprintf(7, "Binding to %s\n", config.c_ldap_bind_dn);
186
187         i = ldap_simple_bind_s(dirserver,
188                                 config.c_ldap_bind_dn,
189                                 config.c_ldap_bind_pw
190         );
191         if (i != LDAP_SUCCESS) {
192                 lprintf(3, "Cannot bind: %s (%d)\n", ldap_err2string(i), i);
193                 dirserver = NULL;       /* FIXME disconnect from ldap */
194                 return;
195         }
196
197         CtdlCreateLdapRoot();
198 }
199
200
201
202 /*
203  * Write (add, or change if already exists) a directory entry to the
204  * LDAP server, based on the information supplied in a vCard.
205  */
206 void ctdl_vcard_to_ldap(struct CtdlMessage *msg) {
207         struct vCard *v = NULL;
208         int i, j;
209         char this_dn[SIZ];
210         LDAPMod **attrs = NULL;
211         int num_attrs = 0;
212         int num_emails = 0;
213         int alias_attr = (-1);
214         int num_phones = 0;
215         int phone_attr = (-1);
216         int have_addr = 0;
217         int have_cn = 0;
218
219         char givenname[SIZ];
220         char sn[SIZ];
221         char uid[SIZ];
222         char street[SIZ];
223         char city[SIZ];
224         char state[SIZ];
225         char zipcode[SIZ];
226
227         if (dirserver == NULL) return;
228         if (msg == NULL) return;
229         if (msg->cm_fields['M'] == NULL) return;
230         if (msg->cm_fields['A'] == NULL) return;
231         if (msg->cm_fields['N'] == NULL) return;
232
233         /* First make sure the OU for the user's home Citadel host is created */
234         CtdlCreateHostOU(msg->cm_fields['N']);
235
236         /* Initialize variables */
237         strcpy(givenname, "_");
238         strcpy(sn, "_");
239
240         sprintf(this_dn, "cn=%s,ou=%s,%s",
241                 msg->cm_fields['A'],
242                 msg->cm_fields['N'],
243                 config.c_ldap_base_dn
244         );
245                 
246         sprintf(uid, "%s@%s",
247                 msg->cm_fields['A'],
248                 msg->cm_fields['N']
249         );
250
251         /* The first LDAP attribute will be an 'objectclass' list.  Citadel
252          * doesn't do anything with this.  It's just there for compatibility
253          * with Kolab.
254          */
255         num_attrs = 1;
256         attrs = mallok( (sizeof(LDAPMod *) * num_attrs) );
257         attrs[0] = mallok(sizeof(LDAPMod));
258         memset(attrs[0], 0, sizeof(LDAPMod));
259         attrs[0]->mod_op        = LDAP_MOD_ADD;
260         attrs[0]->mod_type      = "objectclass";
261         attrs[0]->mod_values    = mallok(2 * sizeof(char *));
262         attrs[0]->mod_values[0] = strdoop("inetOrgPerson");
263         /*
264         attrs[0]->mod_values[1] = strdoop("organizationalPerson");
265         attrs[0]->mod_values[2] = strdoop("person");
266         attrs[0]->mod_values[3] = strdoop("Top");
267         */
268         attrs[0]->mod_values[1] = NULL;
269
270         /* Convert the vCard fields to LDAP properties */
271         v = vcard_load(msg->cm_fields['M']);
272         if (v->numprops) for (i=0; i<(v->numprops); ++i) {
273
274                 if (!strcasecmp(v->prop[i].name, "n")) {
275                         extract_token(sn,               v->prop[i].value, 0, ';');
276                         extract_token(givenname,        v->prop[i].value, 1, ';');
277                 }
278
279                 if (!strcasecmp(v->prop[i].name, "fn")) {
280                         attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
281                         attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
282                         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
283                         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
284                         attrs[num_attrs-1]->mod_type            = "cn";
285                         attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
286                         attrs[num_attrs-1]->mod_values[0]       = strdoop(v->prop[i].value);
287                         attrs[num_attrs-1]->mod_values[1]       = NULL;
288                         have_cn = 1;
289                 }
290
291                 if (!strcasecmp(v->prop[i].name, "title")) {
292                         attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
293                         attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
294                         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
295                         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
296                         attrs[num_attrs-1]->mod_type            = "title";
297                         attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
298                         attrs[num_attrs-1]->mod_values[0]       = strdoop(v->prop[i].value);
299                         attrs[num_attrs-1]->mod_values[1]       = NULL;
300                 }
301
302                 if (!strcasecmp(v->prop[i].name, "org")) {
303                         attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
304                         attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
305                         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
306                         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
307                         attrs[num_attrs-1]->mod_type            = "o";
308                         attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
309                         attrs[num_attrs-1]->mod_values[0]       = strdoop(v->prop[i].value);
310                         attrs[num_attrs-1]->mod_values[1]       = NULL;
311                 }
312
313                 if ( (!strcasecmp(v->prop[i].name, "adr"))
314                    ||(!strncasecmp(v->prop[i].name, "adr;", 4)) ) {
315                         /* Unfortunately, we can only do a single address */
316                         if (!have_addr) {
317                                 have_addr = 1;
318                                 strcpy(street, "");
319                                 extract_token(&street[strlen(street)],
320                                         v->prop[i].value, 0, ';'); /* po box */
321                                 strcat(street, " ");
322                                 extract_token(&street[strlen(street)],
323                                         v->prop[i].value, 1, ';'); /* extend addr */
324                                 strcat(street, " ");
325                                 extract_token(&street[strlen(street)],
326                                         v->prop[i].value, 2, ';'); /* street */
327                                 striplt(street);
328                                 extract_token(city, v->prop[i].value, 3, ';');
329                                 extract_token(state, v->prop[i].value, 4, ';');
330                                 extract_token(zipcode, v->prop[i].value, 5, ';');
331
332                                 attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
333                                 attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
334                                 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
335                                 attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
336                                 attrs[num_attrs-1]->mod_type            = "street";
337                                 attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
338                                 attrs[num_attrs-1]->mod_values[0]       = strdoop(street);
339                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
340
341                                 attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
342                                 attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
343                                 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
344                                 attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
345                                 attrs[num_attrs-1]->mod_type            = "l";
346                                 attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
347                                 attrs[num_attrs-1]->mod_values[0]       = strdoop(city);
348                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
349
350                                 attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
351                                 attrs[num_attrs-1] = mallok(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            = "st";
355                                 attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
356                                 attrs[num_attrs-1]->mod_values[0]       = strdoop(state);
357                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
358
359                                 attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
360                                 attrs[num_attrs-1] = mallok(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            = "postalcode";
364                                 attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
365                                 attrs[num_attrs-1]->mod_values[0]       = strdoop(zipcode);
366                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
367                         }
368                 }
369
370                 if ( (!strcasecmp(v->prop[i].name, "tel"))
371                    ||(!strncasecmp(v->prop[i].name, "tel;", 4)) ) {
372                         ++num_phones;
373                         /* The first 'tel' property creates the 'telephoneNumber' attribute */
374                         if (num_phones == 1) {
375                                 attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
376                                 phone_attr = num_attrs-1;
377                                 attrs[phone_attr] = mallok(sizeof(LDAPMod));
378                                 memset(attrs[phone_attr], 0, sizeof(LDAPMod));
379                                 attrs[phone_attr]->mod_op               = LDAP_MOD_ADD;
380                                 attrs[phone_attr]->mod_type             = "telephoneNumber";
381                                 attrs[phone_attr]->mod_values           = mallok(2 * sizeof(char *));
382                                 attrs[phone_attr]->mod_values[0]        = strdoop(v->prop[i].value);
383                                 attrs[phone_attr]->mod_values[1]        = NULL;
384                         }
385                         /* Subsequent 'tel' properties *add to* the 'telephoneNumber' attribute */
386                         else {
387                                 attrs[phone_attr]->mod_values = reallok(attrs[phone_attr]->mod_values,
388                                                                      num_phones * sizeof(char *));
389                                 attrs[phone_attr]->mod_values[num_phones-1]
390                                                                         = strdoop(v->prop[i].value);
391                                 attrs[phone_attr]->mod_values[num_phones]
392                                                                         = NULL;
393                         }
394                 }
395
396
397                 if ( (!strcasecmp(v->prop[i].name, "email"))
398                    ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
399         
400                         ++num_emails;
401                         lprintf(9, "email addr %d\n", num_emails);
402
403                         /* The first email address creates the 'mail' attribute */
404                         if (num_emails == 1) {
405                                 attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
406                                 attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
407                                 memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
408                                 attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
409                                 attrs[num_attrs-1]->mod_type            = "mail";
410                                 attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
411                                 attrs[num_attrs-1]->mod_values[0]       = strdoop(v->prop[i].value);
412                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
413                         }
414                         /* The second email address creates the 'alias' attribute */
415                         else if (num_emails == 2) {
416                                 attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
417                                 alias_attr = num_attrs-1;
418                                 attrs[alias_attr] = mallok(sizeof(LDAPMod));
419                                 memset(attrs[alias_attr], 0, sizeof(LDAPMod));
420                                 attrs[alias_attr]->mod_op               = LDAP_MOD_ADD;
421                                 attrs[alias_attr]->mod_type             = "alias";
422                                 attrs[alias_attr]->mod_values           = mallok(2 * sizeof(char *));
423                                 attrs[alias_attr]->mod_values[0]        = strdoop(v->prop[i].value);
424                                 attrs[alias_attr]->mod_values[1]        = NULL;
425                         }
426                         /* Subsequent email addresses *add to* the 'alias' attribute */
427                         else if (num_emails > 2) {
428                                 attrs[alias_attr]->mod_values = reallok(attrs[alias_attr]->mod_values,
429                                                                      num_emails * sizeof(char *));
430                                 attrs[alias_attr]->mod_values[num_emails-2]
431                                                                         = strdoop(v->prop[i].value);
432                                 attrs[alias_attr]->mod_values[num_emails-1]
433                                                                         = NULL;
434                         }
435
436
437                 }
438
439         }
440         vcard_free(v);  /* Don't need this anymore. */
441
442         /* "sn" (surname) based on info in vCard */
443         attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
444         attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
445         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
446         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
447         attrs[num_attrs-1]->mod_type            = "sn";
448         attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
449         attrs[num_attrs-1]->mod_values[0]       = strdoop(sn);
450         attrs[num_attrs-1]->mod_values[1]       = NULL;
451
452         /* "givenname" (first name) based on info in vCard */
453         attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
454         attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
455         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
456         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
457         attrs[num_attrs-1]->mod_type            = "givenname";
458         attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
459         attrs[num_attrs-1]->mod_values[0]       = strdoop(givenname);
460         attrs[num_attrs-1]->mod_values[1]       = NULL;
461
462         /* "uid" is a Kolab compatibility thing.  We just do cituser@citnode */
463         attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
464         attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
465         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
466         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
467         attrs[num_attrs-1]->mod_type            = "uid";
468         attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
469         attrs[num_attrs-1]->mod_values[0]       = strdoop(uid);
470         attrs[num_attrs-1]->mod_values[1]       = NULL;
471
472         /* Add a "cn" (Common Name) attribute based on the user's screen name,
473          * but only there was no 'fn' (full name) property in the vCard 
474          */
475         if (!have_cn) {
476                 attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
477                 attrs[num_attrs-1] = mallok(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            = "cn";
481                 attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
482                 attrs[num_attrs-1]->mod_values[0]       = strdoop(msg->cm_fields['A']);
483                 attrs[num_attrs-1]->mod_values[1]       = NULL;
484         }
485         
486         /* The last attribute must be a NULL one. */
487         attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
488         attrs[num_attrs - 1] = NULL;
489         
490         lprintf(9, "Calling ldap_add_s()\n");
491         begin_critical_section(S_LDAP);
492         i = ldap_add_s(dirserver, this_dn, attrs);
493         end_critical_section(S_LDAP);
494
495         /* If the entry already exists, repopulate it instead */
496         if (i == LDAP_ALREADY_EXISTS) {
497                 for (j=0; j<(num_attrs-1); ++j) {
498                         attrs[j]->mod_op = LDAP_MOD_REPLACE;
499                 }
500                 lprintf(9, "Calling ldap_modify_s()\n");
501                 begin_critical_section(S_LDAP);
502                 i = ldap_modify_s(dirserver, this_dn, attrs);
503                 end_critical_section(S_LDAP);
504         }
505
506         if (i != LDAP_SUCCESS) {
507                 lprintf(3, "ldap_add_s() failed: %s (%d)\n",
508                         ldap_err2string(i), i);
509         }
510
511         lprintf(9, "Freeing attributes\n");
512         /* Free the attributes */
513         for (i=0; i<num_attrs; ++i) {
514                 if (attrs[i] != NULL) {
515
516                         /* First, free the value strings */
517                         if (attrs[i]->mod_values != NULL) {
518                                 for (j=0; attrs[i]->mod_values[j] != NULL; ++j) {
519                                         phree(attrs[i]->mod_values[j]);
520                                 }
521                         }
522
523                         /* Free the value strings pointer list */       
524                         if (attrs[i]->mod_values != NULL) {
525                                 phree(attrs[i]->mod_values);
526                         }
527
528                         /* Now free the LDAPMod struct itself. */
529                         phree(attrs[i]);
530                 }
531         }
532         phree(attrs);
533         lprintf(9, "LDAP operation complete.\n");
534 }
535
536
537 #endif                          /* HAVE_LDAP */
538
539
540 /*
541  * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
542  */
543 char *serv_ldap_init(void)
544 {
545 #ifdef HAVE_LDAP
546         CtdlRegisterCleanupHook(serv_ldap_cleanup);
547
548         if (strlen(config.c_ldap_host) > 0) {
549                 CtdlConnectToLdap();
550         }
551
552 #endif                          /* HAVE_LDAP */
553         return "$Id$";
554 }