1 /* ldap.c - LDAP Backend to ptloader
2  *
3  * Copyright (c) 1994-2008 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  */
42 
43 #include <sysexits.h>
44 #include <syslog.h>
45 #include <config.h>
46 #include "ptloader.h"
47 #include "util.h"
48 #include "assert.h"
49 
50 #ifdef HAVE_LDAP
51 
52 #include <ctype.h>
53 #include <string.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <sys/types.h>
57 #include <sys/stat.h>
58 #include <sys/types.h>
59 #include <sys/param.h>
60 #include <fcntl.h>
61 #include <sys/socket.h>
62 #include <sys/un.h>
63 #include <sys/uio.h>
64 
65 /* Functions like ldap_bind() have been deprecated in OpenLDAP 2.3 */
66 #define LDAP_DEPRECATED 1
67 
68 #include <ldap.h>
69 #include <lber.h>
70 
71 /* libimap */
72 #include "imap/global.h"
73 
74 /* libconfig */
75 #include "libconfig.h"
76 
77 /* libcyrus */
78 #include "auth_pts.h"
79 #include "strhash.h"
80 #include "xmalloc.h"
81 #include "xstrlcat.h"
82 
83 /* xxx this just uses the UNIX canonicalization semantics, which is
84  * most likely wrong */
85 
86 /* Map of which characters are allowed by auth_canonifyid.
87  * Key: 0 -> not allowed (special, ctrl, or would confuse Unix or imapd)
88  *      1 -> allowed, but requires an alpha somewhere else in the string
89  *      2 -> allowed, and is an alpha
90  *
91  * At least one character must be an alpha.
92  *
93  * This may not be restrictive enough.
94  * Here are the reasons for the restrictions:
95  *
96  * &    forbidden because of MUTF-7.  (This could be fixed.)
97  * :    forbidden because it's special in /etc/passwd
98  * /    forbidden because it can't be used in a mailbox name
99  * * %  forbidden because they're IMAP magic in the LIST/LSUB commands
100  * ?    it just scares me
101  * ctrl chars, DEL
102  *      can't send them as IMAP characters in plain folder names, I think
103  * 80-FF forbidden because you can't send them in IMAP anyway
104  *       (and they're forbidden as folder names). (This could be fixed.)
105  *
106  * + and - are *allowed* although '+' is probably used for userid+detail
107  * subaddressing and qmail users use '-' for subaddressing.
108  *
109  * Identifiers don't require a digit, really, so that should probably be
110  * relaxed, too.
111  */
112 static char allowedchars[256] = {
113  /* 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F */
114     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00-0F */
115     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10-1F */
116     1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, /* 20-2F */
117     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, /* 30-3F */
118 
119     1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 40-4F */
120     2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, /* 50-5F */
121     1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 60-6F */
122     2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, /* 70-7F */
123 
124     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
125     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
126     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
127     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
128 
129     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
130     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
131     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
132     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
133 };
134 
135 typedef struct _ptsm {
136     const char      *uri;
137     int             version;
138     struct timeval  timeout;
139     int             size_limit;
140     int             time_limit;
141     int             deref;
142     int             referrals;
143     int             restart;
144     int             scope;
145     const char      *base;
146     int             sasl;
147     const char      *id;
148     const char      *bind_dn;
149     const char      *password;
150     const char      *authz;
151     const char      *mech;
152     const char      *realm;
153     const char      *filter;
154     const char      *sasl_secprops;
155     int             start_tls;
156     int             tls_check_peer;
157     const char      *tls_cacert_file;
158     const char      *tls_cacert_dir;
159     const char      *tls_ciphers;
160     const char      *tls_cert;
161     const char      *tls_key;
162     int             member_method;
163     const char      *user_attribute;
164     const char      *member_attribute;
165     const char      *member_filter;
166     const char      *member_base;
167     int             member_scope;
168     const char      *group_filter;
169     const char      *group_base;
170     int             group_scope;
171 
172     /* Used for domain name space -> root dn discovery */
173     const char      *domain_base_dn;
174     const char      *domain_filter;
175     const char      *domain_name_attribute;
176     int             domain_scope;
177     const char      *domain_result_attribute;
178 
179     LDAP            *ld;
180 } t_ptsm;
181 
182 #define PTSM_OK 0
183 #define PTSM_FAIL -1
184 #define PTSM_NOMEM -2
185 #define PTSM_RETRY -3
186 
187 #define PTSM_MEMBER_METHOD_ATTRIBUTE 0
188 #define PTSM_MEMBER_METHOD_FILTER 1
189 
190 #define ISSET(x)  ((x != NULL) && (*(x) != '\0'))
191 #define EMPTY(x)  ((x == NULL) || (*(x) == '\0'))
192 
193 static t_ptsm *ptsm = NULL;
194 
ptsmodule_interact(LDAP * ld,unsigned flags,void * def,void * inter)195 static int ptsmodule_interact(
196     LDAP *ld __attribute__((unused)),
197     unsigned flags __attribute__((unused)),
198     void *def,
199     void *inter)
200 {
201     sasl_interact_t *in = inter;
202     const char *p;
203     t_ptsm *ptsmdef = def;
204 
205     for (;in->id != SASL_CB_LIST_END;in++) {
206         p = NULL;
207         switch(in->id) {
208             case SASL_CB_AUTHNAME:
209                 if (ISSET(ptsmdef->id))
210                     p = ptsmdef->id;
211                 break;
212             case SASL_CB_USER:
213                 if (ISSET(ptsmdef->authz))
214                     p = ptsmdef->authz;
215                 break;
216             case SASL_CB_GETREALM:
217                 if (ISSET(ptsmdef->realm))
218                     p = ptsmdef->realm;
219                 break;
220             case SASL_CB_PASS:
221                 if (ISSET(ptsmdef->password))
222                     p = ptsmdef->password;
223                 break;
224         }
225 
226         in->result = p ? p : "";
227         in->len = strlen(in->result);
228     }
229 
230     return LDAP_SUCCESS;
231 }
232 
233 /*
234  * Convert 'identifier' into canonical form.
235  * Returns a pointer to a static buffer containing the canonical form
236  * or NULL if 'identifier' is invalid.
237  *
238  * XXX If any of the characters marked with 0 are valid and are cropping up,
239  * the right thing to do is probably to canonicalize the identifier to two
240  * representations: one for getpwent calls and one for folder names.  The
241  * latter canonicalizes to a MUTF7 representation.
242  */
ptsmodule_canonifyid(const char * identifier,size_t len)243 static char *ptsmodule_canonifyid(const char *identifier, size_t len)
244 {
245     static char retbuf[81];
246     char sawalpha;
247     char *p;
248     int username_tolower = 0;
249     int i = 0;
250 
251     if(!len) len = strlen(identifier);
252     if(len >= sizeof(retbuf)) return NULL;
253 
254     memcpy(retbuf, identifier, len);
255     retbuf[len] = '\0';
256 
257     if (!strncmp(retbuf, "group:", 6))
258         i = 6;
259 
260     /* Copy the string and look up values in the allowedchars array above.
261      * If we see any we don't like, reject the string.
262      * Lowercase usernames if requested.
263      */
264     username_tolower = config_getswitch(IMAPOPT_USERNAME_TOLOWER);
265     sawalpha = 0;
266     for(p = retbuf+i; *p; p++) {
267         if (username_tolower && Uisupper(*p))
268             *p = tolower((unsigned char)*p);
269 
270         switch (allowedchars[*(unsigned char*) p]) {
271         case 0:
272             return NULL;
273 
274         case 2:
275             sawalpha = 1;
276             /* FALL THROUGH */
277 
278         default:
279             ;
280         }
281     }
282 
283     if (!sawalpha) return NULL;  /* has to be one alpha char */
284 
285     return retbuf;
286 }
287 
288 
ptsmodule_connect(void)289 static int ptsmodule_connect(void)
290 {
291         int rc = 0;
292 
293         if (ptsm == NULL)  // Sanity Check
294                 return PTSM_FAIL;
295 
296         if (ptsm->ld != NULL)
297                 return PTSM_OK;
298 
299         if (ISSET(ptsm->tls_cacert_file)) {
300                 rc = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTFILE, ptsm->tls_cacert_file);
301                 if (rc != LDAP_SUCCESS) {
302                         syslog(LOG_WARNING, "Unable to set LDAP_OPT_X_TLS_CACERTFILE (%s).", ldap_err2string (rc));
303                 }
304         }
305 
306         if (ISSET(ptsm->tls_cacert_dir)) {
307                 rc = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTDIR, ptsm->tls_cacert_dir);
308                 if (rc != LDAP_SUCCESS) {
309                         syslog(LOG_WARNING, "Unable to set LDAP_OPT_X_TLS_CACERTDIR (%s).", ldap_err2string (rc));
310                 }
311         }
312 
313         if (ptsm->tls_check_peer != 0) {
314                 rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &ptsm->tls_check_peer);
315                 if (rc != LDAP_SUCCESS) {
316                         syslog(LOG_WARNING, "Unable to set LDAP_OPT_X_TLS_REQUIRE_CERT (%s).", ldap_err2string (rc));
317                 }
318         }
319 
320         if (ISSET(ptsm->tls_ciphers)) {
321                 /* set cipher suite, certificate and private key: */
322                 rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CIPHER_SUITE, ptsm->tls_ciphers);
323                 if (rc != LDAP_SUCCESS) {
324                         syslog(LOG_WARNING, "Unable to set LDAP_OPT_X_TLS_CIPHER_SUITE (%s).", ldap_err2string (rc));
325                 }
326         }
327 
328         if (ISSET(ptsm->tls_cert)) {
329                 rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CERTFILE, ptsm->tls_cert);
330                 if (rc != LDAP_SUCCESS) {
331                         syslog(LOG_WARNING, "Unable to set LDAP_OPT_X_TLS_CERTFILE (%s).", ldap_err2string (rc));
332                 }
333         }
334 
335         if (ISSET(ptsm->tls_key)) {
336                 rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_KEYFILE, ptsm->tls_key);
337                 if (rc != LDAP_SUCCESS) {
338                         syslog(LOG_WARNING, "Unable to set LDAP_OPT_X_TLS_KEYFILE (%s).", ldap_err2string (rc));
339                 }
340         }
341 
342         rc = ldap_initialize(&ptsm->ld, ptsm->uri);
343         if (rc != LDAP_SUCCESS) {
344                 syslog(LOG_ERR, "ldap_initialize failed (%s)", ptsm->uri);
345                 return PTSM_FAIL;
346         }
347 
348         rc = ldap_set_option(ptsm->ld, LDAP_OPT_PROTOCOL_VERSION, &(ptsm->version));
349         if (rc != LDAP_OPT_SUCCESS) {
350 
351                 if (ptsm->sasl ||
352                     ptsm->start_tls) {
353                         syslog(LOG_ERR, "Failed to set LDAP_OPT_PROTOCOL_VERSION %d, required for ldap_start_tls and ldap_sasl.", ptsm->version);
354                         ldap_unbind(ptsm->ld);
355             ptsm->ld = NULL;
356                         return PTSM_FAIL;
357                 } else
358                         syslog(LOG_WARNING, "Unable to set LDAP_OPT_PROTOCOL_VERSION %d.", ptsm->version);
359 
360                 ptsm->version = LDAP_VERSION2;
361         }
362 
363         rc = ldap_set_option(ptsm->ld, LDAP_OPT_NETWORK_TIMEOUT, &(ptsm->timeout));
364         if (rc != LDAP_OPT_SUCCESS) {
365                 syslog(LOG_WARNING, "Unable to set LDAP_OPT_NETWORK_TIMEOUT %ld.%06d.",
366                        (long)ptsm->timeout.tv_sec, (int)ptsm->timeout.tv_usec);
367         }
368 
369         rc = ldap_set_option(ptsm->ld, LDAP_OPT_SIZELIMIT, &(ptsm->size_limit));
370         if (rc != LDAP_OPT_SUCCESS) {
371                 syslog(LOG_WARNING, "Unable to set LDAP_OPT_SIZELIMIT %d.", ptsm->size_limit);
372         }
373 
374         rc = ldap_set_option(ptsm->ld, LDAP_OPT_TIMELIMIT, &(ptsm->time_limit));
375         if (rc != LDAP_OPT_SUCCESS) {
376                 syslog(LOG_WARNING, "Unable to set LDAP_OPT_TIMELIMIT %d.", ptsm->time_limit);
377         }
378 
379         rc = ldap_set_option(ptsm->ld, LDAP_OPT_DEREF, &(ptsm->deref));
380         if (rc != LDAP_OPT_SUCCESS) {
381                 syslog(LOG_WARNING, "Unable to set LDAP_OPT_DEREF %d.", ptsm->deref);
382         }
383 
384         rc = ldap_set_option(ptsm->ld, LDAP_OPT_REFERRALS, ptsm->referrals ? LDAP_OPT_ON : LDAP_OPT_OFF);
385         if (rc != LDAP_OPT_SUCCESS) {
386                 syslog(LOG_WARNING, "Unable to set LDAP_OPT_REFERRALS.");
387         }
388 
389         rc = ldap_set_option(ptsm->ld, LDAP_OPT_RESTART, ptsm->restart ? LDAP_OPT_ON : LDAP_OPT_OFF);
390         if (rc != LDAP_OPT_SUCCESS) {
391                 syslog(LOG_WARNING, "Unable to set LDAP_OPT_RESTART.");
392         }
393 
394         if (ptsm->start_tls) {
395 
396                 rc = ldap_start_tls_s(ptsm->ld, NULL, NULL);
397                 if (rc != LDAP_SUCCESS) {
398                         syslog(LOG_ERR, "start tls failed (%s).", ldap_err2string(rc));
399                         ldap_unbind(ptsm->ld);
400             ptsm->ld = NULL;
401                         return PTSM_FAIL;
402                 }
403         }
404 
405         if (ptsm->sasl) {
406 
407                 if (EMPTY(ptsm->mech))
408                         ldap_get_option(ptsm->ld, LDAP_OPT_X_SASL_MECH, &(ptsm->mech));
409 
410                 if (EMPTY(ptsm->realm))
411                         ldap_get_option(ptsm->ld, LDAP_OPT_X_SASL_REALM, &(ptsm->realm));
412 
413                 if (ISSET(ptsm->sasl_secprops)) {
414                         rc = ldap_set_option(ptsm->ld, LDAP_OPT_X_SASL_SECPROPS, (void *) ptsm->sasl_secprops);
415                         if( rc != LDAP_OPT_SUCCESS ) {
416                                 syslog(LOG_ERR, "Unable to set LDAP_OPT_X_SASL_SECPROPS.");
417                                 ldap_unbind(ptsm->ld);
418                 ptsm->ld = NULL;
419                                 return PTSM_FAIL;
420                         }
421                 }
422 
423                 rc = ldap_sasl_interactive_bind_s(
424                         ptsm->ld,
425                         ptsm->bind_dn,
426                         ptsm->mech,
427                         NULL,
428                         NULL,
429                         LDAP_SASL_QUIET,
430                         ptsmodule_interact,
431                         ptsm);
432         } else
433                 rc = ldap_simple_bind_s(ptsm->ld, ptsm->bind_dn, ptsm->password);
434 
435     if (rc != LDAP_SUCCESS) {
436         syslog(LOG_ERR,
437                (ptsm->sasl ? "ldap_sasl_interactive_bind() failed %d (%s)." : "ldap_simple_bind() failed %d (%s)."), rc, ldap_err2string(rc));
438         ldap_unbind(ptsm->ld);
439         ptsm->ld = NULL;
440         return (rc == LDAP_SERVER_DOWN ? PTSM_RETRY : PTSM_FAIL);
441     }
442 
443     return PTSM_OK;
444 }
445 
446 /* API */
447 
myinit(void)448 static void myinit(void)
449 {
450     const char *p = NULL;
451 
452     if (ptsm)
453         return; // Already configured
454 
455     ptsm = xmalloc(sizeof(t_ptsm));
456     if (ptsm == NULL) {
457         fatal("xmalloc() failed", EX_CONFIG);
458     }
459 
460     ptsm->uri = config_getstring(IMAPOPT_LDAP_URI);
461 
462     ptsm->version = (config_getint(IMAPOPT_LDAP_VERSION) == 2 ? LDAP_VERSION2 : LDAP_VERSION3);
463     ptsm->timeout.tv_sec = config_getduration(IMAPOPT_LDAP_TIMEOUT, 's');
464     ptsm->timeout.tv_usec = 0;
465     ptsm->restart = config_getswitch(IMAPOPT_LDAP_RESTART);
466 
467     p = config_getstring(IMAPOPT_LDAP_DEREF);
468 
469     if (!strcasecmp(p, "search")) {
470         ptsm->deref = LDAP_DEREF_SEARCHING;
471     } else if (!strcasecmp(p, "find")) {
472         ptsm->deref = LDAP_DEREF_FINDING;
473     } else if (!strcasecmp(p, "always")) {
474         ptsm->deref = LDAP_DEREF_ALWAYS;
475     } else {
476         ptsm->deref = LDAP_DEREF_NEVER;
477     }
478 
479     ptsm->referrals = config_getswitch(IMAPOPT_LDAP_REFERRALS);
480     ptsm->size_limit = config_getint(IMAPOPT_LDAP_SIZE_LIMIT);
481     ptsm->time_limit = config_getduration(IMAPOPT_LDAP_TIME_LIMIT, 's');
482 
483     p = config_getstring(IMAPOPT_LDAP_SCOPE);
484 
485     if (!strcasecmp(p, "one")) {
486         ptsm->scope = LDAP_SCOPE_ONELEVEL;
487     } else if (!strcasecmp(p, "base")) {
488         ptsm->scope = LDAP_SCOPE_BASE;
489     } else {
490         ptsm->scope = LDAP_SCOPE_SUBTREE;
491     }
492 
493     ptsm->bind_dn = config_getstring(IMAPOPT_LDAP_BIND_DN);
494     ptsm->sasl = config_getswitch(IMAPOPT_LDAP_SASL);
495     ptsm->id = config_getstring(IMAPOPT_LDAP_ID);
496     ptsm->authz = config_getstring(IMAPOPT_LDAP_AUTHZ);
497     ptsm->mech = config_getstring(IMAPOPT_LDAP_MECH);
498     ptsm->realm = config_getstring(IMAPOPT_LDAP_REALM);
499     ptsm->password = config_getstring(IMAPOPT_LDAP_PASSWORD);
500     ptsm->start_tls = config_getswitch(IMAPOPT_LDAP_START_TLS);
501     ptsm->tls_check_peer = config_getswitch(IMAPOPT_LDAP_VERIFY_PEER);
502     ptsm->tls_cacert_file = config_getstring(IMAPOPT_LDAP_CA_FILE);
503     ptsm->tls_cacert_dir = config_getstring(IMAPOPT_LDAP_CA_DIR);
504     ptsm->tls_ciphers = config_getstring(IMAPOPT_LDAP_CIPHERS);
505     ptsm->tls_cert = config_getstring(IMAPOPT_LDAP_CLIENT_CERT);
506     ptsm->tls_key = config_getstring(IMAPOPT_LDAP_CLIENT_KEY);
507 
508     p = config_getstring(IMAPOPT_LDAP_MEMBER_METHOD);
509     if (!strcasecmp(p, "filter")) {
510         ptsm->member_method = PTSM_MEMBER_METHOD_FILTER;
511     } else {
512         ptsm->member_method = PTSM_MEMBER_METHOD_ATTRIBUTE;
513     }
514 
515     p = config_getstring(IMAPOPT_LDAP_MEMBER_SCOPE);
516     if (!strcasecmp(p, "one")) {
517         ptsm->member_scope = LDAP_SCOPE_ONELEVEL;
518     } else if (!strcasecmp(p, "base")) {
519         ptsm->member_scope = LDAP_SCOPE_BASE;
520     } else {
521         ptsm->member_scope = LDAP_SCOPE_SUBTREE;
522     }
523 
524     ptsm->member_filter = config_getstring(IMAPOPT_LDAP_MEMBER_FILTER);
525     ptsm->member_base = config_getstring(IMAPOPT_LDAP_MEMBER_BASE);
526     ptsm->member_attribute = (config_getstring(IMAPOPT_LDAP_MEMBER_ATTRIBUTE) ?
527         config_getstring(IMAPOPT_LDAP_MEMBER_ATTRIBUTE) : config_getstring(IMAPOPT_LDAP_MEMBER_ATTRIBUTE));
528 
529     ptsm->user_attribute = (config_getstring(IMAPOPT_LDAP_USER_ATTRIBUTE) ?
530         config_getstring(IMAPOPT_LDAP_USER_ATTRIBUTE) : config_getstring(IMAPOPT_LDAP_USER_ATTRIBUTE));
531 
532     p = config_getstring(IMAPOPT_LDAP_GROUP_SCOPE);
533     if (!strcasecmp(p, "one")) {
534         ptsm->group_scope = LDAP_SCOPE_ONELEVEL;
535     } else if (!strcasecmp(p, "base")) {
536         ptsm->group_scope = LDAP_SCOPE_BASE;
537     } else {
538         ptsm->group_scope = LDAP_SCOPE_SUBTREE;
539     }
540 
541     ptsm->group_filter = config_getstring(IMAPOPT_LDAP_GROUP_FILTER);
542     ptsm->group_base = config_getstring(IMAPOPT_LDAP_GROUP_BASE);
543     ptsm->filter = config_getstring(IMAPOPT_LDAP_FILTER);
544     ptsm->base = config_getstring(IMAPOPT_LDAP_BASE);
545 
546     if (ptsm->version != LDAP_VERSION3 && (ptsm->sasl || ptsm->start_tls))
547         ptsm->version = LDAP_VERSION3;
548 
549     ptsm->domain_base_dn = config_getstring(IMAPOPT_LDAP_DOMAIN_BASE_DN);
550     ptsm->domain_filter = config_getstring(IMAPOPT_LDAP_DOMAIN_FILTER);
551     ptsm->domain_name_attribute = config_getstring(IMAPOPT_LDAP_DOMAIN_NAME_ATTRIBUTE);
552 
553     p = config_getstring(IMAPOPT_LDAP_DOMAIN_SCOPE);
554 
555     if (!strcasecmp(p, "one")) {
556         ptsm->domain_scope = LDAP_SCOPE_ONELEVEL;
557     } else if (!strcasecmp(p, "base")) {
558         ptsm->domain_scope = LDAP_SCOPE_BASE;
559     } else {
560         ptsm->domain_scope = LDAP_SCOPE_SUBTREE;
561     }
562 
563     ptsm->domain_result_attribute = config_getstring(IMAPOPT_LDAP_DOMAIN_RESULT_ATTRIBUTE);
564 
565     ptsm->ld = NULL;
566 }
567 
568 /*
569  * Note: calling function must free memory.
570  */
ptsmodule_escape(const char * s,const unsigned int n,char ** result)571 static int ptsmodule_escape(
572     const char *s,
573     const unsigned int n,
574     char **result)
575 {
576     char *buf;
577     char *end, *ptr, *temp;
578 
579     if (n > strlen(s))  // Sanity check, just in case
580         return PTSM_FAIL;
581 
582     buf = xmalloc(n * 5 + 1);
583     if (buf == NULL) {
584         return PTSM_NOMEM;
585     }
586 
587     buf[0] = '\0';
588     ptr = (char *)s;
589     end = ptr + n;
590 
591     while (((temp = strpbrk(ptr, "*()\\\0"))!=NULL) && (temp<end)) {
592 
593         if (temp>ptr)
594             strncat(buf, ptr, temp-ptr);
595 
596         switch (*temp) {
597             case '*':
598                 strcat(buf, "\\2a");
599                 break;
600             case '(':
601                 strcat(buf, "\\28");
602                 break;
603             case ')':
604                 strcat(buf, "\\29");
605                 break;
606             case '\\':
607                 strcat(buf, "\\5c");
608                 break;
609             case '\0':
610                 strcat(buf, "\\00");
611                 break;
612         }
613         ptr=temp+1;
614     }
615     if (ptr<end)
616         strncat(buf, ptr, end-ptr);
617 
618     *result = buf;
619 
620     return PTSM_OK;
621 }
622 
ptsmodule_standard_root_dn(const char * domain,const char ** result)623 static int ptsmodule_standard_root_dn(const char *domain, const char **result)
624 {
625     const char *dc_sep = ",dc=";
626     char *domain_copy;
627     char *part, *tok_state;
628     struct buf buf = BUF_INITIALIZER;
629 
630     assert(domain != NULL && domain[0] != '\0');
631 
632     syslog(LOG_DEBUG, "ptsmodule_standard_root_dn called for domain %s", domain);
633 
634     /* Each dot is to be replaced with ',dc='.
635      * We also need a leading 'dc=' at the start.
636      */
637     domain_copy = xstrdup(domain);
638     part = strtok_r(domain_copy, ".", &tok_state);
639     buf_setcstr(&buf, "dc=");
640 
641     while (part != NULL) {
642         syslog(LOG_DEBUG, "Root DN now %s", buf_cstring(&buf));
643 
644         buf_appendcstr(&buf, part);
645         syslog(LOG_DEBUG, "Root DN now %s", buf_cstring(&buf));
646 
647         part = strtok_r(NULL, ".", &tok_state);
648 
649         if (part != NULL)
650             buf_appendcstr(&buf, dc_sep);
651     }
652 
653     free(domain_copy);
654 
655     syslog(LOG_DEBUG, "Root DN now %s", buf_cstring(&buf));
656 
657     *result = buf_release(&buf);
658 
659     syslog(LOG_DEBUG, "Root DN now %s", *result);
660 
661     return PTSM_OK;
662 }
663 
ptsmodule_tokenize_domains(const char * d,int n,char ** result)664 static int ptsmodule_tokenize_domains(
665     const char *d,
666     int n,
667     char **result)
668 {
669     char *s, *s1;
670     char *lasts;
671     int nt, i, rc;
672 
673     *result = NULL;
674 
675     if (d == NULL || n < 1 || n > 9)
676         return PTSM_FAIL;
677 
678     s = strdup(d);
679     if (s == NULL)
680         return PTSM_NOMEM;
681 
682     for( nt=0, s1=s; *s1; s1++ )
683         if( *s1 == '.' ) nt++;
684     nt++;
685 
686     if (n > nt) {
687         free(s);
688         return PTSM_FAIL;
689     }
690 
691     i = nt - n;
692     s1 = (char *)strtok_r(s, ".", &lasts);
693     while(s1) {
694         if (i == 0) {
695             rc = ptsmodule_escape(s1, strlen(s1), result);
696             free(s);
697             return rc;
698         }
699         s1 = (char *)strtok_r(NULL, ".", &lasts);
700         i--;
701     }
702 
703     free(s);
704     return PTSM_FAIL;
705 }
706 
707 #define PTSM_MAX(a,b) (a>b?a:b)
708 
709 /*
710  * ptsmodule_expand_tokens
711  * Parts with the strings provided.
712  *   %%   = %
713  *   %u   = user
714  *   %U   = user part of %u
715  *   %d   = domain part of %u if available, otherwise same as %R
716  *   %R   = prepend '@' to domain
717  *   %1-9 = domain tokens (%1 = tld, %2 = domain when %d = domain.tld)
718  *   %D   = user DN
719  * Note: calling function must free memory.
720  */
ptsmodule_expand_tokens(const char * pattern,const char * username,const char * dn,char ** result)721 static int ptsmodule_expand_tokens(
722     const char *pattern,
723     const char *username,
724     const char *dn,
725     char **result)
726 {
727     char *buf;
728     char *end, *ptr, *temp;
729     char *ebuf, *user;
730     char *domain;
731     int rc;
732 
733     /* to permit multiple occurences of username and/or realm in filter */
734     /* and avoid memory overflow in filter build [eg: (|(uid=%u)(userid=%u)) ] */
735     int percents, user_len, dn_len, maxparamlength;
736 
737     if (pattern == NULL) {
738         syslog(LOG_ERR, "filter pattern not setup");
739         return PTSM_FAIL;
740     }
741 
742     /* find the longest param of username and realm,
743        do not worry about domain because it is always shorter
744        then username                                           */
745     user_len = username ? strlen(username) : 0;
746     dn_len = dn ? strlen(dn) : 0;
747 
748     maxparamlength = PTSM_MAX(user_len+1, dn_len); /* +1 for %R when '@' is prepended */
749 
750     /* find the number of occurences of percent sign in filter */
751     for (percents=0, buf=(char *)pattern; *buf; buf++) {
752         if (*buf == '%') percents++;
753     }
754 
755     /* percents * 3 * maxparamlength because we need to account for
756          * an entirely-escaped worst-case-length parameter */
757     buf = xmalloc(strlen(pattern) + (percents * 3 * maxparamlength) +1);
758     if (buf == NULL)
759         return PTSM_NOMEM;
760 
761     buf[0] = '\0';
762 
763     ptr = (char *)pattern;
764     end = ptr + strlen(ptr);
765 
766     while ((temp=strchr(ptr,'%')) != NULL) {
767 
768         if ((temp-ptr) > 0)
769             strncat(buf, ptr, temp-ptr);
770 
771         if ((temp+1) >= end) {
772             syslog(LOG_DEBUG, "Incomplete lookup substitution format");
773             break;
774         }
775 
776         switch (*(temp+1)) {
777             case '%':
778                 strncat(buf,temp+1,1);
779                 break;
780             case 'u':
781                 if (ISSET(username)) {
782                     rc = ptsmodule_escape(username, strlen(username), &ebuf);
783                     if (rc == PTSM_OK) {
784                         strcat(buf,ebuf);
785                         free(ebuf);
786                     }
787                 } else
788                     syslog(LOG_DEBUG, "Username not available.");
789                 break;
790             case 'U':
791                 if (ISSET(username)) {
792 
793                     user = strchr(username, '@');
794                     rc = ptsmodule_escape(username, (user ? (unsigned char)(user - username) : strlen(username)), &ebuf);
795                     if (rc == PTSM_OK) {
796                         strcat(buf,ebuf);
797                         free(ebuf);
798                     }
799                 } else
800                     syslog(LOG_DEBUG, "Username not available.");
801                 break;
802             case '1':
803             case '2':
804             case '3':
805             case '4':
806             case '5':
807             case '6':
808             case '7':
809             case '8':
810             case '9':
811                 if (ISSET(username) && (domain = strchr(username, '@')) && domain[1]!='\0') {
812                     rc=ptsmodule_tokenize_domains(domain+1, (int) *(temp+1)-48, &ebuf);
813                     if (rc == PTSM_OK) {
814                         strcat(buf,ebuf);
815                         free(ebuf);
816                     }
817                 } else
818                     syslog(LOG_DEBUG, "Domain tokens not available.");
819                 break;
820             case 'R':
821             case 'd':
822                 if (ISSET(username) && (domain = strchr(username, '@')) && domain[1]!='\0') {
823                     rc=ptsmodule_escape(domain+1, strlen(domain+1), &ebuf);
824                     if (rc == PTSM_OK) {
825                         if (*(temp+1) == 'R')
826                             strcat(buf,"@");
827                         strcat(buf,ebuf);
828                         free(ebuf);
829                     }
830                     break;
831                 }
832                 break;
833             case 'D':
834                 if (ISSET(dn)) {
835                     rc = ptsmodule_escape(dn, strlen(dn), &ebuf);
836                     if (rc == PTSM_OK) {
837                         strcat(buf,ebuf);
838                         free(ebuf);
839                     }
840                 } else
841                     syslog(LOG_DEBUG, "dn not available.");
842                 break;
843             default:
844                 break;
845         }
846         ptr=temp+2;
847     }
848     if (temp<end)
849         strcat(buf, ptr);
850 
851     *result = buf;
852 
853     return PTSM_OK;
854 }
855 
ptsmodule_get_dn(const char * canon_id,size_t size,char ** ret)856 static int ptsmodule_get_dn(
857     const char *canon_id,
858     size_t size,
859     char **ret)
860 {
861     int rc;
862 
863 #if LDAP_VENDOR_VERSION >= 20125
864     struct berval *dn = NULL;
865     LDAPControl c;
866     LDAPControl *ctrl[2];
867     char *authzid;
868 #endif
869     char *base = NULL, *filter = NULL;
870     char *domain = NULL;
871     char domain_filter[1024];
872     char *attrs[] = {LDAP_NO_ATTRS,NULL}; //do not return all attrs!
873     char *domain_attrs[] = {(char *)ptsm->domain_name_attribute,(char *)ptsm->domain_result_attribute,NULL};
874     LDAPMessage *res;
875     LDAPMessage *entry;
876     char **vals;
877     /* unused: BerElement *ber; */
878 
879     *ret = NULL;
880 
881     if (ptsm->ld == NULL)
882         return PTSM_FAIL;
883 
884 #if LDAP_VENDOR_VERSION >= 20125
885 
886     if (ptsm->sasl) {
887 
888         authzid = xmalloc(size + sizeof("u:"));
889         if (authzid == NULL)
890             return PTSM_NOMEM;
891 
892         strcpy(authzid, "u:");
893         strcpy(authzid+2, canon_id);
894         c.ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ;
895         c.ldctl_value.bv_val = authzid;
896         c.ldctl_value.bv_len = size + 2;
897         c.ldctl_iscritical = 1;
898 
899         ctrl[0] = &c;
900         ctrl[1] = NULL;
901         rc = ldap_whoami_s(ptsm->ld, &dn, ctrl, NULL);
902         free(authzid);
903         if ( rc != LDAP_SUCCESS || !dn ) {
904             if (rc == LDAP_SERVER_DOWN) {
905                 ldap_unbind(ptsm->ld);
906                 ptsm->ld = NULL;
907                 return PTSM_RETRY;
908             }
909             return PTSM_FAIL;
910         }
911 
912         if ( dn->bv_val &&
913             !strncmp(dn->bv_val, "dn:", 3) )
914             *ret = strdup(dn->bv_val+3);
915         ber_bvfree(dn);
916 
917     } else
918 
919 #endif
920 
921     {
922         rc = ptsmodule_expand_tokens(ptsm->filter, canon_id, NULL, &filter);
923         if (rc != PTSM_OK)
924             goto done;
925 
926         if (ptsm->domain_base_dn && ptsm->domain_base_dn[0] != '\0' && (strrchr(canon_id, '@') == NULL)) {
927             syslog(LOG_DEBUG, "collecting all domains from %s", ptsm->domain_base_dn);
928 
929 #pragma GCC diagnostic push
930 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
931             /* Format string comes from imapd.conf.  Not ideal, but it's what
932              * we've got...
933              */
934             snprintf(domain_filter, sizeof(domain_filter), ptsm->domain_filter, "*");
935 #pragma GCC diagnostic pop
936 
937             syslog(LOG_DEBUG, "Domain filter: %s", domain_filter);
938 
939             rc = ldap_search_st(ptsm->ld, ptsm->domain_base_dn, ptsm->domain_scope, domain_filter, domain_attrs, 0, &(ptsm->timeout), &res);
940 
941             if (rc != LDAP_SUCCESS) {
942                 if (rc == LDAP_SERVER_DOWN) {
943                     syslog(LOG_ERR, "LDAP not available: %s", ldap_err2string(rc));
944                     ldap_unbind(ptsm->ld);
945                     ptsm->ld = NULL;
946                     rc = PTSM_RETRY;
947                     goto done;
948                 }
949 
950                 syslog(LOG_ERR, "LDAP search for domain failed: %s", ldap_err2string(rc));
951                 rc = PTSM_FAIL;
952                 goto done;
953             }
954             if (ldap_count_entries(ptsm->ld, res) < 1) {
955                 syslog(LOG_ERR, "No domain found");
956                 rc = PTSM_FAIL;
957                 goto done;
958             } else if (ldap_count_entries(ptsm->ld, res) >= 1) {
959                 int count_matches = 0;
960                 char *temp_base = NULL;
961                 LDAPMessage *res2;
962                 for (entry = ldap_first_entry(ptsm->ld, res); entry != NULL; entry = ldap_next_entry(ptsm->ld, entry)) {
963                     if ((vals = ldap_get_values(ptsm->ld, entry, ptsm->domain_name_attribute)) != NULL) {
964                         syslog(LOG_DEBUG, "we have a domain %s", vals[0]);
965                         ptsmodule_standard_root_dn(vals[0], (const char **) &temp_base);
966                         rc = ldap_search_st(ptsm->ld, temp_base, ptsm->scope, filter, attrs, 0, &(ptsm->timeout), &res2);
967                         if (rc == LDAP_SUCCESS && ldap_count_entries(ptsm->ld, res2) == 1) {
968                             syslog(LOG_DEBUG, "Found %s in %s", canon_id, temp_base);
969                             base = temp_base;
970                             count_matches++;
971                         }
972                     }
973                 }
974 
975                 if (count_matches > 1) {
976                     syslog(LOG_ERR, "LDAP search for %s failed because it matches multiple accounts.", canon_id);
977                     rc = PTSM_FAIL;
978                     goto done;
979                 } else if (count_matches == 0) {
980                     syslog(LOG_ERR, "LDAP search for %s failed because it does not match any account in all domains.", canon_id);
981                     rc = PTSM_FAIL;
982                     goto done;
983                 }
984 
985                 syslog(LOG_DEBUG, "we have found %s in %s", canon_id, base);
986             }
987         }
988         else if (ptsm->domain_base_dn && ptsm->domain_base_dn[0] != '\0' && (strrchr(canon_id, '@') != NULL)) {
989             syslog(LOG_DEBUG, "Attempting to get domain for %s from %s", canon_id, ptsm->domain_base_dn);
990 
991             /* Get the base dn to search from domain_base_dn searched on domain_scope with
992                 domain_filter */
993             domain = strrchr(canon_id, '@');
994 
995             /* Strip the first character which is a '@' */
996             domain = domain+1;
997 
998 #pragma GCC diagnostic push
999 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
1000             /* Format string comes from imapd.conf.  Not ideal, but it's what
1001              * we've got...
1002              */
1003             snprintf(domain_filter, sizeof(domain_filter), ptsm->domain_filter, domain);
1004 #pragma GCC diagnostic pop
1005 
1006             syslog(LOG_DEBUG, "Domain filter: %s", domain_filter);
1007 
1008             rc = ldap_search_st(ptsm->ld, ptsm->domain_base_dn, ptsm->domain_scope, domain_filter, domain_attrs, 0, &(ptsm->timeout), &res);
1009 
1010             if (rc != LDAP_SUCCESS) {
1011                 if (rc == LDAP_SERVER_DOWN) {
1012                     ldap_unbind(ptsm->ld);
1013                     ptsm->ld = NULL;
1014                     syslog(LOG_ERR, "LDAP not available: %s", ldap_err2string(rc));
1015                     rc = PTSM_RETRY;
1016                     goto done;
1017                 }
1018 
1019                 syslog(LOG_ERR, "LDAP search for domain failed: %s", ldap_err2string(rc));
1020                 rc = PTSM_FAIL;
1021                 goto done;
1022             }
1023 
1024             if (ldap_count_entries(ptsm->ld, res) < 1) {
1025                 syslog(LOG_ERR, "No domain %s found", domain);
1026                 rc = PTSM_FAIL;
1027                 goto done;
1028             } else if (ldap_count_entries(ptsm->ld, res) > 1) {
1029                 syslog(LOG_ERR, "Multiple domains %s found", domain);
1030                 rc = PTSM_FAIL;
1031                 goto done;
1032             } else {
1033                 if ((entry = ldap_first_entry(ptsm->ld, res)) != NULL) {
1034                     if ((vals = ldap_get_values(ptsm->ld, entry, ptsm->domain_result_attribute)) != NULL) {
1035                         syslog(LOG_DEBUG, "Root DN for domain %s is %s", domain, vals[0]);
1036                         ptsm->base = vals[0];
1037                         rc = PTSM_OK;
1038                     } else if ((vals = ldap_get_values(ptsm->ld, entry, ptsm->domain_name_attribute)) != NULL) {
1039                         syslog(LOG_DEBUG, "Domain %s is an alias domain for parent domain %s", domain, vals[0]);
1040                         rc = ptsmodule_standard_root_dn(vals[0], &ptsm->base);
1041                     } else {
1042                         rc = ptsmodule_standard_root_dn(domain, &ptsm->base);
1043                     }
1044 
1045                     if (rc != PTSM_OK) {
1046                         goto done;
1047                     } else {
1048                         base = xstrdup(ptsm->base);
1049                         syslog(LOG_DEBUG, "Continuing with ptsm->base: %s", ptsm->base);
1050                     }
1051                 }
1052             }
1053 
1054         } else {
1055             rc = ptsmodule_expand_tokens(ptsm->base, canon_id, NULL, &base);
1056             if (rc != PTSM_OK)
1057                 goto done;
1058         }
1059 
1060         rc = ldap_search_st(ptsm->ld, base, ptsm->scope, filter, attrs, 0, &(ptsm->timeout), &res);
1061 
1062         if (rc != LDAP_SUCCESS) {
1063             syslog(LOG_DEBUG, "Searching %s with %s failed", base, filter);
1064 
1065             if (rc == LDAP_SERVER_DOWN) {
1066                 ldap_unbind(ptsm->ld);
1067                 ptsm->ld = NULL;
1068                 rc = PTSM_RETRY;
1069                 goto done;
1070             }
1071 
1072             rc = PTSM_FAIL;
1073             goto done;
1074         }
1075 
1076         free(filter);
1077         free(base);
1078 
1079         /*
1080          * We don't want to return the *first* entry found, we want to return
1081          * the *only* entry found.
1082          */
1083         if (ldap_count_entries(ptsm->ld, res) < 1) {
1084             syslog(LOG_ERR, "No entries found");
1085         } else if (ldap_count_entries(ptsm->ld, res) > 1) {
1086             syslog(LOG_ERR, "Multiple entries found: %d", ldap_count_entries(ptsm->ld, res));
1087         } else {
1088 /*      if ( ldap_count_entries(ptsm->ld, res) == 1 ) { */
1089             if ((entry = ldap_first_entry(ptsm->ld, res)) != NULL) {
1090                 *ret = ldap_get_dn(ptsm->ld, entry);
1091             }
1092         }
1093 
1094         ldap_msgfree(res);
1095         res = NULL;
1096     }
1097 
1098     return (*ret ? PTSM_OK : PTSM_FAIL);
1099 
1100  done:
1101     if (filter)
1102         free(filter);
1103     if (base)
1104         free(base);
1105     return rc;
1106 }
1107 
1108 
ptsmodule_make_authstate_attribute(const char * canon_id,size_t size,const char ** reply,int * dsize,struct auth_state ** newstate)1109 static int ptsmodule_make_authstate_attribute(
1110     const char *canon_id,
1111     size_t size,
1112     const char **reply,
1113     int *dsize,
1114     struct auth_state **newstate)
1115 {
1116     char *dn = NULL;
1117     LDAPMessage *res = NULL;
1118     LDAPMessage *entry = NULL;
1119     char **vals = NULL;
1120     char **rdn = NULL;
1121     int rc;
1122     char *attrs[] = {(char *)ptsm->member_attribute,(char *)ptsm->user_attribute,NULL};
1123 
1124     rc = ptsmodule_connect();
1125     if (rc != PTSM_OK) {
1126         *reply = "ptsmodule_connect() failed";
1127         return rc;
1128     }
1129 
1130     rc = ptsmodule_get_dn(canon_id, size, &dn);
1131     if (rc != PTSM_OK) {
1132         *reply = "identifier not found";
1133         goto done;
1134     }
1135 
1136     rc = ldap_search_st(ptsm->ld, dn, LDAP_SCOPE_BASE, "(objectclass=*)", attrs, 0, &(ptsm->timeout), &res);
1137     if ( rc != LDAP_SUCCESS ) {
1138         *reply = "ldap_search(attribute) failed";
1139         if ( rc == LDAP_SERVER_DOWN ) {
1140             ldap_unbind(ptsm->ld);
1141             ptsm->ld = NULL;
1142             rc = PTSM_RETRY;
1143         } else
1144             rc = PTSM_FAIL;
1145         goto done;
1146     }
1147 
1148     if ((entry = ldap_first_entry(ptsm->ld, res)) != NULL) {
1149         int i, numvals;
1150 
1151         vals = ldap_get_values(ptsm->ld, entry, (char *)ptsm->member_attribute);
1152         if (vals != NULL) {
1153             numvals = ldap_count_values(vals);
1154 
1155             *dsize = sizeof(struct auth_state) +
1156                 (numvals * sizeof(struct auth_ident));
1157             *newstate = xmalloc(*dsize);
1158             if (*newstate == NULL) {
1159                 *reply = "no memory";
1160                 rc = PTSM_FAIL;
1161                 goto done;
1162             }
1163 
1164             (*newstate)->ngroups = numvals;
1165             (*newstate)->userid.id[0] = '\0';
1166             for (i = 0; i < numvals; i++) {
1167                 unsigned int j;
1168                 strcpy((*newstate)->groups[i].id, "group:");
1169                 rdn = ldap_explode_rdn(vals[i],1);
1170                 for (j = 0; j < strlen(rdn[0]); j++) {
1171                     if (Uisupper(rdn[0][j]))
1172                         rdn[0][j]=tolower(rdn[0][j]);
1173                 }
1174                 strlcat((*newstate)->groups[i].id, rdn[0], sizeof((*newstate)->groups[i].id));
1175                 (*newstate)->groups[i].hash = strhash((*newstate)->groups[i].id);
1176             }
1177 
1178             ldap_value_free(rdn);
1179             ldap_value_free(vals);
1180             vals = NULL;
1181         }
1182 
1183         if ((char *)ptsm->user_attribute) {
1184             vals = ldap_get_values(ptsm->ld, entry, (char *)ptsm->user_attribute);
1185             if (vals != NULL) {
1186                 numvals = ldap_count_values(vals);
1187 
1188                 if (numvals == 1) {
1189                     if(!*newstate) {
1190                         *dsize = sizeof(struct auth_state);
1191                         *newstate = xmalloc(*dsize);
1192 
1193                         if (*newstate == NULL) {
1194                             *reply = "no memory";
1195                             rc = PTSM_FAIL;
1196                             goto done;
1197                         }
1198 
1199                         (*newstate)->ngroups = 0;
1200                     }
1201 
1202                     size=strlen(vals[0]);
1203                     strcpy((*newstate)->userid.id, ptsmodule_canonifyid(vals[0],size));
1204                     (*newstate)->userid.hash = strhash((*newstate)->userid.id);
1205                 }
1206 
1207                 ldap_value_free(vals);
1208                 vals = NULL;
1209             }
1210         }
1211     }
1212 
1213     if(!*newstate) {
1214         *dsize = sizeof(struct auth_state);
1215         *newstate = xmalloc(*dsize);
1216         if (*newstate == NULL) {
1217             *reply = "no memory";
1218             rc = PTSM_FAIL;
1219             goto done;
1220         }
1221         (*newstate)->ngroups = 0;
1222         (*newstate)->userid.id[0] = '\0';
1223     }
1224 
1225     /* fill in the rest of our new state structure */
1226     if ((*newstate)->userid.id[0]=='\0') {
1227         strcpy((*newstate)->userid.id, canon_id);
1228         (*newstate)->userid.hash = strhash(canon_id);
1229     }
1230     (*newstate)->mark = time(0);
1231 
1232     rc = PTSM_OK;
1233 
1234 done:;
1235 
1236     if (res)
1237         ldap_msgfree(res);
1238     if (vals)
1239         ldap_value_free(vals);
1240     if (dn)
1241         free(dn);
1242 
1243     return rc;
1244 }
1245 
ptsmodule_make_authstate_filter(const char * canon_id,size_t size,const char ** reply,int * dsize,struct auth_state ** newstate)1246 static int ptsmodule_make_authstate_filter(
1247     const char *canon_id,
1248     size_t size,
1249     const char **reply,
1250     int *dsize,
1251     struct auth_state **newstate)
1252 {
1253     char *base = NULL, *filter = NULL;
1254     int rc;
1255     int i; int n;
1256     LDAPMessage *res = NULL;
1257     LDAPMessage *entry = NULL;
1258     char **vals = NULL;
1259     char *attrs[] = {(char *)ptsm->member_attribute,NULL};
1260     char *dn = NULL;
1261     char *errdn = NULL;
1262 
1263     rc = ptsmodule_connect();
1264     if (rc != PTSM_OK) {
1265         *reply = "ptsmodule_connect() failed";
1266         return rc;
1267     }
1268 
1269     rc = ptsmodule_get_dn(canon_id, size, &dn);
1270     if (rc != PTSM_OK) {
1271         *reply = "identifier not found";
1272         return rc;
1273     }
1274 
1275     rc = ptsmodule_expand_tokens(ptsm->member_filter, canon_id, dn, &filter);
1276     if (rc != PTSM_OK) {
1277         *reply = "ptsmodule_expand_tokens() failed for member filter";
1278         goto done;
1279     }
1280 
1281     rc = ptsmodule_expand_tokens(ptsm->group_base, canon_id, dn, &base);
1282     if (rc != PTSM_OK) {
1283         *reply = "ptsmodule_expand_tokens() failed for member search base";
1284         goto done;
1285     }
1286 
1287     rc = ldap_search_st(ptsm->ld, base, ptsm->group_scope, filter, attrs, 0, &(ptsm->timeout), &res);
1288     if (rc != LDAP_SUCCESS) {
1289         *reply = "ldap_search(filter) failed";
1290         if (rc == LDAP_SERVER_DOWN) {
1291             ldap_unbind(ptsm->ld);
1292             ptsm->ld = NULL;
1293             rc = PTSM_RETRY;
1294         } else
1295             rc = PTSM_FAIL;
1296 
1297         goto done;
1298     }
1299 
1300     n = ldap_count_entries(ptsm->ld, res);
1301     if (n < 0) {
1302         *reply = "ldap_count_entries() failed";
1303         rc = PTSM_FAIL;
1304         goto done;
1305     }
1306 
1307     *dsize = sizeof(struct auth_state) + (n * sizeof(struct auth_ident));
1308 
1309     *newstate = xmalloc(*dsize);
1310 
1311     if (*newstate == NULL) {
1312         *reply = "no memory";
1313         rc = PTSM_FAIL;
1314         goto done;
1315     }
1316 
1317     (*newstate)->ngroups = n;
1318     strcpy((*newstate)->userid.id, canon_id);
1319     (*newstate)->userid.hash = strhash(canon_id);
1320     (*newstate)->mark = time(0);
1321 
1322     for (i = 0, entry = ldap_first_entry(ptsm->ld, res); entry != NULL;
1323          i++, entry = ldap_next_entry(ptsm->ld, entry)) {
1324 
1325         if (errdn) ldap_memfree(errdn);
1326         errdn = ldap_get_dn(ptsm->ld, entry);
1327 
1328         vals = ldap_get_values(ptsm->ld, entry, (char *)ptsm->member_attribute);
1329         if (vals == NULL) {
1330             syslog(LOG_ERR, "Multiple values for attribute '%s' on entry '%s'",
1331                             ptsm->member_attribute,
1332                             errdn);
1333             continue;
1334         }
1335 
1336         if (ldap_count_values(vals) < 1 ) {
1337             syslog(LOG_ERR, "No values for attribute '%s' on entry '%s'",
1338                             ptsm->member_attribute,
1339                             errdn);
1340             *reply = "no values";
1341             rc = PTSM_FAIL;
1342             ldap_value_free(vals);
1343             vals = NULL;
1344             goto done;
1345         } else if (ldap_count_values(vals) > 1) {
1346             syslog(LOG_ERR, "Too many values for attribute '%s' on entry '%s'",
1347                             ptsm->member_attribute,
1348                             errdn);
1349             *reply = "too many values";
1350             rc = PTSM_FAIL;
1351             ldap_value_free(vals);
1352             vals = NULL;
1353             goto done;
1354         }
1355 
1356         strcpy((*newstate)->groups[i].id, "group:");
1357 
1358         unsigned int j;
1359         for (j =0; j < strlen(vals[0]); j++) {
1360             if(Uisupper(vals[0][j]))
1361                 vals[0][j]=tolower(vals[0][j]);
1362         }
1363 
1364         strlcat((*newstate)->groups[i].id, vals[0], sizeof((*newstate)->groups[i].id));
1365         (*newstate)->groups[i].hash = strhash((*newstate)->groups[i].id);
1366 
1367         ldap_value_free(vals);
1368         vals = NULL;
1369 
1370         if (errdn) ldap_memfree(errdn);
1371         errdn = NULL;
1372     }
1373 
1374     rc = PTSM_OK;
1375 
1376 done:
1377 
1378     if (res)
1379         ldap_msgfree(res);
1380     if (dn)
1381         free(dn);
1382     if (filter)
1383         free(filter);
1384     if (base)
1385         free(base);
1386     if (errdn)
1387         ldap_memfree(errdn);
1388 
1389     return rc;
1390 }
1391 
ptsmodule_make_authstate_group(const char * canon_id,size_t size,const char ** reply,int * dsize,struct auth_state ** newstate)1392 static int ptsmodule_make_authstate_group(
1393     const char *canon_id,
1394     size_t size __attribute__((unused)),
1395     const char **reply,
1396     int *dsize,
1397     struct auth_state **newstate)
1398 {
1399     char *base = NULL, *filter = NULL;
1400     char *domain = NULL;
1401     char domain_filter[1024];
1402     char *domain_attrs[] = {(char *)ptsm->domain_name_attribute,(char *)ptsm->domain_result_attribute,NULL};
1403     int rc;
1404     int n;
1405     LDAPMessage *res = NULL;
1406     LDAPMessage *entry = NULL;
1407     char **vals = NULL;
1408     char *attrs[] = {NULL};
1409 
1410     if (strncmp(canon_id, "group:", 6))  { // Sanity check
1411         *reply = "not a group identifier";
1412         rc = PTSM_FAIL;
1413         goto done;
1414     }
1415 
1416     rc = ptsmodule_connect();
1417     if (rc != PTSM_OK) {
1418         *reply = "ptsmodule_connect() failed";
1419         goto done;
1420     }
1421 
1422     rc = ptsmodule_expand_tokens(ptsm->group_filter, canon_id+6, NULL, &filter);
1423     if (rc != PTSM_OK) {
1424         *reply = "ptsmodule_expand_tokens() failed for group filter";
1425         goto done;
1426     }
1427 
1428     if (ptsm->domain_base_dn && (strrchr(canon_id, '@') != NULL)) {
1429         syslog(LOG_DEBUG, "(groups) Attempting to get domain for %s from %s", canon_id, ptsm->domain_base_dn);
1430 
1431         /* Get the base dn to search from domain_base_dn searched on domain_scope with
1432             domain_filter */
1433         domain = strrchr(canon_id, '@');
1434 
1435         syslog(LOG_DEBUG, "(groups) Input domain would be %s", domain);
1436 
1437         /* Strip the first character which is a '@' AM: assuming ASCII */
1438         domain = domain+1;
1439 
1440         syslog(LOG_DEBUG, "(groups) Input domain would be %s", domain);
1441 
1442 #pragma GCC diagnostic push
1443 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
1444             /* Format string comes from imapd.conf.  Not ideal, but it's what
1445              * we've got...
1446              */
1447         snprintf(domain_filter, sizeof(domain_filter), ptsm->domain_filter, domain);
1448 #pragma GCC diagnostic pop
1449 
1450         syslog(LOG_DEBUG, "(groups) Domain filter: %s", domain_filter);
1451 
1452         rc = ldap_search_st(ptsm->ld, ptsm->domain_base_dn, ptsm->domain_scope, domain_filter, domain_attrs, 0, &(ptsm->timeout), &res);
1453 
1454         if (rc != LDAP_SUCCESS) {
1455             syslog(LOG_DEBUG, "(groups) Result from domain query not OK");
1456             goto done;
1457         } else {
1458             syslog(LOG_DEBUG, "(groups) Result from domain query OK");
1459         }
1460 
1461         if (ldap_count_entries(ptsm->ld, res) < 1) {
1462             syslog(LOG_ERR, "(groups) No domain %s found", domain);
1463             rc = PTSM_FAIL;
1464             goto done;
1465         } else if (ldap_count_entries(ptsm->ld, res) > 1) {
1466             syslog(LOG_ERR, "(groups) Multiple domains %s found", domain);
1467             rc = PTSM_FAIL;
1468             goto done;
1469         } else {
1470             syslog(LOG_DEBUG, "(groups) Domain %s found", domain);
1471             if ((entry = ldap_first_entry(ptsm->ld, res)) != NULL) {
1472                 if ((vals = ldap_get_values(ptsm->ld, entry, ptsm->domain_result_attribute)) != NULL) {
1473                     ptsm->group_base = vals[0];
1474                     rc = PTSM_OK;
1475                 } else if ((vals = ldap_get_values(ptsm->ld, entry, ptsm->domain_name_attribute)) != NULL) {
1476                     char *new_domain = xstrdup(vals[0]);
1477                     syslog(LOG_DEBUG, "(groups) Domain %s is now domain %s", domain, new_domain);
1478                     rc = ptsmodule_standard_root_dn(new_domain, &ptsm->group_base);
1479                     free(new_domain);
1480                 } else {
1481                     rc = ptsmodule_standard_root_dn(domain, &ptsm->group_base);
1482                 }
1483 
1484                 if (rc != PTSM_OK) {
1485                     goto done;
1486                 } else {
1487                     base = xstrdup(ptsm->group_base);
1488                     syslog(LOG_DEBUG, "Continuing with ptsm->group_base: %s", ptsm->group_base);
1489                 }
1490             }
1491         }
1492     } else {
1493         rc = ptsmodule_expand_tokens(ptsm->group_base, canon_id, NULL, &base);
1494         if (rc != PTSM_OK)
1495             goto done;
1496     }
1497 
1498     syslog(LOG_DEBUG, "(groups) about to search %s for %s", base, filter);
1499 
1500 
1501     rc = ptsmodule_expand_tokens(ptsm->group_base, canon_id+6, NULL, &base);
1502     if (rc != PTSM_OK) {
1503         *reply = "ptsmodule_expand_tokens() failed for group search base";
1504         goto done;
1505     }
1506 
1507     rc = ldap_search_st(ptsm->ld, base, ptsm->group_scope, filter, attrs, 0, &(ptsm->timeout), &res);
1508     if (rc != LDAP_SUCCESS) {
1509         *reply = "ldap_search(group) failed";
1510         if (rc == LDAP_SERVER_DOWN) {
1511             ldap_unbind(ptsm->ld);
1512             ptsm->ld = NULL;
1513             rc = PTSM_RETRY;
1514         } else
1515             rc = PTSM_FAIL;
1516         goto done;
1517     }
1518 
1519     n = ldap_count_entries(ptsm->ld, res);
1520     if (n != 1) {
1521         *reply = "group identifier not found";
1522         rc = PTSM_FAIL;
1523         goto done;
1524     }
1525 
1526     *dsize = sizeof(struct auth_state) +
1527              (n * sizeof(struct auth_ident));
1528     *newstate = xmalloc(*dsize);
1529     if (*newstate == NULL) {
1530         *reply = "no memory";
1531         rc = PTSM_FAIL;
1532         goto done;
1533     }
1534     (*newstate)->ngroups = 0;
1535     strcpy((*newstate)->userid.id, canon_id);
1536     (*newstate)->userid.hash = strhash(canon_id);
1537     (*newstate)->mark = time(0);
1538 
1539     rc = PTSM_OK;
1540 
1541 done:;
1542 
1543     if (res)
1544         ldap_msgfree(res);
1545     if (filter)
1546         free(filter);
1547     if (base)
1548         free(base);
1549 
1550     return rc;
1551 }
1552 
myauthstate(const char * identifier,size_t size,const char ** reply,int * dsize)1553 static struct auth_state *myauthstate(
1554     const char *identifier,
1555     size_t size,
1556     const char **reply,
1557     int *dsize)
1558 {
1559     const char *canon_id;
1560     struct auth_state *newstate = NULL;
1561     int rc;
1562     int retries = 1;
1563 
1564     canon_id = ptsmodule_canonifyid(identifier, size);
1565     if (EMPTY(canon_id)) {
1566         *reply = "ptsmodule_canonifyid() failed";
1567         return NULL;
1568     }
1569     size = strlen(canon_id);
1570 
1571 retry:;
1572 
1573     *reply = NULL;
1574 
1575     if (!strncmp(canon_id, "group:", 6))
1576         rc = ptsmodule_make_authstate_group(canon_id, size, reply, dsize, &newstate);
1577     else {
1578         if (ptsm->member_method == PTSM_MEMBER_METHOD_ATTRIBUTE)
1579             rc = ptsmodule_make_authstate_attribute(canon_id, size, reply, dsize, &newstate);
1580         else
1581             rc = ptsmodule_make_authstate_filter(canon_id, size, reply, dsize, &newstate);
1582     }
1583     if (rc == PTSM_RETRY &&
1584         retries) {
1585         retries--;
1586         goto retry;
1587     }
1588 
1589     return newstate;
1590 }
1591 
1592 #else /* HAVE_LDAP */
1593 
myinit(void)1594 static void myinit(void)
1595 {
1596     fatal("PTS module (ldap) not compiled in", EX_CONFIG);
1597 }
1598 
myauthstate(const char * identifier,size_t size,const char ** reply,int * dsize)1599 static struct auth_state *myauthstate(
1600     const char *identifier __attribute__((unused)),
1601     size_t size __attribute__((unused)),
1602     const char **reply __attribute__((unused)),
1603     int *dsize __attribute__((unused)))
1604 {
1605     fatal("PTS module (ldap) not compiled in", EX_CONFIG);
1606 }
1607 
1608 #endif /* HAVE_LDAP */
1609 
1610 struct pts_module pts_ldap =
1611 {
1612     "ldap",        /* name */
1613 
1614     &myinit,
1615     &myauthstate,
1616 };
1617