#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);
}