diff options
Diffstat (limited to 'src/qmail-authuser.c')
-rwxr-xr-x | src/qmail-authuser.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/src/qmail-authuser.c b/src/qmail-authuser.c new file mode 100755 index 0000000..ff0891b --- /dev/null +++ b/src/qmail-authuser.c @@ -0,0 +1,446 @@ +#include <stdio.h> +#include <unistd.h> +#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 <pwd.h> +static struct passwd *pw; + +#include "hasspnam.h" +#ifdef HASGETSPNAM +#include <shadow.h> +static struct spwd *spw; +#endif + +#include "hasuserpw.h" +#ifdef HASUSERPW +#include <userpw.h> +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); +} |