diff options
Diffstat (limited to 'maildrop/mailbot.c')
| -rw-r--r-- | maildrop/mailbot.c | 965 | 
1 files changed, 965 insertions, 0 deletions
| diff --git a/maildrop/mailbot.c b/maildrop/mailbot.c new file mode 100644 index 0000000..dc8d43f --- /dev/null +++ b/maildrop/mailbot.c @@ -0,0 +1,965 @@ +/* +** Copyright 2001-2010 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#include "config.h" +#include "dbobj.h" +#include "liblock/config.h" +#include "liblock/liblock.h" +#include "unicode/unicode.h" +#include "numlib/numlib.h" +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <time.h> +#if HAVE_LOCALE_H +#include <locale.h> +#endif +#include <langinfo.h> +#if HAVE_STRINGS_H +#include <strings.h> +#endif +#include <ctype.h> +#include "rfc822/rfc822.h" +#include "rfc2045/rfc2045.h" +#include "rfc2045/rfc2045charset.h" +#include <sys/types.h> +#include "mywait.h" +#include <signal.h> +#if HAVE_SYSEXITS_H +#include <sysexits.h> +#endif + +#ifndef EX_TEMPFAIL +#define EX_TEMPFAIL	75 +#endif + +static const char *recips=0; +static const char *dbfile=0; +static const char *charset; +static unsigned interval=1; +static char *sender; + +struct header { +	struct header *next; +	char *buf; +} ; + +static struct header *header_list; + +static struct header *extra_headers=0; + + +void rfc2045_error(const char *str) +{ +	fprintf(stderr, "%s\n", str); +	exit(1); +} + +static void usage() +{ +	fprintf(stderr, +		"Usage: mailbot [ options ] [ $MAILER arg arg... ]\n" +		"\n" +		"    -t filename        - text autoresponse\n" +		"    -c charset         - text MIME character set (default %s)\n" +		"    -m filename        - text autoresponse with a MIME header\n" +		"    -r addr1,addr2...  - any 'addr' required in a To/Cc header\n", +		charset); + +	fprintf(stderr, +		"    -e                 - Prefer replies to Errors-To: or Return-Path: instead\n" +		"                         of From:\n" +		"    -T type            - \"type\": reply, replyall, replydsn, replyfeedback,\n" +		"                         forward, forwardatt\n" +		"    -N                 - Omit contents of the original message from replies\n" +		"    -F \"separator\"     - Set the forwarding separator\n" +		"    -S \"salutation\"    - Set salutation for replies\n" +		"    -d $pathname       - database to prevent duplicate autoresponses\n" +		"    -D x               - at least 'x' days before dupes (default: 1)\n"); + +	fprintf(stderr, +		"    -s subject         - Subject: on autoresponses\n" +		"    -A \"Header: stuff\" - Additional header on the autoresponse\n" +		"    -M recipient       - format \"replydsn\" as a DSN from 'recipient' (required)\n" +		"    -fuser@domain      - Set responding address for replydsn\n" +		"    -f                 - Set responding address from $SENDER\n" +		"    -R type            - Feedback type, for \"-T feedback\" or \"-T replyfeedback\":\n" +		"                         \"abuse\", \"fraud\", \"other\", or \"virus\"\n" +		"    -n                 - only show the resulting message, do not send it\n" +		"    -a                 - Attach entire message for replydsn, feedback, and\n" +		"                         replyfeedback, instead of only the headers.\n" +); + +	fprintf(stderr, +		"    --feedback-original-envelope-id {\"<envelopeid>\"}\n" +		"    --feedback-original-mail-from {\"<mailfrom>\"}\n" +		"    --feedback-reporting-mta {\"dns; hostname\"}\n" +		"    --feedback-source-ip {aaa.bbb.ccc.ddd}\n" +		"    --feedback-incidents {n}\n" +		"    --feedback-authentication-results {\"results\"}\n" +		"    --feedback-original-rcpt-to {\"<rcptto>\"]\n" +		"    --feedback-reported-domain {example.com}\n" +		"                       - optional parameters for -T \"feedback\" and \n" +		"                         -T \"replyfeedback\"\n" +		"    $MAILER arg arg... - run $MAILER (sendmail) to mail the autoresponse\n" +		); + +	exit(EX_TEMPFAIL); +} + +static void read_headers(FILE *tmpfp) +{ +	char buf[BUFSIZ]; +	struct header **lasthdr= &header_list, *prevhdr=0; + +	while (fgets(buf, sizeof(buf), tmpfp)) +	{ +		size_t l=strlen(buf); + +		if (l > 0 && buf[l-1] == '\n') +			--l; +		if (l > 0 && buf[l-1] == '\r') +			--l; +		buf[l]=0; + +		if (l == 0) +		{ +			/* Eat rest of message from stdin */ + +			while (getc(stdin) != EOF) +				; +			break; +		} + +		if (isspace((int)(unsigned char)buf[0]) && prevhdr) +		{ +			if ( (prevhdr->buf= +			     realloc( prevhdr->buf, +				      strlen (prevhdr->buf)+2+strlen(buf))) +			     == NULL) +			{ +				perror("malloc"); +				exit(EX_TEMPFAIL); +			} +			strcat(strcat( prevhdr->buf, "\n"), buf); +		} +		else +		{ +			if ((*lasthdr=(struct header *) +			     malloc(sizeof(struct header))) == NULL || +			    ((*lasthdr)->buf=strdup(buf)) == NULL) +			{ +				perror("malloc"); +				exit(EX_TEMPFAIL); +			} + +			prevhdr= *lasthdr; +			lasthdr= &(*lasthdr)->next; +		} +	} + +	*lasthdr=NULL; +} + +const char *hdr(const char *hdrname) +{ +	struct header *h; +	size_t l=strlen(hdrname); + +	for (h=header_list; h; h=h->next) +	{ +		if (strncasecmp(h->buf, hdrname, l) == 0 && +		    h->buf[l] == ':') +		{ +			const char *p=h->buf+l+1; + +			while (*p && isspace((int)(unsigned char)*p)) +				++p; +			return (p); +		} +	} + +	return (""); +} + +/* +** Get the sender's address +*/ + +static void check_sender() +{ +	const char *h=hdr("reply-to"); +	struct rfc822t *t; +	struct rfc822a *a; + +	if (!h || !*h) +		h=hdr("from"); + +	if (!h || !*h) +		exit(0); + +	t=rfc822t_alloc_new(h, NULL, NULL); + +	if (!t || !(a=rfc822a_alloc(t))) +	{ +		perror("malloc"); +		exit(EX_TEMPFAIL); +	} + +	if (a->naddrs <= 0) +		exit (0); +	sender=rfc822_getaddr(a, 0); +	rfc822a_free(a); +	rfc822t_free(t); + +	if (!sender || !*sender) +		exit(0); +} + +/* +** Do not autorespond to DSNs +*/ + +static void check_dsn() +{ +	static const char ct[]="multipart/report;"; + +	const char *p=hdr("content-type"); + +	if (strncasecmp(p, ct, sizeof(ct)-1) == 0) +		exit(0); + +	p=hdr("precedence"); + +	if (strncasecmp(p, "junk", 4) == 0 || +	    strncasecmp(p, "bulk", 4) == 0 || +	    strncasecmp(p, "list", 4) == 0) +		exit(0);	/* Just in case */ + +	p=hdr("auto-submitted"); + +	if (*p && strcmp(p, "no")) +		exit(0); + +	p=hdr("list-id"); + +	if (*p) +		exit(0); +} + +/* +** Check for a required recipient +*/ + +static void check_recips() +{ +	char *buf; +	struct rfc822t *t; +	struct rfc822a *a; +	struct header *h; + +	if (!recips || !*recips) +		return; + +	buf=strdup(recips); +	if (!buf) +	{ +		perror("strdup"); +		exit(EX_TEMPFAIL); +	} + +	for (h=header_list; h; h=h->next) +	{ +		int i; + +		if (strncasecmp(h->buf, "to:", 3) && +		    strncasecmp(h->buf, "cc:", 3)) +			continue; + +		t=rfc822t_alloc_new(h->buf+3, NULL, NULL); +		if (!t || !(a=rfc822a_alloc(t))) +		{ +			perror("malloc"); +			exit(EX_TEMPFAIL); +		} + +		for (i=0; i<a->naddrs; i++) +		{ +			char *p=rfc822_getaddr(a, i); +			char *q; + +			strcpy(buf, recips); + +			for (q=buf; (q=strtok(q, ", ")) != 0; q=0) +			{ +				if (p && strcasecmp(p, q) == 0) +				{ +					free(p); +					free(buf); +					rfc822a_free(a); +					rfc822t_free(t); +					return; +				} +			} + +			free(p); +		} +		rfc822a_free(a); +		rfc822t_free(t); +	} +	free(buf); +	exit(0); +} + +/* +** Check the dupe database. +*/ + +#ifdef DbObj +static void check_db() +{ +	char *dbname; +	char *lockname; +	int lockfd; +	struct dbobj db; +	time_t now; +	char *sender_key, *p; + +	size_t val_len; +	char *val; + +	if (!dbfile || !*dbfile) +		return; + +	sender_key=strdup(sender); +	dbname=malloc(strlen(dbfile)+ sizeof( "." DBNAME)); +	lockname=malloc(strlen(dbfile)+ sizeof(".lock")); + +	for (p=sender_key; *p; p++) +		*p=tolower((int)(unsigned char)*p); + +	if (!dbname || !lockname || !sender) +	{ +		perror("malloc"); +		exit(EX_TEMPFAIL); +	} + +	strcat(strcpy(dbname, dbfile), "." DBNAME); +	strcat(strcpy(lockname, dbfile), ".lock"); + +	lockfd=open(lockname, O_RDWR|O_CREAT, 0666); + +	if (lockfd < 0 || ll_lock_ex(lockfd)) +	{ +		perror(lockname); +		exit(EX_TEMPFAIL); +	} + +	dbobj_init(&db); + +	if (dbobj_open(&db, dbname, "C") < 0) +	{ +		perror(dbname); +		exit(EX_TEMPFAIL); +	} + +	time(&now); + +	val=dbobj_fetch(&db, sender_key, strlen(sender_key), &val_len, ""); + +	if (val) +	{ +		time_t t; + +		if (val_len >= sizeof(t)) +		{ +			memcpy(&t, val, sizeof(t)); + +			if (t >= now - interval * 60 * 60 * 24) +			{ +				free(val); +				dbobj_close(&db); +				close(lockfd); +				exit(0); +			} +		} +		free(val); +	} + +	dbobj_store(&db, sender_key, strlen(sender_key), +		    (void *)&now, sizeof(now), "R"); +	dbobj_close(&db); +	close(lockfd); +} +#endif + +static void opensendmail(int argn, int argc, char **argv) +{ +	char **newargv; +	int i; + +	if (argn >= argc) +	{ +		static char *sendmail_argv[]={"sendmail", "-f", ""}; + +		argn=0; +		argc=3; +		argv=sendmail_argv; +	} + +	newargv=(char **)malloc( sizeof(char *)*(argc-argn+1)); +	if (!newargv) +	{ +		perror("malloc"); +		exit(EX_TEMPFAIL); +	} + +	for (i=0; argn+i < argc; i++) +		newargv[i]=argv[argn+i]; +	newargv[i]=0; +	signal(SIGCHLD, SIG_DFL); + +	execvp(newargv[0], newargv); +	perror(newargv[0]); +	exit(EX_TEMPFAIL); +} + +static struct rfc2045 *savemessage(FILE *tmpfp) +{ +	struct rfc2045 *rfcp=rfc2045_alloc(); +	char buf[BUFSIZ]; +	int n; + +	if (!rfcp) +	{ +		perror("rfc2045_alloc"); +		exit(1); +	} + +	while ((n=fread(buf, 1, sizeof(buf), stdin)) > 0) +	{ +		if (fwrite(buf, n, 1, tmpfp) != 1) +		{ +			perror("fwrite(tempfile)"); +			exit(1); +		} + +		rfc2045_parse(rfcp, buf, n); +	} + +	if (n < 0) +	{ +		perror("tempfile"); +		exit(1); +	} +	return rfcp; +} + + +struct mimeautoreply_s { +	struct rfc2045_mkreplyinfo info; +	FILE *outf; + +	FILE *contentf; +}; + +static void mimeautoreply_write_func(const char *str, size_t cnt, void *ptr) +{ +	if (cnt && +	    fwrite(str, cnt, 1, ((struct mimeautoreply_s *)ptr)->outf) != 1) +	{ +		perror("tmpfile"); +		exit(1); +	} +} + +static void mimeautoreply_writesig_func(void *ptr) +{ +} + +static int mimeautoreply_myaddr_func(const char *addr, void *ptr) +{ +	return 0; +} + +static void copy_headers(void *ptr) +{ +	struct mimeautoreply_s *p=(struct mimeautoreply_s *)ptr; +	char buf[BUFSIZ]; + +	static const char ct[]="Content-Transfer-Encoding:"; + +	while (fgets(buf, sizeof(buf), p->contentf) != NULL) +	{ +		if (buf[0] == '\n') +			break; + +		if (strncasecmp(buf, ct, sizeof(ct)-1) == 0) +			continue; + +		mimeautoreply_write_func(buf, strlen(buf), ptr); + +		while (strchr(buf, '\n') == NULL) +		{ +			if (fgets(buf, sizeof(buf), p->contentf) == NULL) +				break; + +			mimeautoreply_write_func(buf, strlen(buf), ptr); +		} +	} +} + +static void copy_body(void *ptr) +{ +	struct mimeautoreply_s *p=(struct mimeautoreply_s *)ptr; +	char buf[BUFSIZ]; + +	while (fgets(buf, sizeof(buf), p->contentf) != NULL) +	{ +		mimeautoreply_write_func(buf, strlen(buf), ptr); +	} +} + +struct fb { +	struct fb *next; +	const char *n; +	const char *v; +}; + +int main(int argc, char **argv) +{ +	int argn; +	FILE *tmpfp; +	struct rfc2045 *rfcp; +	struct mimeautoreply_s replyinfo; +	const char *subj=0; +	const char *txtfile=0, *mimefile=0; +	const char *mimedsn=0; +	int nosend=0; +	const char *replymode="reply"; +	int replytoenvelope=0; +	int donotquote=0; +	int fullmsg=0; +	const char *forwardsep="--- Forwarded message ---"; +	const char *replysalut="%F writes:"; +	struct rfc2045src *src; + +	const char *feedback_type=0; +	struct fb *fb_list=0, **fb_tail=&fb_list; +	size_t fb_cnt=0; + +	setlocale(LC_ALL, ""); +	charset=unicode_default_chset(); + +	sender=NULL; +	for (argn=1; argn < argc; argn++) +	{ +		char optc; +		char *optarg; + +		if (argv[argn][0] != '-') +			break; + +		if (strcmp(argv[argn], "--") == 0) +		{ +			++argn; +			break; +		} + +		if (strncmp(argv[argn], "--feedback-", 11) == 0) +		{ +			struct fb *f; + +			if (++argn >= argc) +				break; + +			if ((f=malloc(sizeof(struct fb))) == NULL) +			{ +				perror("malloc"); +				exit(1); +			} +			 +			f->n=argv[argn-1]+11; +			f->v=argv[argn]; + +			f->next=NULL; +			*fb_tail=f; +			fb_tail=&f->next; +			++fb_cnt; +			continue; +		} + +		optc=argv[argn][1]; +		optarg=argv[argn]+2; + +		if (!*optarg) +			optarg=NULL; + +		switch (optc) { +		case 'c': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			if (optarg && *optarg) +			{ +				char *p=libmail_u_convert_tobuf("", +								optarg, +								libmail_u_ucs4_native, +								NULL); + +				if (!p) +				{ +					fprintf(stderr, "Unknown charset: %s\n", +						charset); +					exit(1); +				} +				free(p); +				charset=optarg; +			} +			continue; +		case 't': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			txtfile=optarg; +			continue; +		case 'm': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			mimefile=optarg; +			continue; +		case 'r': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			recips=optarg; +			continue; +		case 'M': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			mimedsn=optarg; +			continue; +		case 'R': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			feedback_type=optarg; +			continue; +		case 'd': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			dbfile=optarg; +			continue; +		case 'e': +			replytoenvelope=1; +			continue; +		case 'T': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			if (optarg && *optarg) +				replymode=optarg; +			continue; +		case 'N': +			donotquote=1; +			continue; +		case 'a': +			fullmsg=1; +			continue; +		case 'F': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			if (optarg && *optarg) +				forwardsep=optarg; +			continue; +		case 'S': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			if (optarg && *optarg) +				replysalut=optarg; +			continue; +		case 'D': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			interval=optarg ? atoi(optarg):1; +			continue; +		case 'A': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			if (optarg) +			{ +				struct header **h; + +				for (h= &extra_headers; *h; +				     h= &(*h)->next) +					; + +				if ((*h=malloc(sizeof(struct header))) == 0 || +				    ((*h)->buf=strdup(optarg)) == 0) +				{ +					perror("malloc"); +					exit(EX_TEMPFAIL); +				} +				(*h)->next=0; +			} +			continue; +		case 's': +			if (!optarg && argn+1 < argc) +				optarg=argv[++argn]; + +			subj=optarg; +			continue; + +		case 'f': +			if (optarg && *optarg) +			{ +				sender=strdup(optarg); +			} +			else +			{ +				sender=getenv("SENDER"); +				if (!sender) +					continue; +				sender=strdup(sender); +			} +			if (sender == NULL) +			{ +				perror("malloc"); +				exit(1); +			} +			continue; +		case 'n': +			nosend=1; +			continue; +		default: +			usage(); +		} +	} + +	if (!txtfile && !mimefile) +		usage(); + +	if (txtfile && mimefile) +		usage(); + +	tmpfp=tmpfile(); + +	if (!tmpfp) +	{ +		perror("tmpfile"); +		exit(1); +	} + +	rfcp=savemessage(tmpfp); + +	if (fseek(tmpfp, 0L, SEEK_SET) < 0) +	{ +		perror("fseek(tempfile)"); +		exit(1); +	} + +	read_headers(tmpfp); + +	if (sender == NULL || *sender == 0) +		check_sender(); + +	check_dsn(); +	check_recips(); +#ifdef DbObj +	check_db(); +#endif + +	src=rfc2045src_init_fd(fileno(tmpfp)); + +	memset(&replyinfo, 0, sizeof(replyinfo)); + +	replyinfo.info.src=src; +	replyinfo.info.rfc2045partp=rfcp; +	replyinfo.info.voidarg=&replyinfo; + +	replyinfo.info.write_func=mimeautoreply_write_func; + +	replyinfo.info.writesig_func=mimeautoreply_writesig_func; + +	replyinfo.info.myaddr_func=mimeautoreply_myaddr_func; + +	replyinfo.info.replymode=replymode; +	replyinfo.info.replytoenvelope=replytoenvelope; +	replyinfo.info.donotquote=donotquote; + +	replyinfo.info.replysalut=replysalut; +	replyinfo.info.forwarddescr="Forwarded message"; +	replyinfo.info.mailinglists=""; +	replyinfo.info.charset=charset; +	replyinfo.info.subject=subj; +	replyinfo.info.forwardsep=forwardsep; +	replyinfo.info.fullmsg=fullmsg; + +	if (mimedsn && *mimedsn) +	{ +		replyinfo.info.dsnfrom=mimedsn; +		replyinfo.info.replymode="replydsn"; +	} +	else if (feedback_type && *feedback_type) +	{ +		replyinfo.info.feedbacktype=feedback_type; + +		if (strcmp(replyinfo.info.replymode, "feedback") && +		    strcmp(replyinfo.info.replymode, "replyfeedback")) +		{ +			fprintf(stderr, "\"-T feedback\" or \"-T replyfeedback\" required\n"); +			exit(1); +		} + +		if (fb_cnt > 0) +		{ +			size_t i; +			struct fb *p; +			const char **strp; + +			replyinfo.info.feedbackheaders= +				strp=malloc(sizeof(char *) * 2 * fb_cnt+1); + +			for (i=0, p=fb_list; p; p=p->next) +			{ +				strp[i++]=p->n; +				strp[i++]=p->v; +			} +			strp[i]=NULL; +		} +	} + +	if (mimefile) +	{ +		if ((replyinfo.contentf=fopen(mimefile, "r")) == NULL) +		{ +			perror(mimefile); +			exit(1); +		} + +		{ +			struct rfc2045 *rfcp=rfc2045_alloc(); +			static const char mv[]="Mime-Version: 1.0\n"; +			char buf[BUFSIZ]; +			int l; +			const char *content_type; +			const char *content_transfer_encoding; +			const char *charset; + +			rfc2045_parse(rfcp, mv, sizeof(mv)-1); + +			while ((l=fread(buf, 1, sizeof(buf), replyinfo.contentf) +				) > 0) +			{ +				rfc2045_parse(rfcp, buf, l); +			} + +			if (l < 0 || +			    fseek(replyinfo.contentf, 0L, SEEK_SET) < 0) +			{ +				perror(mimefile); +				exit(1); +			} + +			rfc2045_mimeinfo(rfcp, &content_type, +					 &content_transfer_encoding, +					 &charset); + +			if (strcasecmp(content_type, "text/plain")) +			{ +				fprintf(stderr, +					"%s must specify text/plain MIME type\n", +					mimefile); +				exit(1); +			} +			{ +				char *p=NULL; + +				if (charset) +					p=libmail_u_convert_tobuf("", +								  charset, +								  libmail_u_ucs4_native, +								  NULL); + +				if (!p) +				{ +					fprintf(stderr, "Unknown charset in %s\n", +						mimefile); +					exit(1); +				} +				free(p); +				replyinfo.info.charset=strdup(charset); +			} +			rfc2045_free(rfcp); +		} +		replyinfo.info.content_set_charset=copy_headers; +		replyinfo.info.content_specify=copy_body; +	} +	else if (txtfile) +	{ +		if ((replyinfo.contentf=fopen(txtfile, "r")) == NULL) +		{ +			perror(mimefile); +			exit(1); +		} +		replyinfo.info.content_specify=copy_body; +	} + +	if (replyinfo.contentf) +		fcntl(fileno(replyinfo.contentf), F_SETFD, FD_CLOEXEC); + +	if (nosend) +		replyinfo.outf=stdout; +	else +	{ +		replyinfo.outf=tmpfile(); + +		if (replyinfo.outf == NULL) +		{ +			perror("tmpfile"); +			exit(1); +		} +	} + +	{ +		struct header *h; + +		for (h=extra_headers; h; h=h->next) +			fprintf(replyinfo.outf, "%s\n", h->buf); +	} +	fprintf(replyinfo.outf, +		"Precedence: junk\n" +		"Auto-Submitted: auto-replied\n"); + +	if (rfc2045_makereply(&replyinfo.info) < 0 || +	    fflush(replyinfo.outf) < 0 || ferror(replyinfo.outf) || +	    (!nosend && +	     ( +	      fseek(replyinfo.outf, 0L, SEEK_SET) < 0 || +	      (close(0), dup(fileno(replyinfo.outf))) < 0) +	     )) +	{ +		perror("tempfile"); +		exit(1); +	} +	fclose(replyinfo.outf); +	fcntl(0, F_SETFD, 0); + +	rfc2045_free(rfcp); +	rfc2045src_deinit(src); + +	if (!nosend) +		opensendmail(argn, argc, argv); +	return (0); +} | 
