Big change to the ldap code to break its dependancy on serv_vcard.c and
[citadel.git] / citadel / modules / ldap / 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 "citserver.h"
35 #include "support.h"
36 #include "config.h"
37 #include "room_ops.h"
38 #include "policy.h"
39 #include "database.h"
40 #include "msgbase.h"
41 #include "serv_ldap.h"
42 #include "tools.h"
43
44
45 #include "ctdl_module.h"
46
47
48
49 #ifdef HAVE_LDAP
50
51 #define LDAP_DEPRECATED 1       /* to stop warnings with newer libraries */
52
53 #include <ldap.h>
54
55 LDAP *dirserver = NULL;
56
57 /*
58  * LDAP connector cleanup function
59  */
60 void serv_ldap_cleanup(void)
61 {
62         if (!dirserver) return;
63
64         lprintf(CTDL_INFO, "Unbinding from directory server\n");
65         ldap_unbind(dirserver);
66         dirserver = NULL;
67 }
68
69
70
71 /*
72  * Create the root node.  If it's already there, so what?
73  */
74 void CtdlCreateLdapRoot(void) {
75         char *dc_values[2];
76         char *objectClass_values[3];
77         LDAPMod dc, objectClass;
78         LDAPMod *mods[3];
79         char topdc[SIZ];
80         int i;
81
82         /* We just want the top-level dc, not the whole hierarchy */
83         strcpy(topdc, config.c_ldap_base_dn);
84         for (i=0; topdc[i]; ++i) {
85                 if (topdc[i] == ',') {
86                         topdc[i] = 0;
87                         break;
88                 }
89         }
90         for (i=0; topdc[i]; ++i) {
91                 if (topdc[i] == '=') strcpy(topdc, &topdc[i+1]);
92         }
93
94         /* Set up the transaction */
95         dc.mod_op               = LDAP_MOD_ADD;
96         dc.mod_type             = "dc";
97         dc_values[0]            = topdc;
98         dc_values[1]            = NULL;
99         dc.mod_values           = dc_values;
100         objectClass.mod_op      = LDAP_MOD_ADD;
101         objectClass.mod_type    = "objectClass";
102         objectClass_values[0]   = "top";
103         objectClass_values[1]   = "domain";
104         objectClass_values[2]   = NULL;
105         objectClass.mod_values  = objectClass_values;
106         mods[0] = &dc;
107         mods[1] = &objectClass;
108         mods[2] = NULL;
109
110         /* Perform the transaction */
111         lprintf(CTDL_DEBUG, "Setting up Base DN node...\n");
112         begin_critical_section(S_LDAP);
113         i = ldap_add_s(dirserver, config.c_ldap_base_dn, mods);
114         end_critical_section(S_LDAP);
115
116         if (i == LDAP_ALREADY_EXISTS) {
117                 lprintf(CTDL_INFO, "Base DN is already present in the directory; no need to add it again.\n");
118         }
119         else if (i != LDAP_SUCCESS) {
120                 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
121                         ldap_err2string(i), i);
122         }
123 }
124
125
126 /*
127  * Create an OU node representing a Citadel host.
128  * parameter cn is not used, its just there to keep the hook interface consistant
129  * parameter object not used here, present for interface compatability
130  */
131 int CtdlCreateLdapHostOU(char *cn, char *host, void **object) {
132         char *dc_values[2];
133         char *objectClass_values[3];
134         LDAPMod dc, objectClass;
135         LDAPMod *mods[3];
136         int i;
137         char dn[SIZ];
138
139         /* The DN is this OU plus the base. */
140         snprintf(dn, sizeof dn, "ou=%s,%s", host, config.c_ldap_base_dn);
141
142         /* Set up the transaction */
143         dc.mod_op               = LDAP_MOD_ADD;
144         dc.mod_type             = "ou";
145         dc_values[0]            = host;
146         dc_values[1]            = NULL;
147         dc.mod_values           = dc_values;
148         objectClass.mod_op      = LDAP_MOD_ADD;
149         objectClass.mod_type    = "objectClass";
150         objectClass_values[0]   = "top";
151         objectClass_values[1]   = "organizationalUnit";
152         objectClass_values[2]   = NULL;
153         objectClass.mod_values  = objectClass_values;
154         mods[0] = &dc;
155         mods[1] = &objectClass;
156         mods[2] = NULL;
157
158         /* Perform the transaction */
159         lprintf(CTDL_DEBUG, "Setting up Host OU node...\n");
160         begin_critical_section(S_LDAP);
161         i = ldap_add_s(dirserver, dn, mods);
162         end_critical_section(S_LDAP);
163
164         if (i == LDAP_ALREADY_EXISTS) {
165                 lprintf(CTDL_INFO, "Host OU is already present in the directory; no need to add it again.\n");
166         }
167         else if (i != LDAP_SUCCESS) {
168                 lprintf(CTDL_CRIT, "ldap_add_s() failed: %s (%d)\n",
169                         ldap_err2string(i), i);
170                 return -1;
171         }
172         return 0;
173 }
174
175
176
177
178
179
180
181
182 void CtdlConnectToLdap(void) {
183         int i;
184         int ldap_version = 3;
185
186         lprintf(CTDL_INFO, "Connecting to LDAP server %s:%d...\n",
187                 config.c_ldap_host, config.c_ldap_port);
188
189         dirserver = ldap_init(config.c_ldap_host, config.c_ldap_port);
190         if (dirserver == NULL) {
191                 lprintf(CTDL_CRIT, "Could not connect to %s:%d : %s\n",
192                         config.c_ldap_host,
193                         config.c_ldap_port,
194                         strerror(errno));
195                 return;
196         }
197
198         ldap_set_option(dirserver, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
199
200         lprintf(CTDL_INFO, "Binding to %s\n", config.c_ldap_bind_dn);
201
202         i = ldap_simple_bind_s(dirserver,
203                                 config.c_ldap_bind_dn,
204                                 config.c_ldap_bind_pw
205         );
206         if (i != LDAP_SUCCESS) {
207                 lprintf(CTDL_CRIT, "Cannot bind: %s (%d)\n", ldap_err2string(i), i);
208                 dirserver = NULL;       /* FIXME disconnect from ldap */
209                 return;
210         }
211
212         CtdlCreateLdapRoot();
213 }
214
215
216
217 /*
218  * Create a base LDAP object for the interface
219  */
220  
221 int CtdlCreateLdapObject(char *cn, char *ou, void **object)
222 {
223         // We do nothing here, this just gets the base structure created by the interface.
224         lprintf (CTDL_DEBUG, "Created ldap object\n");
225         return 0;
226 }
227
228
229 /*
230  * Add an attribute to the ldap object
231  */
232  
233 int CtdlAddLdapAttr(char *cn, char *ou, void **object)
234 {
235         LDAPMod **attrs ;
236         int num_attrs = 0;
237         int num_values = 0;
238         int cur_attr;
239         
240         
241         lprintf (CTDL_DEBUG, "Adding ldap attribute\n");
242         
243         attrs = *object;
244         if (attrs)
245         {
246                 while (attrs[num_attrs])
247                         num_attrs++;
248         }
249         
250         for (cur_attr = 0; cur_attr < num_attrs ; cur_attr++)
251         {
252                 if (!strcmp(attrs[cur_attr]->mod_type, cn))
253                 {       // Adding a value to the attribute
254                         if (attrs[cur_attr]->mod_values)
255                         {
256                                 while (attrs[cur_attr]->mod_values[num_values])
257                                         num_values++;
258                         }
259                         attrs[cur_attr]->mod_values = realloc(attrs[cur_attr]->mod_values, (num_values + 2) * (sizeof(char *)));
260                         attrs[cur_attr]->mod_values[num_values] = strdup(ou);
261                         attrs[cur_attr]->mod_values[num_values+1] = NULL;
262                         return 0;
263                 }
264         }
265         if (num_attrs)
266                 attrs = realloc(attrs, (sizeof(LDAPMod *)) * (num_attrs + 2));
267         else
268                 attrs = malloc((sizeof(LDAPMod *)) * (num_attrs + 2));
269         attrs[num_attrs] = malloc(sizeof(LDAPMod));
270         memset(attrs[num_attrs], 0, sizeof(LDAPMod));
271         attrs[num_attrs+1] = NULL;
272         attrs[num_attrs]->mod_op        = LDAP_MOD_ADD;
273         attrs[num_attrs]->mod_type      = strdup(cn);
274         attrs[num_attrs]->mod_values    = malloc(2 * sizeof(char *));
275         attrs[num_attrs]->mod_values[0] = strdup(ou);
276         attrs[num_attrs]->mod_values[1] = NULL;
277         *object = attrs;
278         return 0;
279 }
280
281
282 /*
283  * SAve the object to the LDAP server
284  */
285 int CtdlSaveLdapObject(char *cn, char *ou, void **object)
286 {
287         int i, j;
288         
289         char this_dn[SIZ];
290         LDAPMod **attrs ;
291         int num_attrs = 0;
292         int count = 0;
293         
294         if (dirserver == NULL) return -1;
295         if (ou == NULL) return -1;
296         if (cn == NULL) return -1;
297         
298         sprintf(this_dn, "cn=%s,ou=%s,%s", cn, ou, config.c_ldap_base_dn);
299
300         /* The last attribute must be a NULL one. */
301         attrs = (LDAPMod **)*object;
302         if (attrs)
303         {
304                 while (attrs[num_attrs])
305                         num_attrs++;
306         }
307         
308         lprintf(CTDL_DEBUG, "Calling ldap_add_s() for '%s'\n", this_dn);
309         
310         begin_critical_section(S_LDAP);
311         i = ldap_add_s(dirserver, this_dn, attrs);
312         end_critical_section(S_LDAP);
313
314         /* If the entry already exists, repopulate it instead */
315         if (i == LDAP_ALREADY_EXISTS) {
316                 for (j=0; j<(num_attrs); ++j) {
317                         attrs[j]->mod_op = LDAP_MOD_REPLACE;
318                 }
319                 lprintf(CTDL_DEBUG, "Calling ldap_modify_s() for '%s'\n", this_dn);
320                 begin_critical_section(S_LDAP);
321                 i = ldap_modify_s(dirserver, this_dn, attrs);
322                 end_critical_section(S_LDAP);
323         }
324
325         if (i != LDAP_SUCCESS) {
326                 lprintf(CTDL_ERR, "ldap_add_s() failed: %s (%d)\n",
327                         ldap_err2string(i), i);
328                 return -1;
329         }
330         return 0;
331 }
332
333
334 /*
335  * Free the object
336  */
337 int CtdlFreeLdapObject(char *cn, char *ou, void **object)
338 {
339         int i, j;
340         
341         LDAPMod **attrs ;
342         int num_attrs = 0;
343
344         attrs = (LDAPMod **)*object;
345         if (attrs)
346         {
347                 while (attrs[num_attrs])
348                         num_attrs++;
349         }
350
351         lprintf(CTDL_DEBUG, "Freeing attributes\n");
352         /* Free the attributes */
353         for (i=0; i<num_attrs; ++i) {
354                 if (attrs[i] != NULL) {
355
356                         /* First, free the value strings */
357                         if (attrs[i]->mod_values != NULL) {
358                                 for (j=0; attrs[i]->mod_values[j] != NULL; ++j) {
359                                         free(attrs[i]->mod_values[j]);
360                                 }
361                         }
362
363                         /* Free the value strings pointer list */       
364                         if (attrs[i]->mod_values != NULL) {
365                                 free(attrs[i]->mod_values);
366                         }
367
368                         /* Now free the LDAPMod struct itself. */
369                         free(attrs[i]);
370                 }
371         }
372         free(attrs[i]);
373         free(attrs);
374         *object = NULL;
375         return 0;
376 }
377
378
379 /*
380  * Delete a record from the LDAP
381  *
382  * parameter object not used here, present for hook interface compatability
383  */
384 int CtdlDeleteFromLdap(char *cn, char *ou, void **object)
385 {
386         int i;
387         
388         char this_dn[SIZ];
389         
390         if (dirserver == NULL) return -1;
391         if (ou == NULL) return -1;
392         if (cn == NULL) return -1;
393         
394         sprintf(this_dn, "cn=%s,ou=%s,%s", cn, ou, config.c_ldap_base_dn);
395         
396         lprintf(CTDL_DEBUG, "Calling ldap_delete_s()\n");
397         
398         begin_critical_section(S_LDAP);
399         i = ldap_delete_s(dirserver, this_dn);
400         end_critical_section(S_LDAP);
401         
402         if (i != LDAP_SUCCESS) {
403                 lprintf(CTDL_ERR, "ldap_delete_s() failed: %s (%d)\n",
404                         ldap_err2string(i), i);
405                 return -1;
406         }
407         return 0;
408 }
409
410
411 #endif                          /* HAVE_LDAP */
412
413
414 /*
415  * Initialize the LDAP connector module ... or don't, if we don't have LDAP.
416  */
417 CTDL_MODULE_INIT(ldap)
418 {
419 #ifdef HAVE_LDAP
420         CtdlRegisterCleanupHook(serv_ldap_cleanup);
421         CtdlRegisterDirectoryServiceFunc(CtdlDeleteFromLdap, DIRECTORY_USER_DEL, "ldap");
422         CtdlRegisterDirectoryServiceFunc(CtdlCreateLdapHostOU, DIRECTORY_CREATE_HOST, "ldap");
423         CtdlRegisterDirectoryServiceFunc(CtdlCreateLdapObject, DIRECTORY_CREATE_OBJECT, "ldap");
424         CtdlRegisterDirectoryServiceFunc(CtdlAddLdapAttr, DIRECTORY_ATTRIB_ADD, "ldap");
425         CtdlRegisterDirectoryServiceFunc(CtdlSaveLdapObject, DIRECTORY_SAVE_OBJECT, "ldap");
426         CtdlRegisterDirectoryServiceFunc(CtdlFreeLdapObject, DIRECTORY_FREE_OBJECT, "ldap");
427         
428
429         if (!IsEmptyStr(config.c_ldap_host)) {
430                 CtdlConnectToLdap();
431         }
432
433 #endif                          /* HAVE_LDAP */
434
435         /* return our Subversion id for the Log */
436         return "$Id$";
437 }