/*
** Copyright 2000-2006 Double Precision, Inc. See COPYING for
** distribution information.
*/
#include "courier_auth_config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include "numlib/numlib.h"
#include "liblock/config.h"
#include "liblock/liblock.h"
#include "auth.h"
#include "authdaemonrc.h"
#include "courierauthdebug.h"
#include "pkglibdir.h"
#include "authstaticlist.h"
#include <ltdl.h>
static const char rcsid[]="$Id: authdaemond.c,v 1.33 2006/06/01 10:47:32 mrsam Exp $";
#ifndef SOMAXCONN
#define SOMAXCONN 5
#endif
#if HAVE_HMACLIB
#include "libhmac/hmac.h"
#include "cramlib.h"
#endif
#include "authstaticlist.h"
static unsigned ndaemons;
struct authstaticinfolist {
struct authstaticinfolist *next;
struct authstaticinfo *info;
lt_dlhandle h;
};
static struct authstaticinfolist *modulelist=NULL;
static int mksocket()
{
int fd=socket(PF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un skun;
if (fd < 0) return (-1);
skun.sun_family=AF_UNIX;
strcpy(skun.sun_path, AUTHDAEMONSOCK);
strcat(skun.sun_path, ".tmp");
unlink(skun.sun_path);
if (bind(fd, (const struct sockaddr *)&skun, sizeof(skun)) ||
listen(fd, SOMAXCONN) ||
chmod(skun.sun_path, 0777) ||
rename(skun.sun_path, AUTHDAEMONSOCK) ||
fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
{
perror(AUTHDAEMONSOCK);
close(fd);
return (-1);
}
return (fd);
}
static int initmodules(const char *p)
{
char buf[100];
char buf2[100];
struct authstaticinfolist **modptr= &modulelist;
struct authstaticinfolist *m;
if (ndaemons <= 0)
{
ndaemons=1;
fprintf(stderr, "ERR: Configuration error - missing 'daemons' setting, using %u\n",
ndaemons);
}
while ((m=modulelist) != NULL)
{
modulelist=m->next;
fprintf(stderr, "INFO: Uninstalling %s\n",
m->info->auth_name);
lt_dlclose(m->h);
free(m);
}
while (p && *p)
{
size_t i;
lt_dlhandle h;
lt_ptr pt;
struct authstaticinfo *a;
if (isspace((int)(unsigned char)*p))
{
++p;
continue;
}
for (i=0; p[i] && !isspace((int)(unsigned char)p[i]); ++i)
;
strcpy(buf, "lib");
strncat(buf, p, i>40 ? 40:i);
fprintf(stderr, "INFO: Installing %s\n", buf);
p += i;
h=lt_dlopenext(buf);
if (h == NULL)
{
fprintf(stderr, "INFO: %s\n", lt_dlerror());
continue;
}
sprintf(buf2, "courier_%s_init", buf+3);
pt=lt_dlsym(h, buf2);
if (pt == NULL)
{
fprintf(stderr,
"ERR: Can't locate init function %s.\n",
buf2);
fprintf(stderr, "ERR: %s\n", lt_dlerror());
continue;
}
a= (*(struct authstaticinfo *(*)(void))pt)();
if ((m=malloc(sizeof(*modulelist))) == NULL)
{
perror("ERR");
lt_dlclose(h);
continue;
}
*modptr=m;
m->next=NULL;
m->info=a;
m->h=h;
modptr= &m->next;
fprintf(stderr, "INFO: Installation complete: %s\n",
a->auth_name);
}
return (0);
}
static int readconfig()
{
char buf[BUFSIZ];
FILE *fp;
char *modlist=0;
unsigned daemons=0;
if ((fp=fopen(AUTHDAEMONRC, "r")) == NULL)
{
perror(AUTHDAEMONRC);
return (-1);
}
while (fgets(buf, sizeof(buf), fp))
{
char *p=strchr(buf, '\n'), *q;
if (!p)
{
int c;
while ((c=getc(fp)) >= 0 && c != '\n')
;
}
else *p=0;
if ((p=strchr(buf, '#')) != 0) *p=0;
for (p=buf; *p; p++)
if (!isspace((int)(unsigned char)*p))
break;
if (*p == 0) continue;
if ((p=strchr(buf, '=')) == 0)
{
fprintf(stderr, "ERR: Bad line in %s: %s\n",
AUTHDAEMONRC, buf);
fclose(fp);
if (modlist)
free(modlist);
return (-1);
}
*p++=0;
while (*p && isspace((int)(unsigned char)*p))
++p;
if (*p == '"')
{
++p;
q=strchr(p, '"');
if (q) *q=0;
}
if (strcmp(buf, "authmodulelist") == 0)
{
if (modlist)
free(modlist);
modlist=strdup(p);
if (!modlist)
{
perror("malloc");
fclose(fp);
return (-1);
}
continue;
}
if (strcmp(buf, "daemons") == 0)
{
daemons=atoi(p);
continue;
}
}
fclose(fp);
fprintf(stderr, "INFO: modules=\"%s\", daemons=%u\n",
modlist ? modlist:"(none)",
daemons);
ndaemons=daemons;
return (initmodules(modlist));
}
static char buf[BUFSIZ];
static char *readptr;
static int readleft;
static char *writeptr;
static int writeleft;
static int getauthc(int fd)
{
fd_set fds;
struct timeval tv;
if (readleft--)
return ( (int)(unsigned char)*readptr++ );
readleft=0;
FD_ZERO(&fds);
FD_SET(fd, &fds);
tv.tv_sec=10;
tv.tv_usec=0;
if (select(fd+1, &fds, 0, 0, &tv) <= 0 ||
!FD_ISSET(fd, &fds))
return (EOF);
readleft=read(fd, buf, sizeof(buf));
readptr=buf;
if (readleft <= 0)
{
readleft=0;
return (EOF);
}
--readleft;
return ( (int)(unsigned char)*readptr++ );
}
static int writeauth(int fd, const char *p, unsigned pl)
{
fd_set fds;
struct timeval tv;
while (pl)
{
int n;
FD_ZERO(&fds);
FD_SET(fd, &fds);
tv.tv_sec=30;
tv.tv_usec=0;
if (select(fd+1, 0, &fds, 0, &tv) <= 0 ||
!FD_ISSET(fd, &fds))
return (-1);
n=write(fd, p, pl);
if (n <= 0) return (-1);
p += n;
pl -= n;
}
return (0);
}
static int writeauthflush(int fd)
{
if (writeptr > buf)
{
if (writeauth(fd, buf, writeptr - buf))
return (-1);
}
writeptr=buf;
writeleft=sizeof(buf);
return (0);
}
static int writeauthbuf(int fd, const char *p, unsigned pl)
{
unsigned n;
while (pl)
{
if (pl < writeleft)
{
memcpy(writeptr, p, pl);
writeptr += pl;
writeleft -= pl;
return (0);
}
if (writeauthflush(fd)) return (-1);
n=pl;
if (n > writeleft) n=writeleft;
memcpy(writeptr, p, n);
p += n;
writeptr += n;
writeleft -= n;
pl -= n;
}
return (0);
}
static int writeenvval(int fd, const char *env, const char *val)
{
if (writeauthbuf(fd, env, strlen(env)) ||
writeauthbuf(fd, "=", 1) ||
writeauthbuf(fd, val, strlen(val)) ||
writeauthbuf(fd, "\n", 1))
return (-1);
return (0);
}
static const char *findopt(const char *options, const char *keyword)
{
size_t keyword_l=strlen(keyword);
while (options)
{
if (strncmp(options, keyword, keyword_l) == 0)
{
switch (options[keyword_l])
{
case '=':
return options + keyword_l + 1;
case ',': case '\0':
return options + keyword_l;
}
}
options=strchr(options, ',');
if (options)
++options;
}
return NULL;
}
/* Returns a malloc'd string containing the merge of the options string
and any default options which apply, or NULL if no options at all */
static char *mergeoptions(const char *options)
{
char *defoptions = getenv("DEFAULTOPTIONS");
char *p;
if (options && *options && defoptions && *defoptions)
{
char *result = malloc(strlen(options) +
strlen(defoptions) + 2);
if (!result)
{
perror("malloc");
return NULL;
}
strcpy(result, options);
defoptions = strdup(defoptions);
if (!defoptions)
{
perror("malloc");
free(result);
return NULL;
}
for (p = strtok(defoptions, ","); p; p = strtok(0, ","))
{
char *q = strchr(p, '=');
if (q) *q = '\0';
if (findopt(result, p)) continue;
if (q) *q = '=';
strcat(result, ",");
strcat(result, p);
}
free(defoptions);
return result;
}
else if (options && *options)
{
return strdup(options);
}
else if (defoptions && *defoptions)
{
return strdup(defoptions);
}
else
return 0;
}
static int printauth(struct authinfo *authinfo, void *vp)
{
int fd= *(int *)vp;
char buf2[NUMBUFSIZE];
char *fullopt;
writeptr=buf;
writeleft=sizeof(buf);
courier_authdebug_authinfo("Authenticated: ", authinfo,
authinfo->clearpasswd,
authinfo->passwd);
if (authinfo->sysusername)
if (writeenvval(fd, "USERNAME", authinfo->sysusername))
return (1);
if (authinfo->sysuserid)
if (writeenvval(fd, "UID", libmail_str_uid_t(*authinfo->sysuserid,
buf2)))
return (1);
if (writeenvval(fd, "GID", libmail_str_uid_t(authinfo->sysgroupid, buf2)))
return (1);
if (writeenvval(fd, "HOME", authinfo->homedir))
return (1);
if (authinfo->address)
if (writeenvval(fd, "ADDRESS", authinfo->address))
return (1);
if (authinfo->fullname)
{
/*
* Only the first field of the comma-seperated GECOS field is the
* full username.
*/
char *fullname;
char *p;
int retval;
fullname=strdup(authinfo->fullname);
if(fullname == NULL)
{
perror("strdup");
return (1);
}
p = fullname;
while (*p != ',' && *p != '\0')
p++;
*p=0;
retval = writeenvval(fd, "NAME", fullname);
free(fullname);
if(retval)
return (1);
}
if (authinfo->maildir)
if (writeenvval(fd, "MAILDIR", authinfo->maildir))
return (1);
if (authinfo->quota)
if (writeenvval(fd, "QUOTA", authinfo->quota))
return (1);
if (authinfo->passwd)
if (writeenvval(fd, "PASSWD", authinfo->passwd))
return (1);
if (authinfo->clearpasswd)
if (writeenvval(fd, "PASSWD2", authinfo->clearpasswd))
return (1);
fullopt = mergeoptions(authinfo->options);
if (fullopt)
{
int rc = writeenvval(fd, "OPTIONS", fullopt);
free(fullopt);
if (rc)
return (1);
}
if (writeauthbuf(fd, ".\n", 2) || writeauthflush(fd))
return (1);
return (0);
}
static void pre(int fd, char *prebuf)
{
char *p=strchr(prebuf, ' ');
char *service;
struct authstaticinfolist *l;
if (!p) return;
*p++=0;
while (*p == ' ') ++p;
service=p;
p=strchr(p, ' ');
if (!p) return;
*p++=0;
while (*p == ' ') ++p;
DPRINTF("received userid lookup request: %s", p);
for (l=modulelist; l; l=l->next)
{
struct authstaticinfo *auth=l->info;
const char *modname = auth->auth_name;
int rc;
if (strcmp(prebuf, ".") && strcmp(prebuf, modname))
continue;
DPRINTF("%s: trying this module", modname);
rc=(*auth->auth_prefunc)(p, service,
&printauth, &fd);
if (rc == 0)
return;
if (rc > 0)
{
DPRINTF("%s: TEMPFAIL - no more modules will be tried",
modname);
return; /* Temporary error */
}
DPRINTF("%s: REJECT - try next module", modname);
}
writeauth(fd, "FAIL\n", 5);
DPRINTF("FAIL, all modules rejected");
}
struct enumerate_info {
int fd;
char *buf_ptr;
size_t buf_left;
char buffer[BUFSIZ];
int enumerate_ok;
};
static void enumflush(struct enumerate_info *ei)
{
if (ei->buf_ptr > ei->buffer)
writeauth(ei->fd, ei->buffer, ei->buf_ptr - ei->buffer);
ei->buf_ptr=ei->buffer;
ei->buf_left=sizeof(ei->buffer);
}
static void enumwrite(struct enumerate_info *ei, const char *s)
{
while (s && *s)
{
size_t l;
if (ei->buf_left == 0)
enumflush(ei);
l=strlen(s);
if (l > ei->buf_left)
l=ei->buf_left;
memcpy(ei->buf_ptr, s, l);
ei->buf_ptr += l;
ei->buf_left -= l;
s += l;
}
}
static void enum_cb(const char *name,
uid_t uid,
gid_t gid,
const char *homedir,
const char *maildir,
const char *options,
void *void_arg)
{
struct enumerate_info *ei=(struct enumerate_info *)void_arg;
char buf[NUMBUFSIZE];
char *fullopt;
if (name == NULL)
{
ei->enumerate_ok=1;
return;
}
enumwrite(ei, name);
enumwrite(ei, "\t");
enumwrite(ei, libmail_str_uid_t(uid, buf));
enumwrite(ei, "\t");
enumwrite(ei, libmail_str_gid_t(gid, buf));
enumwrite(ei, "\t");
enumwrite(ei, homedir);
enumwrite(ei, "\t");
enumwrite(ei, maildir ? maildir : "");
enumwrite(ei, "\t");
fullopt = mergeoptions(options);
if (fullopt)
{
enumwrite(ei, fullopt);
free (fullopt);
}
enumwrite(ei, "\n");
}
static void enumerate(int fd)
{
struct enumerate_info ei;
struct authstaticinfolist *l;
ei.fd=fd;
ei.buf_left=0;
for (l=modulelist; l; l=l->next)
{
struct authstaticinfo *auth=l->info;
if (auth->auth_enumerate == NULL)
continue;
enumwrite(&ei, "# ");
enumwrite(&ei, auth->auth_name);
enumwrite(&ei, "\n\n");
ei.enumerate_ok=0;
(*auth->auth_enumerate)(enum_cb, &ei);
if (!ei.enumerate_ok)
{
enumflush(&ei);
DPRINTF("enumeration terminated prematurely in module %s",
auth->auth_name);
return;
}
}
enumwrite(&ei, ".\n");
enumflush(&ei);
}
static void dopasswd(int, const char *, const char *, const char *,
const char *);
static void passwd(int fd, char *prebuf)
{
char *p;
const char *service;
const char *userid;
const char *opwd;
const char *npwd;
for (p=prebuf; *p; p++)
{
if (*p == '\t')
continue;
if ((int)(unsigned char)*p < ' ')
{
writeauth(fd, "FAIL\n", 5);
return;
}
}
service=prebuf;
if ((p=strchr(service, '\t')) != 0)
{
*p++=0;
userid=p;
if ((p=strchr(p, '\t')) != 0)
{
*p++=0;
opwd=p;
if ((p=strchr(p, '\t')) != 0)
{
*p++=0;
npwd=p;
if ((p=strchr(p, '\t')) != 0)
*p=0;
dopasswd(fd, service, userid, opwd, npwd);
return;
}
}
}
}
static void dopasswd(int fd,
const char *service,
const char *userid,
const char *opwd,
const char *npwd)
{
struct authstaticinfolist *l;
for (l=modulelist; l; l=l->next)
{
struct authstaticinfo *auth=l->info;
int rc;
int (*f)(const char *, const char *, const char *,
const char *)=
auth->auth_changepwd;
if (!f)
continue;
rc= (*f)(service, userid, opwd, npwd);
if (rc == 0)
{
writeauth(fd, "OK\n", 3);
return;
}
if (rc > 0)
break;
}
writeauth(fd, "FAIL\n", 5);
}
static void auth(int fd, char *p)
{
char *service;
char *authtype;
char *pp;
struct authstaticinfolist *l;
service=p;
if ((p=strchr(p, '\n')) == 0) return;
*p++=0;
authtype=p;
if ((p=strchr(p, '\n')) == 0) return;
*p++=0;
pp=malloc(strlen(p)+1);
if (!pp)
{
perror("CRIT: malloc() failed");
return;
}
DPRINTF("received auth request, service=%s, authtype=%s", service, authtype);
for (l=modulelist; l; l=l->next)
{
struct authstaticinfo *auth=l->info;
const char *modname = auth->auth_name;
int rc;
DPRINTF("%s: trying this module", modname);
rc=(*auth->auth_func)(service, authtype,
strcpy(pp, p),
&printauth, &fd);
if (rc == 0)
{
free(pp);
return;
}
if (rc > 0)
{
DPRINTF("%s: TEMPFAIL - no more modules will be tried", modname);
free(pp);
return; /* Temporary error */
}
DPRINTF("%s: REJECT - try next module", modname);
}
DPRINTF("FAIL, all modules rejected");
writeauth(fd, "FAIL\n", 5);
free(pp);
}
static void idlefunc()
{
struct authstaticinfolist *l;
for (l=modulelist; l; l=l->next)
{
struct authstaticinfo *auth=l->info;
if (auth->auth_idle)
(*auth->auth_idle)();
}
}
static void doauth(int fd)
{
char buf[BUFSIZ];
int i, ch;
char *p;
readleft=0;
for (i=0; (ch=getauthc(fd)) != '\n'; i++)
{
if (ch < 0 || i >= sizeof(buf)-2)
return;
buf[i]=ch;
}
buf[i]=0;
for (p=buf; *p; p++)
{
if (*p == ' ')
{
*p++=0;
while (*p == ' ') ++p;
break;
}
}
if (strcmp(buf, "PRE") == 0)
{
pre(fd, p);
return;
}
if (strcmp(buf, "PASSWD") == 0)
{
passwd(fd, p);
return;
}
if (strcmp(buf, "AUTH") == 0)
{
int j;
i=atoi(p);
if (i < 0 || i >= sizeof(buf)) return;
for (j=0; j<i; j++)
{
ch=getauthc(fd);
if (ch < 0) return;
buf[j]=ch;
}
buf[j]=0;
auth(fd, buf);
}
if (strcmp(buf, "ENUMERATE") == 0)
{
enumerate(fd);
}
}
static int sighup_pipe= -1;
static RETSIGTYPE sighup(int n)
{
if (sighup_pipe >= 0)
{
close(sighup_pipe);
sighup_pipe= -1;
}
signal(SIGHUP, sighup);
#if RETSIGTYPE != void
return (1);
#endif
}
static int sigterm_received=0;
static RETSIGTYPE sigterm(int n)
{
sigterm_received=1;
if (sighup_pipe >= 0)
{
close(sighup_pipe);
sighup_pipe= -1;
}
else
{
kill(0, SIGTERM);
_exit(0);
}
#if RETSIGTYPE != void
return (0);
#endif
}
static int startchildren(int *pipefd)
{
unsigned i;
pid_t p;
signal(SIGCHLD, sighup);
for (i=0; i<ndaemons; i++)
{
p=fork();
while (p == -1)
{
perror("CRIT: fork() failed");
sleep(5);
p=fork();
}
if (p == 0)
{
sighup_pipe= -1;
close(pipefd[1]);
signal(SIGHUP, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGCHLD, SIG_DFL);
return (1);
}
}
return (0);
}
static int killchildren(int *pipefd)
{
int waitstat;
while (wait(&waitstat) >= 0 || errno != ECHILD)
;
if (pipe(pipefd))
{
perror("CRIT: pipe() failed");
return (-1);
}
return (0);
}
int start()
{
int s;
int fd;
int pipefd[2];
int do_child;
for (fd=3; fd<256; fd++)
close(fd);
if (pipe(pipefd))
{
perror("pipe");
return (1);
}
if (lt_dlinit())
{
fprintf(stderr, "ERR: lt_dlinit() failed: %s\n",
lt_dlerror());
exit(1);
}
if (lt_dlsetsearchpath(PKGLIBDIR))
{
fprintf(stderr, "ERR: lt_dlsetsearchpath() failed: %s\n",
lt_dlerror());
exit(1);
}
if (readconfig())
{
close(pipefd[0]);
close(pipefd[1]);
return (1);
}
s=mksocket();
if (s < 0)
{
perror(AUTHDAEMONSOCK);
close(pipefd[0]);
close(pipefd[1]);
return (1);
}
signal(SIGPIPE, SIG_IGN);
signal(SIGHUP, sighup);
signal(SIGTERM, sigterm);
close(0);
if (open("/dev/null", O_RDWR) != 0)
{
perror("open");
exit(1);
}
dup2(0, 1);
sighup_pipe= pipefd[1];
do_child=startchildren(pipefd);
for (;;)
{
struct sockaddr saddr;
socklen_t saddr_len;
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(pipefd[0], &fds);
if (do_child)
FD_SET(s, &fds);
tv.tv_sec=300;
tv.tv_usec=0;
if (select( (s > pipefd[0] ? s:pipefd[0])+1,
&fds, 0, 0, &tv) < 0)
continue;
if (FD_ISSET(pipefd[0], &fds))
{
close(pipefd[0]);
if (do_child)
return (0); /* Parent died */
fprintf(stderr, "INFO: stopping authdaemond children\n");
while (killchildren(pipefd))
sleep(5);
if (sigterm_received)
return (0);
fprintf(stderr, "INFO: restarting authdaemond children\n");
readconfig();
sighup_pipe=pipefd[1];
do_child=startchildren(pipefd);
continue;
}
if (!FD_ISSET(s, &fds))
{
idlefunc();
continue;
}
saddr_len=sizeof(saddr);
if ((fd=accept(s, &saddr, &saddr_len)) < 0)
continue;
if (fcntl(fd, F_SETFL, 0) < 0)
{
perror("CRIT: fcntl() failed");
}
else
doauth(fd);
close(fd);
}
}
int main(int argc, char **argv)
{
courier_authdebug_login_init();
if (argc > 1)
{
fprintf(stderr, "Error: authdaemond no longer handles its own daemonizing.\n"
"Use new startup script.\n");
exit(1);
}
start();
return (0);
}
syntax highlighted by Code2HTML, v. 0.9.1