s/qmail 4.4.14
Next generation secure email transport
Loading...
Searching...
No Matches
qmail-ldapam.c
Go to the documentation of this file.
1#define LDAP_DEPRECATED 1
2/* OpenLDAP still needs some time to stabilize the new code base.
3 In the new regime, there are dark edges where dragons live ;-)
4*/
5//#include <stdio.h> // to be removed
6#include <sys/types.h>
7#include <sys/stat.h>
8#include <unistd.h>
9#include <string.h>
10#include <grp.h>
11#include <pwd.h>
12#include <ldap.h>
13#include "auto_qmail.h"
14#include "qmail.h"
15#include "case.h"
16#include "control.h"
17#include "constmap.h"
18#include "readwrite.h"
19#include "buffer.h"
20#include "fd.h"
21#include "byte.h"
22#include "case.h"
23#include "str.h"
24#include "stralloc.h"
25#include "exit.h"
26#include "logmsg.h"
27#include "pathexec.h"
28#include "getln.h"
29#include "scan.h"
30#include "getoptb.h"
31#include "open.h"
32#include "readclose.h"
33#include "env.h"
34#include "base64.h"
35
36#define WHO "qmail-ldapam"
37
38#define FDAUTH 3
39#define FDLOG 4
40#define FDPWD 5
41#define PORT_LDAP 389
42#define PORT_LDAPS 636
43
45buffer ba = BUFFER_INIT(buffer_unixwrite,FDAUTH,authbuf,sizeof(authbuf));
46char bspace[512];
47buffer bp;
48
50buffer bl = BUFFER_INIT(buffer_unixwrite,FDLOG,logbuf,sizeof(logbuf));
51
53stralloc ldapcntl = {0};
54stralloc disabled = {0};
55
56/* LDAP binding params */
57
58stralloc binddn = {0};
59stralloc bindpw = {0};
60stralloc bindpwds = {0};
61stralloc bindbase = {0};
62stralloc bindhost = {0};
63stralloc filter = {0};
64
65stralloc user = {0}; // user w/o domain appended
66stralloc domaindn = {0};
67stralloc password = {0};
68stralloc filters = {0};
69stralloc userattr = {0};
70
71unsigned long scope = LDAP_SCOPE_SUBTREE; // (2), BASE (0), ONELEVEL (1)
72int version = LDAP_VERSION3;
73
74static void log_error(const char *in,const char *src,const char *type)
75{
76 buffer_puts(&bl,WHO);
77 buffer_put(&bl,": ",2);
78 if (type) { buffer_puts(&bl,type); buffer_put(&bl," ",1); }
79 buffer_puts(&bl,in);
80 if (src) { buffer_puts(&bl,src); buffer_put(&bl," ",1); }
81 buffer_put(&bl,"\n",1);
82 buffer_flush(&bl);
83}
84
85
86static void pam_exit(int fail)
87{
88 for (int i = 0; i < sizeof(authbuf); ++i) authbuf[i] = 0;
89 _exit(fail);
90}
91
92static void temp_nomem(void)
93{
94 buffer_puts(&bl,WHO);
95 buffer_puts(&bl,": FATAL: out of memory\n");
96 buffer_flush(&bl);
97 pam_exit(111);
98}
99
100static int get_password(stralloc *in)
101{
102 int fd;
103 struct stat st;
104
105 fd = open_read(in->s);
106 if (fd == -1) return 0;
107
108 if (fstat(fd,&st) == -1)
109 { log_error("unable to read: ",in->s,"FATAL"); pam_exit(111); }
110 if ((st.st_mode & 0477) != 0400)
111 { log_error("wrong file permissions for: ",in->s,"FATAL"); pam_exit(111); }
112 if (readclose_append(fd,&password,BUFSIZE_LINE) == -1)
113 { log_error("unable to read: ",in->s,"FATAL"); pam_exit(111); }
114
115 if (!stralloc_copyb(in,password.s,password.len - 1)) return -1;
116 if (!stralloc_0(in)) return -1;
117
118 return password.len;
119}
120
121static void read_passwd(void)
122{
123 int match = 0;
124
125 if (!bindpwds.len) {
126 buffer_init(&bp,buffer_unixread,FDPWD,bspace,sizeof(bspace));
127 if (getln(&bp,&bindpwds,&match,'\0') == -1)
128 { log_error("unable to read password",0,"FATAL"); pam_exit(111); }
129 close(FDPWD);
130 if (match) --bindpwds.len;
131 }
132 if (!stralloc_0(&bindpwds)) temp_nomem();
133}
134
135/* This is a clumsy way to convert DNs into three-level Internet domain names.
136 DNs can be given as dc=,dc= (RFC 4514); or as legacy O=/OU=/C= and mapped in correct order
137 Though domain names can be given in UTF8, standard ASCII is supported only and not converted to Punycode */
138
139static int dn2domain(stralloc *domain,char *dn,int flagdn)
140{
141 char c;
142 char *ou = 0;
143 char *org = 0;
144 char *country = 0;
145 int j, k, l = 0;
146
147 int dnlen = str_len(dn);
148 if (!stralloc_copys(domain,"")) return -1;
149
150 if (flagdn == 1) c = ',';
151 if (flagdn == 2) c = '/';
152
153 /* dc= case */
154
155 for (int i = 0; i < dnlen; ++i) {
156 if (case_startb(dn + i,3,"dc=")) {
157 j = str_chr(dn + i,c);
158 if (dn[i + j] == c || dnlen == i + j) {
159 if (!stralloc_catb(domain,dn + i + 3,j - 3)) return -1;
160 if (dnlen != i + j)
161 if (!stralloc_cats(domain,".")) return -1;
162 }
163 }
164 }
165 if (domain->len > 2) {
166 if (!stralloc_0(domain)) return -1;
167 return 1;
168 }
169
170 /* o= case */
171
172 for (int i = 0; i < dnlen; ++i) {
173 if (case_startb(dn + i,3,"ou=")) {
174 j = str_chr(dn + i,c);
175 if (dn[i + j] == c || dnlen == i + j) ou = dn + i + 3;
176 }
177 if (case_startb(dn + i,2,"o=")) {
178 k = str_chr(dn + i,c);
179 if (dn[i + k] == c || dnlen == i + k) org = dn + i + 2;
180 }
181 if (case_startb(dn + i,2,"c=")) {
182 l = str_chr(dn + i,c);
183 if (dn[i + l] == c || dnlen == i + l) country = dn + i + 2;
184 }
185 }
186 /* put everything together in correct order */
187
188 if (org && *org && country && *country) {
189 if (ou && *ou) {
190 if (!stralloc_catb(domain,ou,j - 3)) return -1;
191 if (!stralloc_cats(domain,".")) return -1;
192 }
193 if (!stralloc_catb(domain,org,k - 2)) return -1;
194 if (!stralloc_cats(domain,".")) return -1;
195 if (!stralloc_catb(domain,country,l)) return -1;
196 if (!stralloc_0(domain)) return -1;
197 }
198 if (domain->len > 2) return 2;
199
200 return 0;
201}
202
203static stralloc cafile = {0};
204static stralloc cadir = {0};
205static stralloc certfile = {0};
206static stralloc keyfile = {0};
207
208/* LDAP *ldap_setup() is used to prepare an (1) unencrypted, (2) StartTLS,
209 or (3) strong bind connection to a LDAP server, given flagtls on input */
210
211LDAP *ldap_setup(int flagtls,char *host,int port)
212{
213 LDAP *ld;
214
215/* Simple bind */
216
217 if (!(ld = ldap_init(host,port)))
218 { log_error("unable to initialize LDAP bind",0,"FATAL"); pam_exit(111); }
219
220// Specify version 3; the default is version 2; required for TLS
221
222 if (ldap_set_option(ld,LDAP_OPT_PROTOCOL_VERSION,(void *)&version) != LDAP_OPT_SUCCESS)
223 { log_error("unable to initialize LDAP vers 3 bind",0,"FATAL"); pam_exit(111); }
224
225/* StartTLS bind */
226
227 if (flagtls == 1) {
228 if (ldap_start_tls_s(ld,NULL,NULL) != LDAP_SUCCESS)
229 { log_error("unable to connect via StartTLS to the LDAP server",0,"FATAL"); pam_exit(111); }
230
231 if (cafile.len > 1)
232 if (ldap_set_option(ld,LDAP_OPT_X_TLS_CACERTFILE,(void *)cafile.s) != LDAP_OPT_SUCCESS)
233 { log_error("unable to set LDAP CA file: ",cafile.s,"FATAL"); pam_exit(111); }
234
235 if (cadir.len > 1)
236 if (ldap_set_option(ld,LDAP_OPT_X_TLS_CACERTDIR,(void *)cadir.s) != LDAP_OPT_SUCCESS)
237 { log_error("unable to set LDAP CA directory: ",cadir.s,"FATAL"); pam_exit(111); }
238 }
239
240/* Strong bind */
241
242 if (flagtls == 2) { // setup TLS context -- options first
243 if (certfile.len && keyfile.len) {
244 if (ldap_set_option(ld,LDAP_OPT_X_TLS_CERTFILE,certfile.s) != LDAP_OPT_SUCCESS)
245 { log_error("unable to initialize LDAP vers 3 bind",0,"FATAL"); pam_exit(111); }
246 if (ldap_set_option(ld,LDAP_OPT_X_TLS_KEYFILE,keyfile.s) != LDAP_OPT_SUCCESS)
247 { log_error("unable to initialize LDAP vers 3 bind",0,"FATAL"); pam_exit(111); }
248 }
249 if (ldap_install_tls(ld) != LDAP_SUCCESS)
250 { log_error("unable to connect via TLS for strong bind to the LDAP server",0,"FATAL"); pam_exit(111); }
251 }
252
253 return ld;
254}
255
256static int ldap_testbind(int flag,char *host,unsigned long port)
257{
258 LDAP *ld;
259 int r;
260
261 ld = ldap_setup(flag,host,port);
262 r = ldap_simple_bind_s(ld,binddn.s,bindpw.s);
263 if (r == LDAP_SUCCESS) ldap_unbind(ld);
264
265 return r;
266}
267
268/* The ldap_mailaddr() recipient Mailbox given by the RECIPIENT interface
269 uses the checkpassword API:
270 a) We start from existing DN in the LDAP (a 'proxy' from 'ldapbind') allowing
271 b) to search for the MAIL attribute in the subtree given by the 'bindbase' from 'ldapbind'.
272 We return the DN as AUTHUSER.
273 The search attribute is customizable via 'ldapbind' but defaults to MAIL.
274*/
275
276static int ldap_mailaddr(int flag,char *host,unsigned long port,char *mailaddr,char *filter)
277{
278 LDAP *ld;
279 LDAPMessage *result, *entry;
280 char *search = "mail";
281 char *dn, **mail;
282 int r;
283 int addrok = 0;
284
285 r = str_len(mailaddr);
286 if (r == 0 || str_chr(mailaddr,'@') == r)
287 { log_error("invalid mail address : ",mailaddr,"ERROR"); pam_exit(2); }
288
289 ld = ldap_setup(flag,host,port);
290
291 if (ldap_simple_bind_s(ld,binddn.s,bindpw.s) != LDAP_SUCCESS)
292 { log_error("can't bind with LDAP server for mailaddr. DN:",binddn.s,"ERROR"); pam_exit(110); }
293
294 // Construct filter and search with limited scope
295
296 if (filter && *filter) search = filter;
297 if (!stralloc_copys(&filters,"(")) temp_nomem();
298 if (!stralloc_cats(&filters,search)) temp_nomem();
299 if (!stralloc_cats(&filters,"=")) temp_nomem();
300 if (!stralloc_cats(&filters,mailaddr)) temp_nomem();
301 if (!stralloc_cats(&filters,")")) temp_nomem();
302 if (!stralloc_0(&filters)) temp_nomem();
303
304 r = ldap_search_s(ld,bindbase.s,scope,filters.s,NULL,0,&result);
305 if (r)
306 { log_error("search failed: ",ldap_err2string(r),"ERROR"); pam_exit(110); }
307
308 entry = ldap_first_entry(ld,result);
309 if (!entry) { ldap_unbind(ld); pam_exit(1); } // Not found
310
311 dn = ldap_get_dn(ld,result); // DN = Authuser
312 mail = ldap_get_values(ld,entry,search);
313 addrok = ldap_count_values(mail);
314
315 ldap_msgfree(result);
316 ldap_unbind(ld);
317
318 if (!addrok) { log_error("mail address not found: ",mailaddr,"INFO"); pam_exit(1); }
319
320 /* Done; set environment variables */
321
322 if (!env_put("AUTHUSER",dn)) pam_exit(111);
323 return 0;
324}
325
326/* The ldap_authuser() needs to be authorized by some information over the checkpassword API:
327 a) The DN + password in the LDAP (LDAP aware).
328 b) Username + password, where the username includes the userid + domain: userid@domain.
329 From here, we construct the DN for the LDAP bind: CN = userid (default)
330 UserAttr := userid, eg. UID: UID = userid; UserAttr defined in 'ldapbind'.
331 c) We also check, whether the DN has a (MAIL) attribute value matching the SMTP Mailfrom:<>.
332 Here, the default attribute is MAIL, but can be customized as well in 'ldapbind'.
333 For the retrieved DN entry, we iterate for that attribute over of all values.
334*/
335
336static int ldap_authuser(int flag,char *host,unsigned long port,char *domain,char *user,char *pwd,char *id,char *filter)
337{
338 LDAP *ld;
339 LDAPMessage *result, *entry;
340 unsigned long scope = LDAP_SCOPE_BASE;
341 char *search = "mail";
342 char **mail;
343 char *mailfrom;
344 int addrok = 0;
345 int r;
346
347 mailfrom = env_get("MAILFROM");
348 if (!mailfrom || !*mailfrom)
349 { log_error("Missing Mailfrom:<> for user: ",user,"FATAL"); pam_exit(2); }
350
351 ld = ldap_setup(flag,host,port);
352
353 /* Fabricate DN to use for binding; if not given directly */
354
355 if (domain && *domain) {
356 if (id && *id) { // custom DN identifier
357 if (!stralloc_copys(&binddn,id)) temp_nomem();
358 } else
359 if (!stralloc_copys(&binddn,"cn")) temp_nomem(); // default DN cn=,dc=,...
360 if (!stralloc_cats(&binddn,"=")) temp_nomem();
361 if (!stralloc_cats(&binddn,user)) temp_nomem();
362
363 if (!stralloc_cats(&binddn,",dc=")) temp_nomem();
364 for (int i = 0; i < str_len(domain); ++i) {
365 if (domain[i] == '.') {
366 if (!stralloc_cats(&binddn,",dc=")) temp_nomem();
367 } else
368 if (!stralloc_catb(&binddn,domain + i,1)) temp_nomem();
369 }
370 if (!stralloc_0(&binddn)) temp_nomem();
371 } else
372 if (!stralloc_copys(&binddn,user)) temp_nomem(); // User DN given
373
374 if (ldap_simple_bind_s(ld,binddn.s,pwd) != LDAP_SUCCESS)
375 { log_error("can't bind with LDAP server for auth. DN:",binddn.s,"INFO"); pam_exit(111); }
376
377 // Construct filter and search with limited scope
378
379 if (filter && *filter) search = filter;
380 if (!stralloc_copys(&filters,"(")) temp_nomem();
381 if (!stralloc_cats(&filters,search)) temp_nomem();
382 if (!stralloc_cats(&filters,"=*)")) temp_nomem();
383 if (!stralloc_0(&filters)) temp_nomem();
384
385 r = ldap_search_s(ld,binddn.s,scope,filters.s,NULL,0,&result);
386 if (r)
387 { log_error("search failed: ",ldap_err2string(r),"ERROR"); pam_exit(110); }
388
389 entry = ldap_first_entry(ld,result);
390 if (!entry) { ldap_msgfree(result); ldap_unbind(ld); pam_exit(1); }
391
392 /* Look for a corresponding mailaddr matching $MAILFROM */
393
394 mail = ldap_get_values(ld,entry,search);
395 for (int i = 0; i < ldap_count_values(mail); ++i)
396 if (case_equals(mailfrom,mail[i])) addrok++;
397
398 ldap_msgfree(result);
399 ldap_msgfree(entry);
400 ldap_unbind(ld);
401
402 if (!addrok) { log_error("mail address not found: ",mailfrom,"INFO"); pam_exit(1); }
403
404 /* Done, set environment variables */
405
406 if (!env_put("AUTHUSER",binddn.s)) pam_exit(111);
407 return 0;
408}
409
410/* The ldap_mboxuser() needs to be authorized by some information over the checkpassword API:
411 a) The DN + password in the LDAP (LDAP aware).
412 b) Username + password, where the username includes the userid + domain: userid@domain.
413 From here, we construct the DN for the LDAP bind:
414 CN = userid (default)
415 UserAttr := userid, eg. UID: UID = userid; UserAttr defined in 'ldapbind'.
416 c) We require, that for the retrieved DN a HOMEDIRECTORY attribute exists (eg. PosixAccount schema).
417 Here, the default attribute is 'homeDirectory', but can be customized as well in 'ldapbind'.
418 d) For the given DN entry, we iterate for that attribute over of all values and check
419 its existance while opening it. Thus read access is required.
420 e) We apply a chdir() to the HOMEDIRECTORY; if possible.
421 f) The existance of a subsequent 'mbox' or 'maildir' is subject of the called program.
422
423 Note: While for usual Unix users HOMEDIRECTORY, it is typically '/home/user',
424 for a virtual mail manager (VMM) it is the VMM's home directory.
425*/
426
427static int ldap_mboxuser(int flag,char *host,unsigned long port,char *domain,char *user,char *pwd,char *id,char *filter)
428{
429 LDAP *ld;
430 LDAPMessage *result, *entry;
431 unsigned long scope = LDAP_SCOPE_BASE;
432 char *search = "homeDirectory";
433 char **homedir;
434 int fd;
435 int homeok = 0;
436 int r;
437
438 ld = ldap_setup(flag,host,port);
439
440 if (domain && *domain) {
441 if (id && *id) { // custom DN identifier
442 if (!stralloc_copys(&binddn,id)) temp_nomem();
443 } else
444 if (!stralloc_copys(&binddn,"cn")) temp_nomem(); // default DN cn=,dc=,...
445 if (!stralloc_cats(&binddn,"=")) temp_nomem();
446 if (!stralloc_cats(&binddn,user)) temp_nomem();
447
448 if (!stralloc_cats(&binddn,",dc=")) temp_nomem();
449 for (int i = 0; i < str_len(domain); ++i) {
450 if (domain[i] == '.') {
451 if (!stralloc_cats(&binddn,",dc=")) temp_nomem();
452 } else
453 if (!stralloc_catb(&binddn,domain + i,1)) temp_nomem();
454 }
455 if (!stralloc_0(&binddn)) temp_nomem();
456 } else
457 if (!stralloc_copys(&binddn,user)) temp_nomem(); // User DN given
458
459 if (ldap_simple_bind_s(ld,binddn.s,pwd) != LDAP_SUCCESS)
460 { log_error("can't bind with LDAP server for home. DN:",binddn.s,"INFO"); pam_exit(111); }
461
462 // Construct filter and search with limited scope
463
464 if (filter && *filter) search = filter;
465 if (!stralloc_copys(&filters,"(")) temp_nomem();
466 if (!stralloc_cats(&filters,search)) temp_nomem();
467 if (!stralloc_cats(&filters,"=*)")) temp_nomem();
468 if (!stralloc_0(&filters)) temp_nomem();
469
470 r = ldap_search_s(ld,binddn.s,scope,filters.s,NULL,0,&result);
471 if (r)
472 { log_error("search failed: ",ldap_err2string(r),"ERROR"); pam_exit(110); }
473
474 entry = ldap_first_entry(ld,result);
475 if (!entry) { ldap_msgfree(result); ldap_unbind(ld); pam_exit(1); } // Not found
476 homedir = ldap_get_values(ld,entry,search);
477
478 for (int i = 0; i < ldap_count_values(homedir); ++i) {
479 fd = open_read(homedir[i]);
480 if (fd == -1) continue;
481 if (chdir(homedir[i]) != -1) {
482 if (!env_put("HOME",homedir[i])) pam_exit(111);
483 homeok++;
484 }
485 }
486
487 ldap_memfree(search);
488 ldap_msgfree(entry);
489 ldap_unbind(ld);
490
491 if (!homeok)
492 { log_error("can't change to the home directory for DN:",binddn.s,"ERROR"); pam_exit(1); }
493
494 return 0;
495}
496
497/* ldap_proxyauth():
498 1. Do an initial bind using BindDN and BindPW from ldapbind; considering a hierarchy given by a UserAttr extension.
499 2. Perform a search for the 'userPersonalName' in that branch resulting in a ProxyAuth user (UPN).
500 3. Take the UPN's local part and perform a second bind with the given password of the user.
501 4. If the 'home' variable is provided (taken from the filter value of ldapbind),
502 construct the HOMEDIR of that user and chdir to that.
503 Note: The mapping of BindDN <=> UserAttr <=> UPN + user password needs to realized in the LDAP.
504*/
505
506static int ldap_proxyauth(int flag,char *host,unsigned long port,char *mailaddr,char *pwd,char *id,char *home)
507{
508 LDAP *ld;
509 LDAPMessage *result;
510 stralloc binduser = {0};
511 stralloc homedir = {0};
512 int scope = LDAP_SCOPE_BASE;
513 int fd;
514 int i, j, r;
515 int homeok = 0;
516
517 ld = ldap_setup(flag,host,port);
518
519 /* First BindDN with subordinate 'id' as context: DN:cn=bindCN,cn=id,dc=x,dc=y,dc=z */
520
521 i = str_chr(binddn.s,',');
522 if (case_starts(binddn.s + i + 1,"dc=") && id && *id) {
523 if (!stralloc_copyb(&binduser,binddn.s,i)) temp_nomem();
524 if (!stralloc_cats(&binduser,",cn=")) temp_nomem();
525 if (!stralloc_cats(&binduser,id)) temp_nomem();
526 if (!stralloc_cats(&binduser,binddn.s + i)) temp_nomem();
527 if (!stralloc_0(&binduser)) temp_nomem();
528 }
529 else
530 { log_error("can't setup proxy auth user for DN:",binddn.s,"ERROR"); pam_exit(110); }
531
532 if (ldap_simple_bind_s(ld,binduser.s,bindpw.s) != LDAP_SUCCESS)
533 { log_error("can't bind with LDAP server for proxy. DN:",binduser.s,"INFO"); pam_exit(111); }
534
535 /* Do we have a 'UserPrincipalName' with that 'mailaddr' in that scope? */
536
537 if (!stralloc_copys(&filters,"(UserPrincipalName=")) temp_nomem();
538 if (!stralloc_cats(&filters,mailaddr)) temp_nomem();
539 if (!stralloc_cats(&filters,")")) temp_nomem();
540 if (!stralloc_0(&filters)) temp_nomem();
541
542 r = ldap_search_s(ld,binduser.s,scope,filters.s,NULL,0,&result);
543 if (r)
544 { log_error("UserPrincipalName not found in LDAP ",mailaddr,"INFO"); pam_exit(1); }
545
546 /* Second bind: If found, we try to bind with the localpart of 'mailaddr':
547 DN:cn=local,cn=id,dc=x,dc=y,dc=z
548 */
549
550 j = str_chr(mailaddr,'@');
551 if (!stralloc_copys(&binduser,"cn=")) temp_nomem();
552 if (!stralloc_catb(&binduser,mailaddr,j)) temp_nomem();
553 if (!stralloc_cats(&binduser,",cn=")) temp_nomem();
554 if (!stralloc_cats(&binduser,id)) temp_nomem();
555 if (!stralloc_cats(&binduser,binddn.s + i)) temp_nomem(); // domainpart of binddn
556 if (!stralloc_0(&binduser)) temp_nomem();
557
558 if (ldap_simple_bind_s(ld,binduser.s,pwd) != LDAP_SUCCESS)
559 { log_error("can't bind with LDAP server. DN:",binduser.s,"INFO"); pam_exit(1); }
560
561 /* Verify it! */
562
563 if (!stralloc_copys(&filters,"(objectclass=*)")) temp_nomem();
564 if (!stralloc_0(&filters)) temp_nomem();
565
566 r = ldap_search_s(ld,binduser.s,scope,filters.s,NULL,0,&result);
567 if (r)
568 { log_error("UserPincipalName rotten in LDAP",ldap_err2string(r),"ERROR"); ldap_unbind(ld); pam_exit(110); }
569
570 if (!env_put("USER",mailaddr)) pam_exit(111);
571
572 /* Look for a mbox/maildir path for eg. vpopmail/vmailmgr */
573
574 if (home && *home) {
576 if (!stralloc_cats(&homedir,"/")) temp_nomem();
577 if (!stralloc_catb(&homedir,mailaddr,j)) temp_nomem();
578 if (!stralloc_0(&homedir)) temp_nomem();
579 fd = open_read(homedir.s);
580 if (fd != -1 && chdir(homedir.s) != -1) {
581 if (!env_put("HOME",homedir.s)) pam_exit(111);
582 homeok++;
583 }
584 }
585
586 ldap_unbind(ld);
587
588 if (home && !homeok)
589 { log_error("can't change to the home directory of user:",mailaddr,"ERROR"); pam_exit(1) ; }
590
591 if (!env_put("AUTHUSER",binduser.s)) pam_exit(111);
592 return 0;
593}
594
595int main(int argc,char * const argv[])
596{
597 char *authuser = 0;
598 char *ldaparam = 0;
599 char *domain = 0;
600 char *pwgiven = 0;
601 unsigned long port = PORT_LDAP;
602 int authlen = 0;
603 int buflen = 0;
604 int domlen = 0;
605 int flagauth = 0;
606 int flaghome = 0;
607 int flagprox = 0;
608 int flagstls = 0;
609 int dnseen = 0;
610 int opt;
611 int i = 0;
612 int s, p, u, k;
613 int rc;
614
615 while ((opt = getoptb(argc,(char **)argv,"ahps")) !=opteof)
616 switch (opt) {
617 case 'a': flagauth = 1; break;
618 case 'h': flaghome = 1; break;
619 case 'p': flagprox = 1; break;
620 case 's': flagstls = 1; break;
621 }
622 argv += optind;
623 argc -= optind;
624
625 env_unset("USER");
626
627 /* Read input from FDAUTH */
628
629 for (;;) {
630 do {
631 rc = read(FDAUTH,authbuf + buflen,sizeof(authbuf) - buflen);
632 } while ((rc == -1) && (errno == EINTR));
633 if (rc == -1) pam_exit(111);
634 if (rc == 0) break;
635 buflen += rc;
636 if (buflen >= sizeof(authbuf)) pam_exit(2);
637 }
638 close(FDAUTH);
639
640 authuser = authbuf + i; /* username or DN */
641 if (i == buflen) pam_exit(2);
642 while (authbuf[i++]) /* password */
643 if (i == buflen) pam_exit(2);
644 pwgiven = authbuf + i;
645 if (i == buflen) pam_exit(2);
646
647 authlen = str_len(authuser);
648 if (!stralloc_copyb(&user,authuser,authlen)) temp_nomem();
649
650 if ((i = byte_rchr(authuser,authlen,'@'))) /* domain */
651 if (i < authlen && authuser[i] == '@') {
652 domain = authuser + i + 1;
653 domlen = str_len(domain);
654 case_lowerb(domain,domlen);
655 if (!stralloc_copyb(&user,authuser,i)) temp_nomem();
656 }
657 if (!stralloc_0(&user)) pam_exit(111);
658 if ((i = str_chr(authuser,',')) && authuser[i] == ',') dnseen = 1;
659 if ((i = str_chr(authuser,'/')) && authuser[i] == '/') dnseen = 2; // old style
660 if (!env_put("USER",authuser)) pam_exit(111);
661
662 /* Read control file users/ldapauth and go for checks */
663
664 if (chdir(auto_qmail) == -1) pam_exit(110);
665
666 switch (control_readfile(&ldapcntl,"control/ldapbind",0)) {
667 case -1: pam_exit(110);
668 case 0: if (!constmap_init(&mapldapauth,"",0,1)) temp_nomem();
669 case 1: if (!constmap_init(&mapldapauth,ldapcntl.s,ldapcntl.len,1)) temp_nomem();
670 }
671
672 /* For a propper lookup we need to convert X.500 DNs to domain names;
673 dc= type style and old styles are supported */
674
675 if (dnseen) {
676 dn2domain(&domaindn,authuser,dnseen);
677 domain = domaindn.s;
678 }
679
680 /* Check for disabled authuser/domains - only working if user@domain given*/
681
682 if (!stralloc_copys(&disabled,"!")) temp_nomem();
683 if (!stralloc_catb(&disabled,authuser,authlen)) temp_nomem();
685
686 if (domlen) {
687 if (!stralloc_copys(&disabled,"!")) temp_nomem();
688 if (!stralloc_catb(&disabled,domain,domlen)) temp_nomem();
690 }
691
692 if (!ldaparam && domlen)
693 ldaparam = constmap(&mapldapauth,domain,domlen); // 1. ldap server by domain
694 if (!ldaparam)
695 ldaparam = constmap(&mapldapauth,"*",1); // 2. one ldap for all
696 if (dnseen) domain = 0; // only used for lookup in ldapbind
697
698 if (!ldaparam) pam_exit(1);
699
700 /* Evaluate LDAP lookup params:
701 p i s i i i u i i k
702 domain:Host;[s]port|Base:Scope|BindDN|BindPwd|Filter:UserAttr|CA|Cert:keyfile*/
703
704 i = 0;
705 int len = 0;
706 int paramlen = str_len(ldaparam);
707 if (!stralloc_copys(&bindhost,"localhost")) temp_nomem(); /* Default LDAP host */
708
709 i = str_chr(ldaparam,'|'); /* Host;Port */
710 len += i;
711 if (ldaparam[i] == '|' || len == paramlen) {
712 ldaparam[i] = 0;
713 p = str_chr(ldaparam,';'); /* Port */
714 if (ldaparam[p] == ';') {
715 ldaparam[p] = 0;
716 if (ldaparam[p + 1] == 's') { flagstls = 2; port = PORT_LDAPS; ++p; }
717 if (p < i) scan_ulong(ldaparam + p + 1,&port);
718 }
719 if (i) /* can be omitted */
720 if (!stralloc_copys(&bindhost,ldaparam)) temp_nomem();
721 }
722 if (!stralloc_0(&bindhost)) temp_nomem();
723
724 if (len < paramlen) {
725 ldaparam += i + 1;
726 i = str_chr(ldaparam,'|'); /* Base:Scope */
727 len += i + 1;
728 if (ldaparam[i] == '|' || len == paramlen) {
729 ldaparam[i] = 0;
730 s = str_chr(ldaparam,':'); /* Scope */
731 if (ldaparam[s] == ':') {
732 ldaparam[s] = 0;
733 if (s < i) scan_ulong(ldaparam + s + 1,&scope);
734 }
735 if (!stralloc_copys(&bindbase,ldaparam)) temp_nomem();
736 if (!stralloc_0(&bindbase)) temp_nomem();
737 }
738 }
739
740 if (len < paramlen) {
741 ldaparam += i + 1;
742 i = str_chr(ldaparam,'|'); /* Bind DN */
743 len += i + 1;
744 if (ldaparam[i] == '|' || len == paramlen) {
745 ldaparam[i] = 0;
746 if (!stralloc_copys(&binddn,ldaparam)) temp_nomem();
747 if (!stralloc_0(&binddn)) temp_nomem();
748 }
749 }
750
751 if (len < paramlen) {
752 ldaparam += i + 1;
753 i = str_chr(ldaparam,'|'); /* Bind PWD */
754 len += i + 1;
755 if (ldaparam[i] == '|' || len == paramlen) {
756 ldaparam[i] = 0;
757 if (!stralloc_copys(&bindpw,ldaparam)) temp_nomem();
758 if (!stralloc_0(&bindpw)) temp_nomem();
759 switch (get_password(&bindpw)) {
760 // case 0: log_error("pwd taken from ldapbind",0,"INFO"); break;
761 case -1: log_error("can't copy pwd",0,"FATAL"); pam_exit(111);
762 // default: log_error("pwd read from file",0,"INFO");
763 }
764 }
765 }
766
767 if (len < paramlen) {
768 ldaparam += i + 1; /* Filter */
769 i = str_chr(ldaparam,'|');
770 len += i + 1;
771 if (ldaparam[i] == '|' || len == paramlen) {
772 ldaparam[i] = 0;
773 u = str_chr(ldaparam,':'); /* User Attribute */
774 if (ldaparam[u] == ':') {
775 ldaparam[u] = 0;
776 if (!stralloc_copys(&userattr,ldaparam + u + 1)) temp_nomem();
777 if (!stralloc_0(&userattr)) temp_nomem();
778 }
779 if (!stralloc_copys(&filter,ldaparam)) temp_nomem();
780 if (!stralloc_0(&filter)) temp_nomem();
781 }
782 }
783
784 if (len < paramlen) {
785 ldaparam += i + 1;
786 i = str_chr(ldaparam,'|'); /* CA cert */
787 len += i + 1;
788 if (ldaparam[i] == '|' || len == paramlen) {
789 ldaparam[i] = 0;
790 if (ldaparam[i - 1] == '/') { /* CA dir/ */
791 if (!stralloc_copys(&cadir,ldaparam)) temp_nomem();
792 if (!stralloc_0(&cadir)) temp_nomem();
793 } else {
794 if (!stralloc_copys(&cafile,ldaparam)) temp_nomem();
795 if (!stralloc_0(&cafile)) temp_nomem();
796 }
797 }
798 }
799
800 if (len < paramlen) {
801 ldaparam += i + 1;
802 i = str_chr(ldaparam,'|'); /* Client cert */
803 len += i + 1;
804 if (ldaparam[i] == '|' || len == paramlen) {
805 ldaparam[i] = 0;
806 k = str_chr(ldaparam,':'); /* keyfile */
807 if (ldaparam[k] == ':') {
808 ldaparam[k] = 0;
809 if (!stralloc_copys(&keyfile,ldaparam + k + 1)) temp_nomem();
810 if (!stralloc_0(&keyfile)) temp_nomem();
811 }
812 if (!stralloc_copys(&certfile,ldaparam)) temp_nomem();
813 if (!stralloc_0(&certfile)) temp_nomem();
814 }
815 }
816
817 /* Get and read passwords from file or FDAUTH */
818
819 if (bindpw.s[0] == '*') {
820 read_passwd();
821 for (i = 0; i < bindpwds.len; i++) {
822 if (bindpwds.s[i] == '\0' || bindpwds.s[i] == ' ' ) {
823 if (!stralloc_copyb(&bindpw,bindpwds.s,i - 1)) temp_nomem();
824 if (!stralloc_0(&bindpw)) temp_nomem();
825 if (ldap_testbind(flagstls,bindhost.s,port) == LDAP_SUCCESS) break; // gotcha
826 bindpwds.s = bindpwds.s + i;
827 }
828 }
829 }
830
831 if (!stralloc_copys(&password,pwgiven)) temp_nomem();
832 if (!stralloc_0(&password)) temp_nomem();
833
834 if (flagauth)
835 ldap_authuser(flagstls,bindhost.s,port,domain,user.s,password.s,userattr.s,filter.s);
836 else if (flagprox)
837 ldap_proxyauth(flagstls,bindhost.s,port,authuser,password.s,userattr.s,filter.s);
838 else if (flaghome)
839 ldap_mboxuser(flagstls,bindhost.s,port,domain,user.s,password.s,userattr.s,filter.s);
840 else
841 ldap_mailaddr(flagstls,bindhost.s,port,authuser,filter.s);
842
843 for (i = 0; i < sizeof(authbuf); ++i) authbuf[i] = 0;
844
845 pathexec(argv);
846 pam_exit(111);
847}
char auto_qmail[]
int main()
Definition: chkshsgr.c:6
int constmap_init(struct constmap *, char *, int, int)
int control_readfile(stralloc *sa, char *fn, int flagme)
Definition: control.c:87
int stralloc_copys(stralloc *, char const *)
void _exit(int)
void c(char *, char *, char *, int, int, int)
Definition: install.c:70
void p(char *, char *, int, int, int)
Definition: install.c:52
char host[256]
Definition: hostname.c:5
int match
Definition: matchup.c:196
stralloc homedir
int fd
stralloc filters
Definition: qmail-ldapam.c:68
#define FDPWD
Definition: qmail-ldapam.c:40
stralloc password
Definition: qmail-ldapam.c:67
unsigned long scope
Definition: qmail-ldapam.c:71
buffer ba
Definition: qmail-ldapam.c:45
stralloc disabled
Definition: qmail-ldapam.c:54
char authbuf[BUFSIZE_AUTH]
Definition: qmail-ldapam.c:44
#define PORT_LDAP
Definition: qmail-ldapam.c:41
stralloc domaindn
Definition: qmail-ldapam.c:66
stralloc ldapcntl
Definition: qmail-ldapam.c:53
stralloc bindpw
Definition: qmail-ldapam.c:59
stralloc user
Definition: qmail-ldapam.c:65
buffer bl
Definition: qmail-ldapam.c:50
stralloc bindpwds
Definition: qmail-ldapam.c:60
stralloc bindhost
Definition: qmail-ldapam.c:62
#define FDAUTH
Definition: qmail-ldapam.c:38
struct constmap mapldapauth
Definition: qmail-ldapam.c:52
char bspace[512]
Definition: qmail-ldapam.c:46
buffer bp
Definition: qmail-ldapam.c:47
int version
Definition: qmail-ldapam.c:72
stralloc userattr
Definition: qmail-ldapam.c:69
LDAP * ldap_setup(int flagtls, char *host, int port)
Definition: qmail-ldapam.c:211
char logbuf[BUFSIZE_SMALL]
Definition: qmail-ldapam.c:49
#define PORT_LDAPS
Definition: qmail-ldapam.c:42
#define FDLOG
Definition: qmail-ldapam.c:39
stralloc binddn
Definition: qmail-ldapam.c:58
#define WHO
Definition: qmail-ldapam.c:36
stralloc bindbase
Definition: qmail-ldapam.c:61
stralloc filter
Definition: qmail-ldapam.c:63
unsigned int port
stralloc home
Definition: qmail-pw2u.c:99
buffer in
Definition: qmail-pw2u.c:240
int flagauth
Definition: qmail-remote.c:60
int flagtls
Definition: qmail-remote.c:63
int j
Definition: qmail-send.c:926
stralloc mailfrom
Definition: qmail-smtpd.c:532
#define BUFSIZE_LINE
Definition: qmail.h:8
#define BUFSIZE_SMALL
Definition: qmail.h:12
#define BUFSIZE_AUTH
Definition: qmail.h:9
uint32_t k[64]
Definition: sha256.c:27
stralloc domain
Definition: spf.c:34
stralloc certfile
Definition: qmail-remote.c:430
stralloc keyfile
Definition: qmail-remote.c:431
stralloc cadir
Definition: qmail-remote.c:429
stralloc cafile
Definition: qmail-remote.c:428
void temp_nomem(void)
Definition: qmail-remote.c:149