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); +} |
