fa423ffb759d81a2856392af90709af299509f96
[citadel.git] / citadel / ldap.c
1 /*
2  * These functions implement the portions of AUTHMODE_LDAP and AUTHMODE_LDAP_AD which
3  * actually speak to the LDAP server.
4  *
5  * Copyright (c) 2011-2017 by the citadel.org development team.
6  *
7  * This program is open source software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License, version 3.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  */
15
16 int ctdl_require_ldap_version = 3;
17
18 #define _GNU_SOURCE             // Needed to suppress warning about vasprintf() when running on Linux/Linux
19 #include <stdio.h>
20 #include <libcitadel.h>
21 #include "citserver.h"
22 #include "citadel_ldap.h"
23 #include "ctdl_module.h"
24 #include "user_ops.h"
25 #include "internet_addressing.h"
26 #include "config.h"
27
28 #ifdef HAVE_LDAP
29 #define LDAP_DEPRECATED 1       // Suppress libldap's warning that we are using deprecated API calls
30 #include <ldap.h>
31
32
33 /*
34  * Utility function, supply a search result and get back the fullname (display name, common name, etc) from the first result
35  */
36 void derive_fullname_from_ldap_result(char *fullname, int fullname_size, LDAP *ldserver, LDAPMessage *search_result)
37 {
38         char **values;
39
40         if (fullname == NULL) return;
41
42         if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
43                 values = ldap_get_values(ldserver, search_result, "displayName");
44                 if (values) {
45                         if (values[0]) {
46                                 if (fullname) safestrncpy(fullname, values[0], fullname_size);
47                                 syslog(LOG_DEBUG, "ldap: displayName = %s", values[0]);
48                         }
49                         ldap_value_free(values);
50                 }
51         }
52         else {
53                 values = ldap_get_values(ldserver, search_result, "cn");
54                 if (values) {
55                         if (values[0]) {
56                                 if (fullname) safestrncpy(fullname, values[0], fullname_size);
57                                 syslog(LOG_DEBUG, "ldap: cn = %s", values[0]);
58                         }
59                         ldap_value_free(values);
60                 }
61         }
62         syslog(LOG_DEBUG, "\033[31mldap: display name: <%s> \033[0m", fullname);
63 }
64
65
66
67
68 /*
69  * Wrapper function for ldap_initialize() that consistently fills in the correct fields
70  */
71 int ctdl_ldap_initialize(LDAP **ld) {
72
73         char server_url[256];
74         int ret;
75
76         snprintf(server_url, sizeof server_url, "ldap://%s:%d", CtdlGetConfigStr("c_ldap_host"), CtdlGetConfigInt("c_ldap_port"));
77         ret = ldap_initialize(ld, server_url);
78         if (ret != LDAP_SUCCESS) {
79                 syslog(LOG_ERR, "ldap: could not connect to %s : %m", server_url);
80                 *ld = NULL;
81                 return(errno);
82         }
83
84         return(ret);
85 }
86
87
88 /*
89  * Look up a user in the directory to see if this is an account that can be authenticated
90  */
91 int CtdlTryUserLDAP(char *username,
92                 char *found_dn, int found_dn_size,
93                 char *fullname, int fullname_size,
94                 uid_t *uid, int lookup_based_on_username)
95 {
96         LDAP *ldserver = NULL;
97         int i;
98         LDAPMessage *search_result = NULL;
99         LDAPMessage *entry = NULL;
100         char searchstring[1024];
101         struct timeval tv;
102         char **values;
103         char *user_dn = NULL;
104
105         if (fullname) safestrncpy(fullname, username, fullname_size);
106
107         if (ctdl_ldap_initialize(&ldserver) != LDAP_SUCCESS) {
108                 return(errno);
109         }
110
111         ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
112         ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
113
114         striplt(CtdlGetConfigStr("c_ldap_bind_dn"));
115         striplt(CtdlGetConfigStr("c_ldap_bind_pw"));
116         syslog(LOG_DEBUG, "ldap: bind DN: %s", CtdlGetConfigStr("c_ldap_bind_dn"));
117         i = ldap_simple_bind_s(ldserver,
118                 (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_dn")) ? CtdlGetConfigStr("c_ldap_bind_dn") : NULL),
119                 (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_pw")) ? CtdlGetConfigStr("c_ldap_bind_pw") : NULL)
120         );
121         if (i != LDAP_SUCCESS) {
122                 syslog(LOG_ERR, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
123                 return(i);
124         }
125
126         tv.tv_sec = 10;
127         tv.tv_usec = 0;
128
129         if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
130                 if (lookup_based_on_username != 0)
131                         snprintf(searchstring, sizeof(searchstring), "(displayName=%s)",username);
132                 else
133                         snprintf(searchstring, sizeof(searchstring), "(sAMAccountName=%s)", username);
134         }
135         else {
136                 if (lookup_based_on_username != 0) {
137                         snprintf(searchstring, sizeof(searchstring), "(cn=%s)",username);
138                 }
139                 else {
140                         snprintf(searchstring, sizeof(searchstring), "(&(objectclass=posixAccount)(uid=%s))", username);
141                 }
142         }
143
144         syslog(LOG_DEBUG, "ldap: search: %s", searchstring);
145         (void) ldap_search_ext_s(
146                 ldserver,                                       /* ld                           */
147                 CtdlGetConfigStr("c_ldap_base_dn"),             /* base                         */
148                 LDAP_SCOPE_SUBTREE,                             /* scope                        */
149                 searchstring,                                   /* filter                       */
150                 NULL,                                           /* attrs (all attributes)       */
151                 0,                                              /* attrsonly (attrs + values)   */
152                 NULL,                                           /* serverctrls (none)           */
153                 NULL,                                           /* clientctrls (none)           */
154                 &tv,                                            /* timeout                      */
155                 1,                                              /* sizelimit (1 result max)     */
156                 &search_result                                  /* res                          */
157         );
158
159         /* Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
160          * the search succeeds.  Instead, we check to see whether search_result is still NULL.
161          */
162         if (search_result == NULL) {
163                 syslog(LOG_DEBUG, "ldap: zero search results were returned");
164                 ldap_unbind(ldserver);
165                 return(2);
166         }
167
168         /* At this point we've got at least one result from our query.  If there are multiple
169          * results, we still only look at the first one.
170          */
171         entry = ldap_first_entry(ldserver, search_result);
172         if (entry) {
173
174                 user_dn = ldap_get_dn(ldserver, entry);
175                 if (user_dn) {
176                         syslog(LOG_DEBUG, "ldap: dn = %s", user_dn);
177                 }
178
179                 derive_fullname_from_ldap_result(fullname, fullname_size, ldserver, search_result);
180
181                 /* If we know the username is the CN/displayName, we already set the uid*/
182                 if (lookup_based_on_username==0) {
183                         if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
184                                 values = ldap_get_values(ldserver, search_result, "objectGUID");
185                                 if (values) {
186                                         if (values[0]) {
187                                                 if (uid != NULL) {
188                                                         *uid = abs(HashLittle(values[0], strlen(values[0])));
189                                                         syslog(LOG_DEBUG, "ldap: uid hashed from objectGUID = %d", *uid);
190                                                 }
191                                         }
192                                         ldap_value_free(values);
193                                 }
194                         }
195                         else {
196                                 values = ldap_get_values(ldserver, search_result, "uidNumber");
197                                 if (values) {
198                                         if (values[0]) {
199                                                 syslog(LOG_DEBUG, "ldap: uidNumber = %s", values[0]);
200                                                 if (uid != NULL) {
201                                                         *uid = atoi(values[0]);
202                                                 }
203                                         }
204                                         ldap_value_free(values);
205                                 }
206                         }
207                 }
208
209         }
210
211         /* free the results */
212         ldap_msgfree(search_result);
213
214         /* unbind so we can go back in as the authenticating user */
215         ldap_unbind(ldserver);
216
217         if (!user_dn) {
218                 syslog(LOG_DEBUG, "ldap: No such user was found.");
219                 return(4);
220         }
221
222         if (found_dn) safestrncpy(found_dn, user_dn, found_dn_size);
223         ldap_memfree(user_dn);
224         return(0);
225 }
226
227
228 int CtdlTryPasswordLDAP(char *user_dn, const char *password)
229 {
230         LDAP *ldserver = NULL;
231         int i = (-1);
232
233         if (IsEmptyStr(password)) {
234                 syslog(LOG_DEBUG, "ldap: empty passwords are not permitted");
235                 return(1);
236         }
237
238         syslog(LOG_DEBUG, "ldap: trying to bind as %s", user_dn);
239         i = ctdl_ldap_initialize(&ldserver);
240         if (i == LDAP_SUCCESS) {
241                 ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
242                 i = ldap_simple_bind_s(ldserver, user_dn, password);
243                 if (i == LDAP_SUCCESS) {
244                         syslog(LOG_DEBUG, "ldap: bind succeeded");
245                 }
246                 else {
247                         syslog(LOG_DEBUG, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
248                 }
249                 ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
250                 ldap_unbind(ldserver);
251         }
252
253         if (i == LDAP_SUCCESS) {
254                 return(0);
255         }
256
257         return(1);
258 }
259
260
261 //return !0 iff property changed.
262 int vcard_set_props_iff_different(struct vCard *v,char *propname,int numvals, char **vals) {
263         int i;
264         char *oldval = "";
265         for(i=0;i<numvals;i++) {
266           oldval = vcard_get_prop(v,propname,0,i,0);
267           if (oldval == NULL) break;
268           if (strcmp(vals[i],oldval)) break;
269         }
270         if (i!=numvals) {
271                 syslog(LOG_DEBUG, "ldap: vcard property %s, element %d of %d changed from %s to %s\n", propname, i, numvals, oldval, vals[i]);
272                 for(i=0;i<numvals;i++) vcard_set_prop(v,propname,vals[i],(i==0) ? 0 : 1);
273                 return 1;
274         }
275         return 0;
276 }
277
278
279 //return !0 iff property changed.
280 int vcard_set_one_prop_iff_different(struct vCard *v,char *propname, char *newfmt, ...) {
281         va_list args;
282         char *newvalue;
283         int changed_something;
284         va_start(args,newfmt);
285         if (-1==vasprintf(&newvalue,newfmt,args)) {
286                 syslog(LOG_ERR, "ldap: out of memory!");
287                 return 0;
288         }
289         changed_something = vcard_set_props_iff_different(v,propname,1,&newvalue);
290         va_end(args);
291         free(newvalue);
292         return changed_something;
293 }
294
295
296 /*
297  * Learn LDAP attributes and stuff them into the vCard.
298  * Returns nonzero if we changed anything.
299  */
300 int Ctdl_LDAP_to_vCard(char *ldap_dn, struct vCard *v)
301 {
302         int changed_something = 0;
303         LDAP *ldserver = NULL;
304         int i;
305         struct timeval tv;
306         LDAPMessage *search_result = NULL;
307         LDAPMessage *entry = NULL;
308         char **givenName;
309         char **sn;
310         char **cn;
311         char **initials;
312         char **o;
313         char **street;
314         char **l;
315         char **st;
316         char **postalCode;
317         char **telephoneNumber;
318         char **mobile;
319         char **homePhone;
320         char **facsimileTelephoneNumber;
321         char **mail;
322         char **uid;
323         char **homeDirectory;
324         char **uidNumber;
325         char **loginShell;
326         char **gidNumber;
327         char **c;
328         char **title;
329         char **uuid;
330         char *attrs[] = { "*","+",NULL};
331
332         if (!ldap_dn) return(0);
333         if (!v) return(0);
334
335         if (ctdl_ldap_initialize(&ldserver) != LDAP_SUCCESS) {
336                 return(0);
337         }
338
339         ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
340         ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
341
342         striplt(CtdlGetConfigStr("c_ldap_bind_dn"));
343         striplt(CtdlGetConfigStr("c_ldap_bind_pw"));
344         syslog(LOG_DEBUG, "ldap: bind DN: %s", CtdlGetConfigStr("c_ldap_bind_dn"));
345         i = ldap_simple_bind_s(ldserver,
346                 (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_dn")) ? CtdlGetConfigStr("c_ldap_bind_dn") : NULL),
347                 (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_pw")) ? CtdlGetConfigStr("c_ldap_bind_pw") : NULL)
348         );
349         if (i != LDAP_SUCCESS) {
350                 syslog(LOG_ERR, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
351                 return(0);
352         }
353
354         tv.tv_sec = 10;
355         tv.tv_usec = 0;
356
357         syslog(LOG_DEBUG, "ldap: search: %s", ldap_dn);
358         (void) ldap_search_ext_s(
359                 ldserver,                               // ld
360                 ldap_dn,                                // base
361                 LDAP_SCOPE_SUBTREE,                     // scope
362                 NULL,                                   // filter
363                 attrs,                                  // attrs (all attributes)
364                 0,                                      // attrsonly (attrs + values)
365                 NULL,                                   // serverctrls (none)
366                 NULL,                                   // clientctrls (none)
367                 &tv,                                    // timeout
368                 1,                                      // sizelimit (1 result max)
369                 &search_result                          // res
370         );
371         
372         /* Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
373          * the search succeeds.  Instead, we check to see whether search_result is still NULL.
374          */
375         if (search_result == NULL) {
376                 syslog(LOG_DEBUG, "ldap: zero search results were returned");
377                 ldap_unbind(ldserver);
378                 return(0);
379         }
380
381         /* At this point we've got at least one result from our query.  If there are multiple
382          * results, we still only look at the first one.
383          */
384         entry = ldap_first_entry(ldserver, search_result);
385         if (entry) {
386                 syslog(LOG_DEBUG, "ldap: search got user details for vcard.");
387                 givenName=ldap_get_values(ldserver, search_result, "givenName");
388                 sn=ldap_get_values(ldserver, search_result, "sn");
389                 cn=ldap_get_values(ldserver, search_result, "cn");
390                 initials=ldap_get_values(ldserver, search_result, "initials");
391                 title=ldap_get_values(ldserver, search_result, "title");
392                 o=ldap_get_values(ldserver, search_result, "o");
393                 street=ldap_get_values(ldserver, search_result, "street");
394                 l=ldap_get_values(ldserver, search_result, "l");
395                 st=ldap_get_values(ldserver, search_result, "st");
396                 postalCode=ldap_get_values(ldserver, search_result, "postalCode");
397                 telephoneNumber=ldap_get_values(ldserver, search_result, "telephoneNumber");
398                 mobile=ldap_get_values(ldserver, search_result, "mobile");
399                 homePhone=ldap_get_values(ldserver, search_result, "homePhone");
400                 facsimileTelephoneNumber=ldap_get_values(ldserver, search_result, "facsimileTelephoneNumber");
401                 mail=ldap_get_values(ldserver, search_result, "mail");
402                 uid=ldap_get_values(ldserver, search_result, "uid");
403                 homeDirectory=ldap_get_values(ldserver, search_result, "homeDirectory");
404                 uidNumber=ldap_get_values(ldserver, search_result, "uidNumber");
405                 loginShell=ldap_get_values(ldserver, search_result, "loginShell");
406                 gidNumber=ldap_get_values(ldserver, search_result, "gidNumber");
407                 c=ldap_get_values(ldserver, search_result, "c");
408                 uuid=ldap_get_values(ldserver, search_result, "entryUUID");
409
410                 if (street && l && st && postalCode && c) changed_something |= vcard_set_one_prop_iff_different(v,"adr",";;%s;%s;%s;%s;%s",street[0],l[0],st[0],postalCode[0],c[0]);
411                 if (telephoneNumber) changed_something |= vcard_set_one_prop_iff_different(v,"tel;work","%s",telephoneNumber[0]);
412                 if (facsimileTelephoneNumber) changed_something |= vcard_set_one_prop_iff_different(v,"tel;fax","%s",facsimileTelephoneNumber[0]);
413                 if (mobile) changed_something |= vcard_set_one_prop_iff_different(v,"tel;cell","%s",mobile[0]);
414                 if (homePhone) changed_something |= vcard_set_one_prop_iff_different(v,"tel;home","%s",homePhone[0]);
415                 if (givenName && sn) {
416                         if (initials) {
417                                 changed_something |= vcard_set_one_prop_iff_different(v,"n","%s;%s;%s",sn[0],givenName[0],initials[0]);
418                         }
419                         else {
420                                 changed_something |= vcard_set_one_prop_iff_different(v,"n","%s;%s",sn[0],givenName[0]);
421                         }
422                 }
423                 if (mail) {
424                         changed_something |= vcard_set_props_iff_different(v,"email;internet",ldap_count_values(mail),mail);
425                 }
426                 if (uuid) changed_something |= vcard_set_one_prop_iff_different(v,"X-uuid","%s",uuid[0]);
427                 if (o) changed_something |= vcard_set_one_prop_iff_different(v,"org","%s",o[0]);
428                 if (cn) changed_something |= vcard_set_one_prop_iff_different(v,"fn","%s",cn[0]);
429                 if (title) changed_something |= vcard_set_one_prop_iff_different(v,"title","%s",title[0]);
430                 
431                 if (givenName) ldap_value_free(givenName);
432                 if (initials) ldap_value_free(initials);
433                 if (sn) ldap_value_free(sn);
434                 if (cn) ldap_value_free(cn);
435                 if (o) ldap_value_free(o);
436                 if (street) ldap_value_free(street);
437                 if (l) ldap_value_free(l);
438                 if (st) ldap_value_free(st);
439                 if (postalCode) ldap_value_free(postalCode);
440                 if (telephoneNumber) ldap_value_free(telephoneNumber);
441                 if (mobile) ldap_value_free(mobile);
442                 if (homePhone) ldap_value_free(homePhone);
443                 if (facsimileTelephoneNumber) ldap_value_free(facsimileTelephoneNumber);
444                 if (mail) ldap_value_free(mail);
445                 if (uid) ldap_value_free(uid);
446                 if (homeDirectory) ldap_value_free(homeDirectory);
447                 if (uidNumber) ldap_value_free(uidNumber);
448                 if (loginShell) ldap_value_free(loginShell);
449                 if (gidNumber) ldap_value_free(gidNumber);
450                 if (c) ldap_value_free(c);
451                 if (title) ldap_value_free(title);
452                 if (uuid) ldap_value_free(uuid);
453         }
454         /* free the results */
455         ldap_msgfree(search_result);
456
457         /* unbind so we can go back in as the authenticating user */
458         ldap_unbind(ldserver);
459         return(changed_something);      /* tell the caller whether we made any changes */
460 }
461
462
463 /*
464  * Extract a user's Internet email addresses from LDAP.
465  * Returns zero if we got a valid set of addresses; nonzero for error.
466  */
467 int extract_email_addresses_from_ldap(char *ldap_dn, char *emailaddrs)
468 {
469         LDAP *ldserver = NULL;
470         int i;
471         struct timeval tv;
472         LDAPMessage *search_result = NULL;
473         LDAPMessage *entry = NULL;
474         char **mail;
475         char *attrs[] = { "*","+",NULL};
476
477         if (!ldap_dn) return(1);
478         if (!emailaddrs) return(1);
479
480         if (ctdl_ldap_initialize(&ldserver) != LDAP_SUCCESS) {
481                 return(2);
482         }
483
484         ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
485         ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
486
487         striplt(CtdlGetConfigStr("c_ldap_bind_dn"));
488         striplt(CtdlGetConfigStr("c_ldap_bind_pw"));
489         syslog(LOG_DEBUG, "ldap: bind DN: %s", CtdlGetConfigStr("c_ldap_bind_dn"));
490         i = ldap_simple_bind_s(ldserver,
491                 (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_dn")) ? CtdlGetConfigStr("c_ldap_bind_dn") : NULL),
492                 (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_pw")) ? CtdlGetConfigStr("c_ldap_bind_pw") : NULL)
493         );
494         if (i != LDAP_SUCCESS) {
495                 syslog(LOG_ERR, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
496                 return(3);
497         }
498
499         tv.tv_sec = 10;
500         tv.tv_usec = 0;
501
502         syslog(LOG_DEBUG, "ldap: search: %s", ldap_dn);
503         (void) ldap_search_ext_s(
504                 ldserver,                               // ld
505                 ldap_dn,                                // base
506                 LDAP_SCOPE_SUBTREE,                     // scope
507                 NULL,                                   // filter
508                 attrs,                                  // attrs (all attributes)
509                 0,                                      // attrsonly (attrs + values)
510                 NULL,                                   // serverctrls (none)
511                 NULL,                                   // clientctrls (none)
512                 &tv,                                    // timeout
513                 1,                                      // sizelimit (1 result max)
514                 &search_result                          // res
515         );
516         
517         /* Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
518          * the search succeeds.  Instead, we check to see whether search_result is still NULL.
519          */
520         if (search_result == NULL) {
521                 syslog(LOG_DEBUG, "ldap: zero search results were returned");
522                 ldap_unbind(ldserver);
523                 return(4);
524         }
525
526         /* At this point we've got at least one result from our query.  If there are multiple
527          * results, we still only look at the first one.
528          */
529         emailaddrs[0] = 0;      /* clear out any previous results */
530         entry = ldap_first_entry(ldserver, search_result);
531         if (entry) {
532                 syslog(LOG_DEBUG, "ldap: search got user details");
533                 mail=ldap_get_values(ldserver, search_result, "mail");
534
535                 if (mail) {
536                         int q;
537                         for (q=0; q<ldap_count_values(mail); ++q) {
538                                 if (IsDirectory(mail[q], 0)) {
539                                         syslog(LOG_DEBUG, "\035FIXME YES DIRECTORY %s\033[0m", mail[q]);
540                                         if ((strlen(emailaddrs) + strlen(mail[q]) + 2) > 512) {
541                                                 syslog(LOG_ERR, "ldap: can't fit all email addresses into user record");
542                                         }
543                                         else {
544                                                 if (!IsEmptyStr(emailaddrs)) {
545                                                         strcat(emailaddrs, "|");
546                                                 }
547                                                 strcat(emailaddrs, mail[q]);
548                                         }
549                                 }
550                         }
551                 }
552         }
553
554         /* free the results */
555         ldap_msgfree(search_result);
556
557         /* unbind so we can go back in as the authenticating user */
558         ldap_unbind(ldserver);
559         return(0);
560 }
561
562
563 /*
564  * Scan LDAP for users and populate Citadel's user database with everyone
565  */
566 void CtdlSynchronizeUsersFromLDAP(void)
567 {
568         LDAP *ldserver = NULL;
569         int i;
570         LDAPMessage *search_result = NULL;
571         LDAPMessage *entry = NULL;
572         char *user_dn = NULL;
573         char searchstring[1024];
574         struct timeval tv;
575         char **values;
576
577         if ((CtdlGetConfigInt("c_auth_mode") != AUTHMODE_LDAP) && (CtdlGetConfigInt("c_auth_mode") != AUTHMODE_LDAP_AD)) {
578                 return;         // not running LDAP
579         }
580
581         syslog(LOG_INFO, "ldap: synchronizing Citadel user database from LDAP");
582
583         if (ctdl_ldap_initialize(&ldserver) != LDAP_SUCCESS) {
584                 return;
585         }
586
587         ldap_set_option(ldserver, LDAP_OPT_PROTOCOL_VERSION, &ctdl_require_ldap_version);
588         ldap_set_option(ldserver, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF);
589
590         striplt(CtdlGetConfigStr("c_ldap_bind_dn"));
591         striplt(CtdlGetConfigStr("c_ldap_bind_pw"));
592         syslog(LOG_DEBUG, "ldap: bind DN: %s", CtdlGetConfigStr("c_ldap_bind_dn"));
593         i = ldap_simple_bind_s(ldserver,
594                 (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_dn")) ? CtdlGetConfigStr("c_ldap_bind_dn") : NULL),
595                 (!IsEmptyStr(CtdlGetConfigStr("c_ldap_bind_pw")) ? CtdlGetConfigStr("c_ldap_bind_pw") : NULL)
596         );
597         if (i != LDAP_SUCCESS) {
598                 syslog(LOG_ERR, "ldap: Cannot bind: %s (%d)", ldap_err2string(i), i);
599                 return;
600         }
601
602         tv.tv_sec = 10;
603         tv.tv_usec = 0;
604
605         if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
606                         snprintf(searchstring, sizeof(searchstring), "(&(objectClass=user)(objectClass=person)(!(objectClass=computer)))");
607         } else {
608                         snprintf(searchstring, sizeof(searchstring), "(objectClass=inetOrgPerson)");
609         }
610
611         syslog(LOG_DEBUG, "ldap: search: %s", searchstring);
612         (void) ldap_search_ext_s(
613                 ldserver,                                       // ld
614                 CtdlGetConfigStr("c_ldap_base_dn"),             // base
615                 LDAP_SCOPE_SUBTREE,                             // scope
616                 searchstring,                                   // filter
617                 NULL,                                           // attrs (all attributes)
618                 0,                                              // attrsonly (attrs + values)
619                 NULL,                                           // serverctrls (none)
620                 NULL,                                           // clientctrls (none)
621                 &tv,                                            // timeout
622                 INT_MAX,                                        // sizelimit (max)
623                 &search_result                                  // result
624         );
625
626         /* Ignore the return value of ldap_search_ext_s().  Sometimes it returns an error even when
627          * the search succeeds.  Instead, we check to see whether search_result is still NULL.
628          */
629         if (search_result == NULL) {
630                 syslog(LOG_DEBUG, "ldap: zero search results were returned");
631                 ldap_unbind(ldserver);
632                 return;
633         }
634
635         syslog(LOG_DEBUG, "ldap: %d entries returned", ldap_count_entries(ldserver, search_result));
636         entry = ldap_first_entry(ldserver, search_result);
637         while (entry) {
638
639                 user_dn = ldap_get_dn(ldserver, entry);
640                 if (user_dn) {
641                         syslog(LOG_DEBUG, "ldap: found %s", user_dn);
642
643                         int fullname_size = 256;
644                         char fullname[256] = { 0 } ;
645                         uid_t uid = (-1);
646
647                         derive_fullname_from_ldap_result(fullname, fullname_size, ldserver, entry);
648
649                         if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD) {
650                                 values = ldap_get_values(ldserver, entry, "objectGUID");        // AD schema: uid hashed from objectGUID
651                                 if (values) {
652                                         if (values[0]) {
653                                                 uid = abs(HashLittle(values[0], strlen(values[0])));
654                                         }
655                                         ldap_value_free(values);
656                                 }
657                         }
658                         else {
659                                 values = ldap_get_values(ldserver, entry, "uidNumber");         // POSIX schema: uid = uidNumber
660                                 if (values) {
661                                         if (values[0]) {
662                                                 uid = atoi(values[0]);
663                                         }
664                                         ldap_value_free(values);
665                                 }
666                         }
667
668                         syslog(LOG_DEBUG, "\033[33mldap: display name: <%s> , uid = <%d>\033[0m", fullname, uid);
669
670                         // FIXME now create or update the user
671
672
673                 }
674
675                 entry = ldap_next_entry(ldserver, entry);
676         }
677
678         /* free the results */
679         ldap_msgfree(search_result);
680
681         /* unbind so we can go back in as the authenticating user */
682         ldap_unbind(ldserver);
683 }
684
685 #endif /* HAVE_LDAP */