#include "global.h" #include "md5.h" #include "hmac_md5.h" #include <errno.h> #include <pwd.h> #include <stdio.h> #include <unistd.h> #include <libgen.h> #include <sys/param.h> #include <sys/stat.h> #include <string.h> #include <stdlib.h> #define LINE_MAX 256 char up[513]; int uplen; static char hextab[]="0123456789abcdef"; #ifndef USERAPOPFILE # define USERAPOPFILE ".apop" #endif #ifndef APOPOPEN # define APOPOPEN "/usr/local/sbin/deapop" #endif #ifndef DASH # define DASH "-" #endif #define PATHSIZ MAXPATHLEN #ifndef QMAILCONTROL # define QMAILCONTROL "/var/qmail/control" #endif static char pathname[MAXPATHLEN]; static char *qmailcontrol = QMAILCONTROL; void abortwithmsg(char *msg) { fprintf(stderr, "%s\n", msg); _exit(3); } void nomem() { abortwithmsg("Out of memory"); } /* memo: \0 can be dropped by strncpy. \0 never be dropped by snprintf. */ char* conv_virtualdomain(char *mailname) { char *dom = strchr(mailname, '@'), *p; char vd[PATHSIZ+1], previous[PATHSIZ+1]; static char rewrite[PATHSIZ+1]; FILE *vdfd; int match=0; if (!dom) return mailname; snprintf(vd, PATHSIZ, "%s/%s", qmailcontrol, "virtualdomains"); dom++; /* set position of domain part beginning */ if (dom && NULL != (vdfd = fopen (vd, "r"))) { char buf[PATHSIZ+1], *s; int l = strlen(dom); int L = strlen(mailname); while ((s=fgets(buf, PATHSIZ, vdfd))) { if ((p=strchr(s, '#'))) *p = '\0'; /* zap comments */ if (!strchr(buf, ':')) continue; while (s && (strrchr(s, '\n') || strrchr(s, '\r') || strrchr(s, ' '))) s[strlen(s)-1] = '\0'; if (!strncmp(mailname, s, L) && s[L] == ':' && s[L+1]) { /* user matches */ match = 3; snprintf(rewrite, PATHSIZ, "%s%s%s", s+L+1, DASH, mailname); break; } if (!strncmp(dom, s, l) && s[l] == ':' && s[l+1]) { /* domain matches */ match = 2; snprintf(rewrite, PATHSIZ, "%s%s%s", s+l+1, DASH, mailname); continue; } if (match < 2 && s[0] == '.') { /* if domain described in wildcard */ if ((p=strchr(s, ':'))) { *p = '\0'; if (!strcmp(dom+(strlen(dom)-strlen(s)), s)) { if (match == 0 || strlen(previous) < strlen(s)) { match = 1; strncpy(previous, s, PATHSIZ); snprintf(rewrite, PATHSIZ, "%s%s%s", p+1, DASH, mailname); } } } } } fclose(vdfd); if (match) { p = strchr(rewrite, '@'); if (p) { *p = '\0'; } memset(vd, 0, sizeof(vd)); memset(previous, 0, sizeof(previous)); return rewrite; } } return mailname; /* no @ marks, return itself */ } typedef union mixval { int i; char c; char* cp; FILE* fp; } mix; mix* assign(char* job, mix arg) { static mix rval; return &rval; } typedef struct { int n; char **line; } Assign; static Assign ag[2] = {{0, 0}, {0, 0}}; /* ag[0][] is for users/assign '=' type, * ag[1][] is for users/assign '+' type. */ void init_assign() { char assign[MAXPATHLEN]; char *buf, *p; FILE *fp; struct stat st; int i; if (ag[0].line && ag[1].line) return; snprintf(pathname, sizeof pathname, "%s", dirname(qmailcontrol)); snprintf(assign, sizeof assign, "%s/%s", pathname, "users/assign"); if (stat(assign, &st) < 0) return; if (NULL == (buf=(char*)malloc(st.st_size))) nomem(); if (NULL == (fp=fopen(assign, "r"))) return; for (i=0; i<2; i++) { if (NULL == (ag[i].line = (char**)malloc(sizeof(char**)))) nomem(); ag[i].line[0] = NULL; } while (fgets(buf, st.st_size, fp)) { int atype, n; for (p=buf; strchr(" \t", *p); p++) ; if (*p == '#') continue; atype = *p=='+' ? 1 : -(*p!='='); /* '='=0, '+'=1, other=-1 */ if (atype==-1) continue; n = ag[atype].n; if (NULL == (ag[atype].line[n]=(char*)malloc(strlen(p)+1))) nomem(); strncpy(ag[atype].line[n], p, strlen(p)); ag[atype].line = (char**)realloc(ag[atype].line, (++n+1)*sizeof(char**)); if (NULL == ag[atype].line) _exit(3); ag[atype].n = n; ag[atype].line[n] = NULL; } #ifdef DEBUG1 int j; for (i=0; i<2; i++) { for (j=0; j<ag[i].n; j++) printf("%c%4d: %s", "=+"[i], j, ag[i].line[j]); } #endif } /* Get Nth field from LINE regarding DELIM as delimiter. * Return the newly allocated string. Free it afterwards. */ char* getfieldn(char *line, int n, char *delim) { int i, dlen=strlen(delim), flen; char *field, *p, *q = NULL; for (i=0, p=line; i<n-1 && *p; i++) { for (q=p; *q && strncmp(delim, q, dlen) && *q != '\n'; q++); p = q+dlen; } if (i==n-1 && *p) { for (q=p; *q && strncmp(delim, q, dlen) && *q != '\n'; q++); flen = q-p; if (NULL == (field=(char*)malloc(flen+1))) nomem(); memset(field, 0, flen+1); strncpy(field, p, flen); return field; } return NULL; } void setuidgidhome(char *line, struct passwd *pw) { char *delim = ":"; char *p; if ((p=getfieldn(line, 3, delim))) { pw->pw_uid = (uid_t)atoi(p); free(p); } if ((p=getfieldn(line, 4, delim))) { pw->pw_gid = (uid_t)atoi(p); free(p); } if ((p=getfieldn(line, 5, delim))) { pw->pw_dir = p; /* Don't free here */ } } static char* substr(char *s, char *e) { char *news; int len = e-s+1; if (NULL == (news=(char*)malloc(len))) nomem(); strncpy(news, s, len); return news; } typedef struct { struct passwd *p; char *dotapop; } pwapop; static pwapop* lookupassign(char *user) { int type, j, len, maxlen; char *l, *r, *dash, *ext, *pre, *field; static pwapop pa; static struct passwd p; init_assign(); pa.p = &p; type=0; /* check against strict assignment */ for (j=0; j<ag[type].n; j++) { l = ag[type].line[j]+1; for (r = l; *r && *r != ':'; r++); if (!*r) continue; len = r-l; if (!strncasecmp(user, l, len)) { setuidgidhome(l, &p); // fprintf(stderr, "%d %d %s\n", p.pw_uid, p.pw_gid, p.pw_dir); dash = getfieldn(l, 6, ":"); ext = getfieldn(l, 7, ":"); len = strlen(p.pw_dir)+1+5+strlen(dash)+strlen(ext)+1; /* $HOME /.apop $DASH $EXT \0 */ if (NULL == (pa.dotapop=malloc(len))) nomem(); snprintf(pa.dotapop, len, "%s/.apop%s%s", p.pw_dir, dash, ext); // fprintf(stderr, "l=%d[%s]\n", len, pa.dotapop); free(dash); free(ext); return &pa; } } type=1; /* check against wildcard assignment */ maxlen = -1; for (j=0; j<ag[type].n; j++) { l = ag[type].line[j]+1; field = getfieldn(l, 1, ":"); len = strlen(field); if (!strncasecmp(user, l, len) && maxlen < len) { /* longest match? */ maxlen = len; if (p.pw_dir) free(p.pw_dir); /* for 2nd loop or later */ setuidgidhome(l, &p); ext = substr(user+len, user+strlen(user)); dash = getfieldn(l, 6, ":"); pre = getfieldn(l, 7, ":"); len = strlen(p.pw_dir)+1+5+strlen(dash)+strlen(pre)+strlen(ext)+1; if (pa.dotapop) free(pa.dotapop); /* for 2nd loop or later */ if (NULL == (pa.dotapop=malloc(len))) nomem(); snprintf(pa.dotapop, len, "%s/.apop%s%s%s", p.pw_dir, dash, pre, ext); // fprintf(stderr, "l=%d[%s]\n", len, pa.dotapop); free(ext); free(dash); free(pre); } if (field) { free(field); field = NULL; } } if (maxlen > 0 && p.pw_dir && pa.dotapop) return &pa; return NULL; } FILE* getapopfd(char *user) { FILE *fp; struct passwd *pw; struct stat stbuf; char apopfile[PATHSIZ], aprog[PATHSIZ*2]; char *suffix = NULL; pwapop *pa; int dashlen = strlen(DASH); uid_t uid; //fprintf(stderr, "%s\n", user); user=conv_virtualdomain(user); //fprintf(stderr, "%s\n", user); if (!user) _exit(2); /* First, lookup users/assign */ if ((pa=lookupassign(user))) { strncpy(apopfile, pa->dotapop, PATHSIZ); free(pa->dotapop); pa->dotapop = NULL; uid = pa->p->pw_uid; } else { /* Then, try to get real user from passwd entries */ pw = getpwnam(user); if (!pw && strstr(user, DASH)) { /* Try again after removing extension - Check qmail alias name */ char tmp[PATHSIZ]; char *s = user; memset(tmp, 0, PATHSIZ); while (*s && s && !pw) { s = strstr(s, DASH); /* Search from left. */ if (!s) break; strncpy(tmp, user, s-user); /* copy upto '-' */ s += dashlen; pw = getpwnam(tmp); } if (pw) { /* s points to the next of DASH */ if (NULL == (suffix=(char*)malloc(strlen(s)+dashlen+1))) nomem(); memcpy(suffix, s-dashlen, strlen(s)+dashlen+1); /* including \0 */ user[strlen(tmp)] = '\0'; /* zap suffix */ } } if (!pw) _exit(2); /* If no user entry here, abort. */ uid = pw->pw_uid; snprintf(apopfile, sizeof apopfile, "%s/%s", pw->pw_dir, USERAPOPFILE); if (suffix) strncat(apopfile, suffix, (sizeof apopfile)-strlen(apopfile)-1); /* Do not care if path name truncated. Just skip it. */ } // fprintf(stderr, "a=%s\n", apopfile); if (setuid(uid) != 0) _exit(2); if (stat(apopfile, &stbuf) < 0) return (FILE*)-2; /* no password file, exit with error */ if (stbuf.st_mode & ~(S_IFREG | S_IREAD | S_IWRITE)) return (FILE*)-3; /* if readable by others, exit with error */ if (stat(APOPOPEN, &stbuf) >= 0) { snprintf(aprog, sizeof aprog, "%s %s", APOPOPEN, apopfile); fp = popen(aprog, "r"); } else { fp = fopen(apopfile, "r"); } return fp; } int doit(unsigned char *testlogin, unsigned char *challenge, unsigned char *response) { static char line[LINE_MAX + 1]; int found_user= 0; unsigned char *password = NULL; unsigned char digest[16]; unsigned char digascii[33]; unsigned char h; FILE *fp; int j; // char *linepnt; if (0 >= (fp=getapopfd(testlogin))) _exit(2); if (fp) { fgets(line, sizeof line, fp); fclose(fp); if (line[0]) { int p = strlen(line); while (p > 0 && (line[p-1] == '\n' || line[p-1] == '\r')) { line[--p] = '\0'; /* zap trailing newlines */ } password = line; found_user=1; } } if (!found_user) { memset(line, 0, sizeof line); return(1); } hmac_md5(challenge, strlen(challenge), password, strlen(password), digest); memset(line, 0, sizeof line); digascii[32]=0; for (j=0; j<16; j++) { h=digest[j] >> 4; digascii[2*j]=hextab[h]; h=digest[j] & 0x0f; digascii[(2*j)+1]=hextab[h]; } return (strcmp(digascii,response) && strcmp(password,challenge)); } int main(int argc,char **argv) { char *login; char *response; char *challenge; int r; int i; int accepted; if (!argv[1]) _exit(2); if (getenv("QMAILCONTROL")) qmailcontrol = getenv("QMAILCONTROL"); uplen = 0; for (;;) { do r = read(3,up + uplen,sizeof(up) - uplen); while ((r == -1) && (errno == EINTR)); if (r == -1) _exit(111); if (r == 0) break; uplen += r; if (uplen >= sizeof(up)) _exit(1); } close(3); i = 0; login = up + i; while (up[i++]) if (i == uplen) _exit(2); challenge = up + i; if (i == uplen) _exit(2); while (up[i++]) if (i == uplen) _exit(2); response = up + i; accepted=doit(login, challenge, response); for (i = 0;i < sizeof(up);++i) up[i] = 0; if (accepted) _exit(1); execvp(argv[1], argv + 1); _exit(111); }