#include #include #include "global.h" #include "stralloc.h" #include "buffer.h" #include "auto_qmail.h" #include "case.h" #include "control.h" #include "constmap.h" #include "str.h" #include "fmt.h" #include "fd.h" #include "open.h" #include "byte.h" #include "scan.h" #include "md5.h" #include "hmac_md5.h" #include "sha1.h" #include "sha256.h" #include "pathexec.h" #include "prot.h" #include "wait.h" #include "sig.h" #include "error.h" #include "env.h" #include "qmail.h" #define FDAUTH 3 #define FDGOSSIP 1 #define SOCKET_CALL "-s" #define DOVECOT_SERVICE "-x" extern char *crypt(); #include static struct passwd *pw; #include "hasspnam.h" #ifdef HASGETSPNAM #include static struct spwd *spw; #endif #include "hasuserpw.h" #ifdef HASUSERPW #include static struct userpw *upw; #endif /** @file qmail-authuser.c @brief user authentication for qmail-smtpd/qmail-pop3d,bincimapd @return 0: ok 1: credentials failure 2: qmail-authuser is misused 110: can't read controls 111: temporary problem checking the password */ char authbuf[BUFSIZE_AUTH]; buffer ba = BUFFER_INIT(write,FDAUTH,authbuf,sizeof(authbuf)); struct constmap mapauthuser; stralloc authfile = {0}; stralloc disabled = {0}; stralloc user = {0}; // user w/o domain appended stralloc homedir = {0}; stralloc shell = {0}; /** @brief Supported storage methods: (1) authuser:[=]plainpasswd, (2) authuser:%hashpasswd, (3) authuser:?, authuser:!, *:?, *:! (! -> +environment) (4) x:+ -> checkvpw; x = { user@domain, @domain, @ } vmailmgr (5) x:& -> vchkpw; x = { user@domain, @domain, @ } vpopmail (6) x:= -> qmail-client; x = { user@domain, @domain, @ } dovecot Supported auth methods: user/login/plain: (1,2,3,4,5,6), cram-md5/apop: (1,5) **/ void exit(int fail) { int i; for (i = 0; i < sizeof(authbuf); ++i) authbuf[i] = 0; _exit(fail); } int dig_ascii(char *digascii,const char *digest,const int len) { static const char hextab[] = "0123456789abcdef"; int j; for (j = 0; j < len; j++) { digascii[2 * j] = hextab[digest[j] >> 4]; digascii[2 * j + 1] = hextab[digest[j] & 0x0f]; } digascii[2 * len] = '\0'; return (2*j); // 2*len } int auth_sha1(char *pwdhash,char *response) { unsigned char digest[20]; unsigned char digascii[41]; sha1_hash(digest,response,str_len(response)); dig_ascii(digascii,digest,20); return str_diffn(digascii,pwdhash,40); } int auth_sha256(char *pwdhash,char *response) { unsigned char digest[32]; unsigned char digascii[65]; sha256_hash(digest,response,str_len(response)); dig_ascii(digascii,digest,32); return str_diffn(digascii,pwdhash,64); } int auth_md5(char *pwdhash,char *response) { MD5_CTX ctx; unsigned char digest[16]; unsigned char digascii[33]; MD5Init(&ctx); MD5Update(&ctx,response,str_len(response)); MD5Final(digest,&ctx); dig_ascii(digascii,digest,16); return str_diffn(digascii,pwdhash,32); } int auth_hash(char *password,char *response) { switch (str_len(password)) { case 32: return auth_md5(password,response); case 40: return auth_sha1(password,response); case 64: return auth_sha256(password,response); default: return -1; } } int auth_unix(char *user,char* response) { char *encrypted = 0; char *stored = 0; int r = -1; pw = getpwnam(user); if (pw) { stored = pw->pw_passwd; if (!stralloc_copys(&homedir,pw->pw_dir)) exit(111); if (!stralloc_copys(&shell,pw->pw_shell)) exit(111); } else { if (errno == ETXTBSY) exit(111); exit(1); } if (response) { #ifdef HASUSERPW upw = getuserpw(user); if (upw) stored = upw->upw_passwd; else if (errno == ETXTBSY) exit(111); #elif HASGETSPNAM spw = getspnam(user); if (spw) stored = spw->sp_pwdp; else if (errno == ETXTBSY) exit(111); #endif if (!stored || !*stored) exit(111); encrypted = crypt(response,stored); if (!encrypted) exit(111); // no password given (tx. M.B.) r = str_diff(encrypted,stored); } if (r == 0 || !response) { if (prot_gid((int) pw->pw_gid) == -1) exit(1); if (prot_uid((int) pw->pw_uid) == -1) exit(1); if (chdir(pw->pw_dir) == -1) exit(111); } return r; } int auth_apop(unsigned char *password,unsigned char *response,unsigned char *challenge) { MD5_CTX context; unsigned char digest[16]; unsigned char digascii[33]; MD5Init(&context); MD5Update(&context,challenge,str_len(challenge)); MD5Update(&context,password,str_len(password)); MD5Final(digest,&context); dig_ascii(digascii,digest,16); return (str_diff(digascii,response)); } int auth_cram(unsigned char *password,unsigned char *response,unsigned char *challenge) { unsigned char digest[16]; unsigned char digascii[33]; hmac_md5(challenge,str_len(challenge),password,str_len(password),digest); dig_ascii(digascii,digest,16); return (str_diff(digascii,response) && str_diff(password,response)); // cram or plain } int auth_dovecot(char *user,char *response,char *socket,char *service) { int wstat; int child; char *wrapper[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; int i = 0; close(FDGOSSIP); /* gossiping doveadm */ switch (child = fork()) { case -1: exit(111); case 0: wrapper[i] = "doveadm"; wrapper[++i] = "auth"; wrapper[++i] = "test"; if (socket) { wrapper[++i] = "-a"; wrapper[++i] = socket; } if (service) { wrapper[++i] = "-x"; wrapper[++i] = service; } wrapper[++i] = user; wrapper[++i] = response; wrapper[++i] = 0; execvp(wrapper[0],wrapper); exit(111); } if (wait_pid(&wstat,child) == -1) exit(111); if (wait_crashed(wstat)) exit(111); return wait_exitcode(wstat); } int auth_wrapper(char *pam,char *arg1,char *arg2,char *auth,int len) { int wstat; int child; int pi[2]; char *wrapper[4] = {0, 0, 0, 0}; if (pipe(pi) == -1) exit(111); if (pi[0] != FDAUTH) exit(111); switch (child = fork()) { case -1: exit(111); case 0: close(pi[1]); if (fd_copy(FDAUTH,pi[0]) == -1) exit(111); wrapper[0] = pam; wrapper[1] = arg1; wrapper[2] = arg2; wrapper[3] = 0; sig_pipedefault(); execvp(wrapper[0],wrapper); exit(111); } close(pi[0]); buffer_init(&ba,write,pi[1],authbuf,sizeof(authbuf)); if (buffer_put(&ba,auth,len) == -1) exit(111); if (buffer_flush(&ba) == -1) exit(111); close(pi[1]); if (wait_pid(&wstat,child) == -1) exit(111); if (wait_crashed(wstat)) exit(111); return wait_exitcode(wstat); } int main(int argc,char **argv) { char *authuser; char *authpass; char *response = 0; char *challenge = 0; char *domain = 0; char *authsocket = 0; char *service = 0; char *program = 0; char *maildirname = 0; int rc = -1; /* initialise: -1; ok: 0; !ok: > 0 */ int authlen = 0; int buflen = 0; int domlen = 0; int i = 0; int r; if (!argv[1]) exit(2); if (!case_diffs(argv[1],SOCKET_CALL)) { // dovecot socket if (!argv[3]) exit(2); authsocket = argv[2]; if (!case_diffs(argv[3],DOVECOT_SERVICE)) { // ++ dovecot service service = argv[4]; if (!argv[5]) exit(2); } } else if (!case_diffs(argv[1],DOVECOT_SERVICE)) { // dovecot service if (!argv[3]) exit(2); service = argv[2]; if (!case_diffs(argv[3],SOCKET_CALL)) { // ++ dovecot socket if (!argv[5]) exit(2); authsocket = argv[4]; } } else if (argv[2]) { // pop or imap user with mailbox if (case_starts(argv[2],"mail") || case_starts(argv[2],"mbox")) { program = argv[1]; maildirname = argv[2]; } } env_unset("USER"); /* Read input on FDAUTH */ for (;;) { do r = read(FDAUTH,authbuf + buflen,sizeof(authbuf) - buflen); while ((r == -1) && (errno == EINTR)); if (r == -1) exit(111); if (r == 0) break; buflen += r; if (buflen >= sizeof(authbuf)) exit(2); } close(FDAUTH); authuser = authbuf + i; /* username */ if (i == buflen) exit(2); while (authbuf[i++]) /* response */ if (i == buflen) exit(2); response = authbuf + i; if (i == buflen) exit(2); while (authbuf[i++]) /* challenge */ if (i == buflen) exit(2); challenge = authbuf + i; authlen = str_len(authuser); if (!stralloc_copyb(&user,authuser,authlen)) exit(111); if ((i = byte_rchr(authuser,authlen,'@'))) /* @domain */ if (i < authlen && authuser[i] == '@') { domain = authuser + i; domlen = str_len(domain); case_lowerb(domain,domlen); user.len = 0; if (!stralloc_copyb(&user,authuser,i)) exit(111); } if (!stralloc_0(&user)) exit(111); if (!env_put("USER",authuser)) exit(111); /* Read control file users/authuser and go for checks */ if (chdir(auto_qmail) == -1) exit(110); switch (control_readfile(&authfile,"users/authuser",0)) { case -1: exit(110); case 0: if (!constmap_init(&mapauthuser,"",0,1)) exit(111); case 1: if (!constmap_init(&mapauthuser,authfile.s,authfile.len,1)) exit(111); } /* Check for disabled authuser/domains */ if (!stralloc_copys(&disabled,"!")) exit(111); if (!stralloc_catb(&disabled,authuser,authlen)) exit(111); if (constmap(&mapauthuser,disabled.s,disabled.len)) exit(1); if (domlen) { disabled.len = 0; if (!stralloc_copys(&disabled,"!")) exit(111); if (!stralloc_catb(&disabled,domain,domlen)) exit(111); if (constmap(&mapauthuser,disabled.s,disabled.len)) exit(1); } /* Virtual and system user accounts */ authpass = constmap(&mapauthuser,authuser,authlen); if (!authpass && domlen) authpass = constmap(&mapauthuser,domain,domlen); // 1. authuser accounts if (!authpass) authpass = constmap(&mapauthuser,"*",1); // 2. system accounts if (!authpass) authpass = constmap(&mapauthuser,"@",1); // 3. virtual user accounts if (!authpass) exit(1); if (str_len(authpass) == 1) { // external IdP switch (authpass[0]) { case '?': rc = auth_unix(user.s,response); break; case '+': if (maildirname) rc = auth_wrapper("checkvpw",program,maildirname,authbuf,buflen); else rc = auth_wrapper("checkvpw","true","Maildir",authbuf,buflen); // Pseudo arg break; case '&': rc = auth_wrapper("vchkpw",program,maildirname,authbuf,buflen); break; case '=': rc = auth_dovecot(authuser,response,authsocket,service); break; default: rc = 2; break; } } else { // authuser file switch (authpass[0]) { case '%': rc = auth_hash(authpass + 1,response); break; default: if (maildirname) { if ((rc = auth_cram(authpass,response,challenge) == 0)) break; // IMAP C/R if ((rc = auth_apop(authpass,response,challenge)) == 0) { auth_unix(user.s,0); // Unix environment only } } else rc = auth_cram(authpass,response,challenge); break; } } if (rc) exit(rc); for (i = 0; i < sizeof(authbuf); ++i) authbuf[i] = 0; if (authsocket && service) pathexec(argv + 5); else if (authsocket || service) pathexec(argv + 3); else pathexec(argv + 1); exit(111); }