diff options
Diffstat (limited to 'maildrop')
| -rw-r--r-- | maildrop/Makefile.am | 2 | ||||
| -rw-r--r-- | maildrop/configure.ac | 6 | ||||
| -rw-r--r-- | maildrop/dovecotauth.c | 480 | ||||
| -rw-r--r-- | maildrop/dovecotauth.h | 59 | ||||
| -rw-r--r-- | maildrop/maildrop.sgml | 15 | ||||
| -rw-r--r-- | maildrop/main.C | 136 | 
6 files changed, 695 insertions, 3 deletions
| diff --git a/maildrop/Makefile.am b/maildrop/Makefile.am index 0367891..6486109 100644 --- a/maildrop/Makefile.am +++ b/maildrop/Makefile.am @@ -42,7 +42,7 @@ maildrop_SOURCES=deliver.C deliverdotlock.C deliverdotlock.h \  	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 --git a/maildrop/configure.ac b/maildrop/configure.ac index 4114fa8..822922b 100644 --- a/maildrop/configure.ac +++ b/maildrop/configure.ac @@ -717,6 +717,12 @@ AC_ARG_ENABLE(authlib-tempreject,  	[  --enable-authlib-tempreject=(1|0) authlib errors are temprejects ],  	[ AUTHLIB_TEMPREJECT="$enableval"], [ AUTHLIB_TEMPREJECT=1 ])  AC_MSG_RESULT($AUTHLIB_TEMPREJECT) +AC_ARG_ENABLE(dovecotauth,[],[ +	if test "$enableval" = "yes" +	then +		AC_DEFINE_UNQUOTED(DOVECOTAUTH,1,[ Dovecot authentication enabled ]) +	fi +])  AC_DEFINE_UNQUOTED(AUTHLIB_TEMPREJECT,$AUTHLIB_TEMPREJECT,  	[ Whether authlib errors cause temporary rejects ]) diff --git a/maildrop/dovecotauth.c b/maildrop/dovecotauth.c new file mode 100644 index 0000000..e5ead09 --- /dev/null +++ b/maildrop/dovecotauth.c @@ -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	<stdio.h> +#include	<stdlib.h> +#include	<string.h> +#include	<errno.h> +#include	<fcntl.h> +#include	<pwd.h> +#include	<time.h> +#include	<unistd.h> +#include	<sys/types.h> +#include	<sys/socket.h> +#include	<sys/un.h> +#include	<sys/select.h> + +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 --git a/maildrop/dovecotauth.h b/maildrop/dovecotauth.h new file mode 100644 index 0000000..dcdc13f --- /dev/null +++ b/maildrop/dovecotauth.h @@ -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	<sys/types.h> + +#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 --git a/maildrop/maildrop.sgml b/maildrop/maildrop.sgml index 2e5591f..b476d71 100644 --- a/maildrop/maildrop.sgml +++ b/maildrop/maildrop.sgml @@ -486,7 +486,20 @@ The warning is repeated every 24 hours (at least), until the maildir drops  below  <replaceable>N</replaceable> percent full.</para>          </listitem> -      </varlistentry>       +      </varlistentry> + +      <varlistentry> +	<term>-t <replaceable>socket</replaceable></term> + +	<listitem> +	  <para> +	    This option is available if maildrop is compiled with optional +	    Dovecot authentication support. <replaceable>socket</replaceable> +	    specifies the location of Dovecot master authentication socket, +	    for example <filename>/var/run/dovecot/auth-master</filename>. +	  </para> +	</listitem> +      </varlistentry>      </variablelist>    </refsect1> diff --git a/maildrop/main.C b/maildrop/main.C index 8da29ba..3a50d6e 100644 --- a/maildrop/main.C +++ b/maildrop/main.C @@ -32,6 +32,15 @@  #include	<pwd.h>  #include	<grp.h>  #include	"../dbobj.h" + +/* +** This switch can later be moved to config.h file with appropriate  +** configure option like --with-dovecotauth or something similar +*/ +#if DOVECOTAUTH +#include	"dovecotauth.h" +#endif +  #if AUTHLIB  #include	<courierauth.h>  #endif @@ -176,6 +185,14 @@ static const char msg[]="maildrop " VERSION " Copyright 1998-2005 Double Precisi  	"\n"  #endif  #endif +#if DOVECOTAUTH +	"Dovecot Authentication extension enabled." +#if CRLF_TERM +	"\r\n" +#else +	"\n" +#endif +#endif  #if AUTHLIB  	"Courier Authentication Library extension enabled."  #if CRLF_TERM @@ -345,6 +362,98 @@ int find_in_authlib(Maildrop *maildrop, const char* user)  }  #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; @@ -369,6 +478,9 @@ int	found;  const	char *numuidgid=0;  #endif  #endif +#if DOVECOTAUTH +const	char *dovecotauth_addr=0; +#endif  	umask( 0007 ); @@ -454,6 +566,18 @@ const	char *numuidgid=0;  		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); @@ -475,7 +599,17 @@ const	char *numuidgid=0;  		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 )  			{ | 
