/* ** Copyright 2000-2006 Double Precision, Inc. See COPYING for ** distribution information. */ #include "courier_auth_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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= 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= 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); }