]> code.citadel.org Git - citadel.git/blob - citadel/serv_ldap.c
* More work on LDAP connector
[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
215         char givenname[SIZ];
216         char sn[SIZ];
217         char uid[SIZ];
218
219         if (dirserver == NULL) return;
220         if (msg == NULL) return;
221         if (msg->cm_fields['M'] == NULL) return;
222         if (msg->cm_fields['A'] == NULL) return;
223         if (msg->cm_fields['N'] == NULL) return;
224
225         /* First make sure the OU for the user's home Citadel host is created */
226         CtdlCreateHostOU(msg->cm_fields['N']);
227
228         /* Initialize variables */
229         strcpy(givenname, "_");
230         strcpy(sn, "_");
231
232         sprintf(this_dn, "cn=%s,ou=%s,%s",
233                 msg->cm_fields['A'],
234                 msg->cm_fields['N'],
235                 config.c_ldap_base_dn
236         );
237                 
238         sprintf(uid, "%s@%s",
239                 msg->cm_fields['A'],
240                 msg->cm_fields['N']
241         );
242
243         /* The first LDAP attribute will be an 'objectclass' list.  Citadel
244          * doesn't do anything with this.  It's just there for compatibility
245          * with Kolab.
246          */
247         num_attrs = 1;
248         attrs = mallok( (sizeof(LDAPMod *) * num_attrs) );
249         attrs[0] = mallok(sizeof(LDAPMod));
250         memset(attrs[0], 0, sizeof(LDAPMod));
251         attrs[0]->mod_op        = LDAP_MOD_ADD;
252         attrs[0]->mod_type      = "objectclass";
253         attrs[0]->mod_values    = mallok(5 * sizeof(char *));
254         attrs[0]->mod_values[0] = strdoop("inetOrgPerson");
255         attrs[0]->mod_values[1] = strdoop("organizationalPerson");
256         attrs[0]->mod_values[2] = strdoop("person");
257         attrs[0]->mod_values[3] = strdoop("Top");
258         attrs[0]->mod_values[4] = NULL;
259
260         /* Add a "cn" (Common Name) attribute based on the user's screen name */
261         attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
262         attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
263         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
264         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
265         attrs[num_attrs-1]->mod_type            = "cn";
266         attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
267         attrs[num_attrs-1]->mod_values[0]       = strdoop(msg->cm_fields['A']);
268         attrs[num_attrs-1]->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, "title")) {
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            = "title";
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                 }
289
290                 if ( (!strcasecmp(v->prop[i].name, "email"))
291                    ||(!strcasecmp(v->prop[i].name, "email;internet")) ) {
292         
293                         ++num_emails;
294                         lprintf(9, "email addr %d\n", num_emails);
295
296                         /* The first email address creates the 'mail' attribute */
297                         if (num_emails == 1) {
298                                 attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
299                                 attrs[num_attrs-1] = mallok(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            = "mail";
303                                 attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
304                                 attrs[num_attrs-1]->mod_values[0]       = strdoop(v->prop[i].value);
305                                 attrs[num_attrs-1]->mod_values[1]       = NULL;
306                         }
307                         /* The second email address creates the 'alias' attribute */
308                         else if (num_emails == 2) {
309                                 attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
310                                 alias_attr = num_attrs-1;
311                                 attrs[alias_attr] = mallok(sizeof(LDAPMod));
312                                 memset(attrs[alias_attr], 0, sizeof(LDAPMod));
313                                 attrs[alias_attr]->mod_op               = LDAP_MOD_ADD;
314                                 attrs[alias_attr]->mod_type             = "alias";
315                                 attrs[alias_attr]->mod_values           = mallok(2 * sizeof(char *));
316                                 attrs[alias_attr]->mod_values[0]        = strdoop(v->prop[i].value);
317                                 attrs[alias_attr]->mod_values[1]        = NULL;
318                         }
319                         /* Subsequent email addresses *add to* the 'alias' attribute */
320                         else if (num_emails > 2) {
321                                 attrs[alias_attr]->mod_values = reallok(attrs[alias_attr]->mod_values,
322                                                                      num_emails * sizeof(char *));
323                                 attrs[alias_attr]->mod_values[num_emails-2]
324                                                                         = strdoop(v->prop[i].value);
325                                 attrs[alias_attr]->mod_values[num_emails-1]
326                                                                         = NULL;
327                         }
328
329
330                 }
331
332         }
333         vcard_free(v);  /* Don't need this anymore. */
334
335         /* "sn" (surname) based on info in vCard */
336         attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
337         attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
338         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
339         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
340         attrs[num_attrs-1]->mod_type            = "sn";
341         attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
342         attrs[num_attrs-1]->mod_values[0]       = strdoop(sn);
343         attrs[num_attrs-1]->mod_values[1]       = NULL;
344
345         /* "givenname" (first name) based on info in vCard */
346         attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
347         attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
348         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
349         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
350         attrs[num_attrs-1]->mod_type            = "givenname";
351         attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
352         attrs[num_attrs-1]->mod_values[0]       = strdoop(givenname);
353         attrs[num_attrs-1]->mod_values[1]       = NULL;
354
355         /* "uid" is a Kolab compatibility thing.  We just do cituser@citnode */
356         attrs = reallok(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
357         attrs[num_attrs-1] = mallok(sizeof(LDAPMod));
358         memset(attrs[num_attrs-1], 0, sizeof(LDAPMod));
359         attrs[num_attrs-1]->mod_op              = LDAP_MOD_ADD;
360         attrs[num_attrs-1]->mod_type            = "uid";
361         attrs[num_attrs-1]->mod_values          = mallok(2 * sizeof(char *));
362         attrs[num_attrs-1]->mod_values[0]       = strdoop(uid);
363         attrs[num_attrs-1]->mod_values[1]       = NULL;
364
365         /* The last attribute must be a NULL one. */
366         attrs = realloc(attrs, (sizeof(LDAPMod *) * ++num_attrs) );
367         attrs[num_attrs - 1] = NULL;
368         
369         lprintf(9, "Calling ldap_add_s()\n");
370         begin_critical_section(S_LDAP);
371         i = ldap_add_s(dirserver, this_dn, attrs);
372         end_critical_section(S_LDAP);
373
374         /* If the entry already exists, repopulate it instead */
375         if (i == LDAP_ALREADY_EXISTS) {
376                 for (j=0; j<(num_attrs-1); ++j) {
377                         attrs[j]->mod_op = LDAP_MOD_REPLACE;
378                 }
379                 lprintf(9, "Calling ldap_modify_s()\n");
380                 begin_critical_section(S_LDAP);
381                 i = ldap_modify_s(dirserver, this_dn, attrs);
382                 end_critical_section(S_LDAP);
383         }
384
385         if (i != LDAP_SUCCESS) {
386                 lprintf(3, "ldap_add_s() failed: %s (%d)\n",
387                         ldap_err2string(i), i);
388         }
389
390         lprintf(9, "Freeing attributes\n");
391         /* Free the attributes */
392         for (i=0; i<num_attrs; ++i) {
393                 if (attrs[i] != NULL) {
394
395                         /* First, free the value strings */
396                         if (attrs[i]->mod_values != NULL) {
397                                 for (j=0; attrs[i]->mod_values[j] != NULL; ++j) {
398                                         phree(attrs[i]->mod_values[j]);
399                                 }
400                         }
401
402                         /* Free the value strings pointer list */       
403                         if (attrs[i]->mod_values != NULL) {
404                                 phree(attrs[i]->mod_values);
405                         }
406
407                         /* Now free the LDAPMod struct itself. */
408                         phree(attrs[i]);
409                 }
410         }
411         phree(attrs);
412         lprintf(9, "LDAP operation complete.\n");
413 }
414
415
416 #endif                          /* HAVE_LDAP */
417
418
419 /*
420  * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
421  */
422 char *serv_ldap_init(void)
423 {
424 #ifdef HAVE_LDAP
425         CtdlRegisterCleanupHook(serv_ldap_cleanup);
426
427         if (strlen(config.c_ldap_host) > 0) {
428                 CtdlConnectToLdap();
429         }
430
431 #endif                          /* HAVE_LDAP */
432         return "$Id$";
433 }