diff -Naur maildrop-2.1.0/maildrop/dovecotauth.c maildrop-2.1.0-dovecotauth/maildrop/dovecotauth.c --- maildrop-2.1.0/maildrop/dovecotauth.c 1970-01-01 01:00:00.000000000 +0100 +++ maildrop-2.1.0-dovecotauth/maildrop/dovecotauth.c 2009-05-12 18:12:00.000000000 +0200 @@ -0,0 +1,480 @@ +/* +** Copyright 2009 Marko Njezic +** Licensed under the same terms as Courier Authlib AND/OR Courier Maildrop. +** +** Partially based on authdaemonlib.c from Courier Authlib, which had the following statement: +** +** Copyright 2000-2006 Double Precision, Inc. See COPYING for +** distribution information. +** +** Code that was taken from authdaemonlib.c is as follows: +** - s_connect() function +** - opensock() function with modification to accept socket address +** - writeauth() function +** - readline() function with related support functions (with modification +** to time-out after TIMEOUT_READ seconds) +*/ + +#include "dovecotauth.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char rcsid[]="$Id$"; + +static int TIMEOUT_SOCK=10, + TIMEOUT_WRITE=10, + TIMEOUT_READ=30; + +static int s_connect(int sockfd, + const struct sockaddr *addr, + size_t addr_s, + time_t connect_timeout) +{ + fd_set fdr; + struct timeval tv; + int rc; + +#ifdef SOL_KEEPALIVE + setsockopt(sockfd, SOL_SOCKET, SOL_KEEPALIVE, + (const char *)&dummy, sizeof(dummy)); +#endif + +#ifdef SOL_LINGER + { + struct linger l; + + l.l_onoff=0; + l.l_linger=0; + + setsockopt(sockfd, SOL_SOCKET, SOL_LINGER, + (const char *)&l, sizeof(l)); + } +#endif + + /* + ** If configuration says to use the kernel's timeout settings, + ** just call connect, and be done with it. + */ + + if (connect_timeout == 0) + return ( connect(sockfd, addr, addr_s)); + + /* Asynchronous connect with timeout. */ + + if (fcntl(sockfd, F_SETFL, O_NONBLOCK) < 0) return (-1); + + if ( connect(sockfd, addr, addr_s) == 0) + { + /* That was easy, we're done. */ + + if (fcntl(sockfd, F_SETFL, 0) < 0) return (-1); + return (0); + } + + if (errno != EINPROGRESS) + return -1; + + /* Wait for the connection to go through, until the timeout expires */ + + FD_ZERO(&fdr); + FD_SET(sockfd, &fdr); + tv.tv_sec=connect_timeout; + tv.tv_usec=0; + + rc=select(sockfd+1, 0, &fdr, 0, &tv); + if (rc < 0) return (-1); + + if (!FD_ISSET(sockfd, &fdr)) + { + errno=ETIMEDOUT; + return (-1); + } + + { + int gserr; + socklen_t gslen = sizeof(gserr); + + if (getsockopt(sockfd, SOL_SOCKET, + SO_ERROR, + (char *)&gserr, &gslen)==0) + { + if (gserr == 0) + return 0; + + errno=gserr; + } + } + return (-1); +} + +static int opensock(const char *addr) +{ + int s=socket(PF_UNIX, SOCK_STREAM, 0); + struct sockaddr_un skun; + + skun.sun_family=AF_UNIX; + strncpy(skun.sun_path, addr, sizeof(skun.sun_path)); + + if (s < 0) + { + perror("CRIT: dovecotauth: socket() failed"); + return (-1); + } + + { + const char *p=getenv("TIMEOUT_SOCK"); + int n=atoi(p ? p:"0"); + + if (n > 0) + TIMEOUT_SOCK=n; + } + + { + const char *p=getenv("TIMEOUT_READ"); + int n=atoi(p ? p:"0"); + + if (n > 0) + TIMEOUT_READ=n; + } + + { + const char *p=getenv("TIMEOUT_WRITE"); + int n=atoi(p ? p:"0"); + + if (n > 0) + TIMEOUT_WRITE=n; + } + + if (s_connect(s, (const struct sockaddr *)&skun, sizeof(skun), + TIMEOUT_SOCK)) + { + perror("ERR: dovecotauth: s_connect() failed"); + if (errno == ETIMEDOUT || errno == ECONNREFUSED) + fprintf(stderr, "ERR: [Hint: perhaps dovecot-auth daemon is not running?]\n"); + close(s); + return (-1); + } + return (s); +} + +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=TIMEOUT_WRITE; + 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); +} + +struct enum_getch { + char buffer[BUFSIZ]; + char *buf_ptr; + size_t buf_left; +}; + +#define getauthc(fd,eg) ((eg)->buf_left-- ? \ + (unsigned char)*((eg)->buf_ptr)++:\ + fillgetauthc((fd),(eg))) + +static int fillgetauthc(int fd, struct enum_getch *eg) +{ + time_t end_time, curtime; + + time(&end_time); + end_time += TIMEOUT_READ; + + for (;;) + { + int n; + fd_set fds; + struct timeval tv; + + time(&curtime); + if (curtime >= end_time) + break; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv.tv_sec=end_time - curtime; + tv.tv_usec=0; + if (select(fd+1, &fds, 0, 0, &tv) <= 0 || !FD_ISSET(fd, &fds)) + break; + + n=read(fd, eg->buffer, sizeof(eg->buffer)); + if (n <= 0) + break; + + eg->buf_ptr=eg->buffer; + eg->buf_left=n; + + --eg->buf_left; + return (unsigned char)*(eg->buf_ptr)++; + } + return EOF; +} + +static int readline(int fd, struct enum_getch *eg, + char *buf, + size_t bufsize) +{ + if (bufsize == 0) + return EOF; + + while (--bufsize) + { + int ch=getauthc(fd, eg); + + if (ch == EOF) + return -1; + if (ch == '\n') + break; + + *buf++=ch; + } + *buf=0; + return 0; +} + +/* +** The actual implementation of Dovecot authentication protocol handling follows. +** Full specification of the protocol can be found at: http://wiki.dovecot.org/Authentication%20Protocol +** We are only interested in the "master" type requests for user information. +*/ + +int parse_userinfo(const char *user, const char *linebuf, + int (*func)(struct dovecotauthinfo *, void *), void *arg) +{ + int return_value=1; + struct dovecotauthinfo a; + char *buf, *p; + uid_t u; + + /* Validate input arguments */ + if (!user || !linebuf) + return (1); + + /* Try to allocate buffer */ + buf = (char *)malloc(strlen(linebuf)+1); + if (!buf) + return (1); + strcpy(buf, linebuf); + + memset(&a, 0, sizeof(a)); + a.homedir=""; + + p = strtok(buf, "\t"); + if (p) + a.address=p; + else + a.address=user; + + /* Parse any additional parameters */ + while ((p = strtok(0, "\t")) != 0) + { + if (strncmp(p, "uid=", 4) == 0) + { + u=atol(p+4); + a.sysuserid = &u; + if (u == 0) + { + fprintf(stderr, "ERR: dovecotauth: Received invalid uid from auth socket\n"); + return_value=1; + goto cleanup_parse_userinfo; + } + } + else if (strncmp(p, "gid=", 4) == 0) + { + a.sysgroupid=atol(p+4); + if (a.sysgroupid == 0) + { + fprintf(stderr, "ERR: dovecotauth: Received invalid gid from auth socket\n"); + return_value=1; + goto cleanup_parse_userinfo; + } + } + else if (strncmp(p, "system_user=", 12) == 0) + { + a.sysusername=p+12; + if (a.sysusername) + { + struct passwd *q=getpwnam(a.sysusername); + + if (q && q->pw_uid == 0) + { + fprintf(stderr, "ERR: dovecotauth: Received invalid system user from auth socket\n"); + return_value=1; + goto cleanup_parse_userinfo; + } + } + } + else if (strncmp(p, "home=", 5) == 0) + { + a.homedir=p+5; + } + else if (strncmp(p, "mail=", 5) == 0) + { + a.maildir=p+5; + } + } + + return_value = (*func)(&a, arg); + +cleanup_parse_userinfo: + free(buf); + return return_value; +} + +#define DOVECOTAUTH_LINEBUFSIZE 8192 + +int _dovecotauth_getuserinfo(int wrfd, int rdfd, const char *user, + int (*func)(struct dovecotauthinfo *, void *), void *arg) +{ + static char cmdpart1[]="VERSION\t1\t0\nUSER\t1\t"; + static char cmdpart2[]="\tservice=maildrop\n"; + int return_value=1, handshake=0; + struct enum_getch eg; + char *cmdbuf, *linebuf; + + /* Validate input arguments */ + if (!user) + return (1); + + /* Try to allocate buffers */ + cmdbuf=(char *)malloc(strlen(cmdpart1)+strlen(cmdpart2)+strlen(user)+20); + if (!cmdbuf) + return (1); + + linebuf=(char *)malloc(DOVECOTAUTH_LINEBUFSIZE); + if (!linebuf) + return (1); + + /* Initial handshake */ + eg.buf_left=0; + while (readline(rdfd, &eg, linebuf, DOVECOTAUTH_LINEBUFSIZE) == 0) + { + if (strncmp(linebuf, "VERSION\t", 8) == 0) + { + if (strncmp(linebuf+8, "1\t", 2) != 0) + { + fprintf(stderr, "ERR: dovecotauth: Authentication protocol version mismatch\n"); + return_value=1; + goto cleanup_dovecotauth_getuserinfo; + } + } + else if (strncmp(linebuf, "SPID\t", 5) == 0) + { + /* End of server side handshake */ + handshake=1; + break; + } + } + + if (!handshake) + { + fprintf(stderr, "ERR: dovecotauth: Did not receive proper server handshake from auth socket\n"); + return_value=1; + goto cleanup_dovecotauth_getuserinfo; + } + + /* + ** Try to be helpful in case that user tries to connect to the wrong auth socket. + ** There's a slight chance that this won't execute in case that the previously + ** returned line ends exactly at the buffer end, but we won't handle that case, + ** since this is just a hint to the user, and not really neccessary. + ** Normally, if user tries to communicate with wrong auth socket, + ** we would simply time-out, while waiting for information. + */ + if (eg.buf_left > 0 && readline(rdfd, &eg, linebuf, DOVECOTAUTH_LINEBUFSIZE) == 0) + { + if (strncmp(linebuf, "CUID\t", 5) == 0) + { + fprintf(stderr, "ERR: dovecotauth: Trying to connect to what appears to be a client auth socket, instead of a master auth socket\n"); + return_value=1; + goto cleanup_dovecotauth_getuserinfo; + } + } + + /* Generate our part of communication */ + strcat(strcat(strcpy(cmdbuf, cmdpart1), user), cmdpart2); + + /* Send our part of communication */ + if (writeauth(wrfd, cmdbuf, strlen(cmdbuf))) + { + return_value=1; + goto cleanup_dovecotauth_getuserinfo; + } + + /* Parse returned information */ + eg.buf_left=0; + if (readline(rdfd, &eg, linebuf, DOVECOTAUTH_LINEBUFSIZE) == 0) + { + if (strncmp(linebuf, "USER\t1\t", 7) == 0) + { + /* User was found in the database and we now parse returned information */ + return_value=parse_userinfo(user, linebuf+7, func, arg); + goto cleanup_dovecotauth_getuserinfo; + } + else if (strcmp(linebuf, "NOTFOUND\t1") == 0) + { + /* User was not found in the database */ + return_value=-1; /* Negative return value means that user is not found! */ + goto cleanup_dovecotauth_getuserinfo; + } + else if (strncmp(linebuf, "FAIL\t1", 6) == 0) + { + /* An internal error has occurred on Dovecot's end */ + return_value=1; + goto cleanup_dovecotauth_getuserinfo; + } + else + { + fprintf(stderr, "ERR: dovecotauth: Received unknown input from auth socket\n"); + } + } + else + fprintf(stderr, "ERR: dovecotauth: Did not receive proper input from auth socket\n"); + +cleanup_dovecotauth_getuserinfo: + free(cmdbuf); + free(linebuf); + return return_value; +} + +int dovecotauth_getuserinfo(const char *addr, const char *user, + int (*func)(struct dovecotauthinfo *, void *), void *arg) +{ + int s=opensock(addr); + int rc; + + if (s < 0) + { + return (1); + } + rc = _dovecotauth_getuserinfo(s, s, user, func, arg); + close(s); + return rc; +} diff -Naur maildrop-2.1.0/maildrop/dovecotauth.h maildrop-2.1.0-dovecotauth/maildrop/dovecotauth.h --- maildrop-2.1.0/maildrop/dovecotauth.h 1970-01-01 01:00:00.000000000 +0100 +++ maildrop-2.1.0-dovecotauth/maildrop/dovecotauth.h 2009-05-12 18:12:00.000000000 +0200 @@ -0,0 +1,59 @@ +#ifndef dovecotauth_h +#define dovecotauth_h + +/* +** Copyright 2009 Marko Njezic +** Licensed under the same terms as Courier Authlib AND/OR Courier Maildrop. +** +** Partially based on courierauth.h from Courier Authlib, which had the following statement: +** +** Copyright 2004 Double Precision, Inc. See COPYING for +** distribution information. +*/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static const char dovecotauth_h_rcsid[]="$Id$"; + +struct dovecotauthinfo { + const char *address; + const char *sysusername; + const uid_t *sysuserid; + gid_t sysgroupid; + const char *homedir; + const char *maildir; + } ; + +/* + This structure is modeled after authinfo structure from Courier Authlib. + + Either sysusername or sysuserid may be NULL, but not both of them. + They, and sysgroupid, specify the authenticated user's system + userid and groupid. homedir points to the authenticated user's + home directory. address and maildir, are obvious. + + After populating this tructure, the lookup function calls the + callback function that's specified in its second argument. The + callback function receives a pointer to the authinfo structure. + + The callback function also receives a context pointer, which is + the third argument to the lookup function. + + The lookup function should return a negative value if the userid + does not exist, a positive value if there was a temporary error + looking up the userid, or whatever is the return code from the + callback function, if the user exists. +*/ + +int dovecotauth_getuserinfo(const char *addr, const char *user, + int (*func)(struct dovecotauthinfo *, void *), void *arg); + +#ifdef __cplusplus +} +#endif + +#endif diff -Naur maildrop-2.1.0/maildrop/main.C maildrop-2.1.0-dovecotauth/maildrop/main.C --- maildrop-2.1.0/maildrop/main.C 2008-05-08 17:38:46.000000000 +0200 +++ maildrop-2.1.0-dovecotauth/maildrop/main.C 2009-05-12 18:10:00.000000000 +0200 @@ -36,6 +36,15 @@ #include #endif +/* +** This switch can later be moved to config.h file with appropriate +** configure option like --with-dovecotauth or something similar +*/ +#define DOVECOTAUTH 1 +#if DOVECOTAUTH +#include "dovecotauth.h" +#endif + static const char rcsid[]="$Id: main.C,v 1.55 2008/05/08 15:38:46 mrsam Exp $"; #if HAS_GETHOSTNAME #else @@ -177,6 +186,14 @@ "\n" #endif #endif +#if DOVECOTAUTH + "Dovecot Authentication extension enabled." +#if CRLF_TERM + "\r\n" +#else + "\n" +#endif +#endif #if MAILDIRQUOTA "Maildir quota extension enabled." #if CRLF_TERM @@ -337,6 +354,98 @@ } #endif +#if DOVECOTAUTH +static int callback_dovecotauth(struct dovecotauthinfo *auth, + void *void_arg) +{ + Maildrop &maildrop=*(Maildrop *)void_arg; + + if (VerboseLevel() > 1) + { + Buffer b; + + b.set(auth->sysgroupid); + b.push(0); + + merr << "maildrop: dovecotauth: groupid=" + << b << "\n"; + } + + setgroupid(auth->sysgroupid); + + uid_t u; + if (auth->sysusername) + { + struct passwd *q=getpwnam(auth->sysusername); + + if (q == NULL) + { + merr << "Cannot find system user " + << auth->sysusername + << "\n"; + + nochangeuidgid(); + } + + u=q->pw_uid; + } + else + u=*auth->sysuserid; + + if (VerboseLevel() > 1) + { + Buffer b; + + b.set(u); + b.push(0); + + merr << "maildrop: dovecotauth: userid=" + << b << "\n"; + } + + setuid(u); + + if ( getuid() != u) + nochangeuidgid(); + + if (VerboseLevel() > 1) + { + merr << "maildrop: dovecotauth: logname=" + << auth->address + << ", home=" + << auth->homedir + << ", mail=" + << (auth->maildir ? auth->maildir:"(default)") + << "\n"; + } + + maildrop.init_home=auth->homedir; + maildrop.init_logname=auth->address; + maildrop.init_shell="/bin/sh"; + maildrop.init_default=auth->maildir ? auth->maildir: + GetDefaultMailbox(auth->address); + + return 0; +} + +int find_in_dovecotauth(const char *addr, Maildrop *maildrop, const char* user) +{ + int rc=dovecotauth_getuserinfo(addr, + user, callback_dovecotauth, maildrop); + + if (rc == 0) + return 1; + + if (rc > 0) + { + errexit=EX_TEMPFAIL; + throw "Temporary authentication failure."; + } + + return 0; +} +#endif + static void tempfail(const char *msg) { errexit = EX_TEMPFAIL; @@ -361,6 +470,9 @@ const char *numuidgid=0; #endif #endif +#if DOVECOTAUTH +const char *dovecotauth_addr=0; +#endif umask( 0007 ); @@ -441,6 +553,18 @@ case 'a': maildrop.authlib_essential=1; break; +#if DOVECOTAUTH + case 't': + if (!*optarg && argn < argc) optarg=argv[argn++]; + if (!*optarg) + { + mout << "You didn't specify the location of Dovecot auth socket.\n"; + return (EX_TEMPFAIL); + } + else + dovecotauth_addr=optarg; + break; +#endif case 'h': help(); return (EX_TEMPFAIL); @@ -462,7 +586,17 @@ if (*deliverymode) { - found = find_in_authlib(&maildrop, deliverymode); + +#if DOVECOTAUTH + if (dovecotauth_addr) + { + found = find_in_dovecotauth(dovecotauth_addr, &maildrop, deliverymode); + } + else +#endif + { + found = find_in_authlib(&maildrop, deliverymode); + } if ( !found ) { diff -Naur maildrop-2.1.0/maildrop/Makefile.am maildrop-2.1.0-dovecotauth/maildrop/Makefile.am --- maildrop-2.1.0/maildrop/Makefile.am 2008-07-20 18:58:32.000000000 +0200 +++ maildrop-2.1.0-dovecotauth/maildrop/Makefile.am 2009-05-12 18:12:00.000000000 +0200 @@ -46,7 +46,7 @@ recipe.C recipe.h recipenode.C recipenode.h recipeparse.C reeval.C \ reeval.h regexpnode.h rematch.C rematch.h rematchmsg.C rematchmsg.h \ rematchstr.C rematchstr.h search.C search.h token.C \ - token.h varlist.C varlist.h + token.h varlist.C varlist.h dovecotauth.c dovecotauth.h maildrop_DEPENDENCIES = libmdcommon.la ../maildir/maildir.libdeps diff -Naur maildrop-2.1.0/maildrop/Makefile.in maildrop-2.1.0-dovecotauth/maildrop/Makefile.in --- maildrop-2.1.0/maildrop/Makefile.in 2009-05-10 15:03:32.000000000 +0200 +++ maildrop-2.1.0-dovecotauth/maildrop/Makefile.in 2009-05-12 18:12:00.000000000 +0200 @@ -85,7 +85,7 @@ re.$(OBJEXT) recipe.$(OBJEXT) recipenode.$(OBJEXT) \ recipeparse.$(OBJEXT) reeval.$(OBJEXT) rematch.$(OBJEXT) \ rematchmsg.$(OBJEXT) rematchstr.$(OBJEXT) search.$(OBJEXT) \ - token.$(OBJEXT) varlist.$(OBJEXT) + token.$(OBJEXT) varlist.$(OBJEXT) dovecotauth.$(OBJEXT) maildrop_OBJECTS = $(am_maildrop_OBJECTS) maildrop_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ @@ -297,7 +297,7 @@ recipe.C recipe.h recipenode.C recipenode.h recipeparse.C reeval.C \ reeval.h regexpnode.h rematch.C rematch.h rematchmsg.C rematchmsg.h \ rematchstr.C rematchstr.h search.C search.h token.C \ - token.h varlist.C varlist.h + token.h varlist.C varlist.h dovecotauth.c dovecotauth.h maildrop_DEPENDENCIES = libmdcommon.la ../maildir/maildir.libdeps maildrop_LDADD = libmdcommon.la `cat ../maildir/maildir.libdeps`\ @@ -455,6 +455,7 @@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tempfile.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/token.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/varlist.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dovecotauth.Po@am__quote@ .C.o: @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< diff -Naur maildrop-2.1.0/README.dovecotauth maildrop-2.1.0-dovecotauth/README.dovecotauth --- maildrop-2.1.0/README.dovecotauth 1970-01-01 01:00:00.000000000 +0100 +++ maildrop-2.1.0-dovecotauth/README.dovecotauth 2009-05-12 18:12:00.000000000 +0200 @@ -0,0 +1,46 @@ +Dovecot Authentication extension for maildrop +============================================= +** Copyright 2009 Marko Njezic +** Licensed under the same terms as Courier Authlib AND/OR Courier Maildrop. + +When you patch maildrop to include Dovecot Authentication extension, you'll be +able to do user database lookups against Dovecot. This extension can happily +coexist with Courier Authlib extension if it is also compiled. + +In order to use it you'll need to specify additional option "-t" that will point +to the location of Dovecot's auth master socket when starting maildrop in +delivery mode. For example: + +maildrop -d USER -t /var/run/dovecot/auth-master + +By specifying "-t" option, maildrop will first try user lookup against Dovecot's +database. If user is not found, maildrop will fallback to local user database +(i.e. passwd), as usual. Lookups against Courier Authlib will not be done when +"-t" option is specified. If you want to perform such lookup when both +extensions are compiled, simply remove "-t" option and maildrop will behave as +usual. + +One significant difference compared to Courier Authlib extension is that Dovecot +Authentication extension will never return uid/gid that's equal to zero. If such +value was returned from database, maildrop will exit with temporary failure. +This was done in order to prevent accidental mistakes. If you really want to +deliver as/to root, you'll have to start maildrop without "-t" option and let +it directly query system user database on its own. + +Make sure that correct permissions are set on Dovecot's auth master socket so +that maildrop can communicate with it. Also, depending on what type of users are +being served from Dovecot's database, if user lookup returns local user +accounts, you may end up with problems when maildrop tries to write mails to the +spool directory if wrong permission are set on it, since maildrop will reset its +permissions (uid/gid) to the values returned from user database. This behavior +is the same as the behavior of lookups against Courier Authlib, since they also +reset maildrop's permissions. When you want maildrop to deliver to the local +users, it's best to let it directly query system user database on its own, +since then it can apply its own "RESET_GID" magic, which will hopefully result +in "correct" permissions that will allow maildrop to write to the spool +directory. + +And last but not least, I hope that you'll find this extension useful. +Especially if you already have an existing user database in Dovecot, but would +like to use maildrop (with its powerful "xfilter" command) to deliver e-mails, +without setting up another authentication user database, like Courier Authlib.