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