X-Git-Url: https://git.distorted.org.uk/~mdw/ezmlm/blobdiff_plain/5b62e993b0af39700031c2875d7f6654e6a02850..f8beb284087c279acfb30506f5bb32baa4949b44:/ezmlm-receipt.c?ds=sidebyside diff --git a/ezmlm-receipt.c b/ezmlm-receipt.c new file mode 100644 index 0000000..7b664f9 --- /dev/null +++ b/ezmlm-receipt.c @@ -0,0 +1,374 @@ +/*$Id: ezmlm-receipt.c,v 1.10 1999/02/05 04:57:44 lindberg Exp $*/ +/*$Name: ezmlm-idx-0324 $*/ +/* Handles receipts and bounces from sublists at the main list */ +/* Set up instead of ezmlm-return in DIR/bouncer of main list */ + +#include +#include "direntry.h" +#include "stralloc.h" +#include "str.h" +#include "env.h" +#include "slurp.h" +#include "getconf.h" +#include "strerr.h" +#include "byte.h" +#include "case.h" +#include "quote.h" +#include "getln.h" +#include "substdio.h" +#include "error.h" +#include "readwrite.h" +#include "fmt.h" +#include "now.h" +#include "seek.h" +#include "idx.h" +#include "errtxt.h" + +#define FATAL "ezmlm-receipt: fatal: " +#define INFO "ezmlm-receipt: info: " + +void die_usage() +{ + strerr_die1x(100,"ezmlm-receipt: usage: ezmlm-receipt [-dD] dir"); +} + +void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); } + +void die_badaddr() +{ + strerr_die2x(100,FATAL,ERR_BAD_ADDRESS); +} +void die_trash() +{ + strerr_die2x(0,INFO,"trash address"); +} + +stralloc line = {0}; +stralloc quoted = {0}; +stralloc intro = {0}; +stralloc bounce = {0}; +stralloc header = {0}; +stralloc failure = {0}; +stralloc paragraph = {0}; +stralloc ddir = {0}; +stralloc outhost = {0}; +stralloc outlocal = {0}; +stralloc inlocal = {0}; +stralloc tagline = {0}; +stralloc listaddr = {0}; +stralloc fndate = {0}; +stralloc fndir = {0}; +stralloc fndatenew = {0}; + +void die_datenew() +{ strerr_die4sys(111,FATAL,ERR_WRITE,fndatenew.s,": "); } +void die_msgin() +{ strerr_die2sys(111,FATAL,ERR_READ_INPUT); } + +char strnum[FMT_ULONG]; +char inbuf[1024]; +substdio ssin; + +char outbuf[256]; /* small - rarely used */ +substdio ssout; + +unsigned long when; +unsigned long addrno = 0L; + +char *sender; +char *dir; +char *workdir; +void **psql = (void **) 0; +stralloc listno = {0}; + + +void doit(addr,msgnum,when,bounce) +/* Just stores address\0nsgnum\0 followed by bounce. File name is */ +/* dttt.ppp[.n], where 'ttt' is a time stamp, 'ppp' the pid, and 'n' the */ +/* number when there are more than 1 addresses in a pre-VERP bounce. In */ +/* this case, the first one is just dttt.ppp, the decond dttt.ppp.2, etc. */ +/* For a main list, bounces come from sublists. They are rare and serious. */ +char *addr; +unsigned long msgnum; +unsigned long when; +stralloc *bounce; +{ + int fd; + unsigned int pos; + DIR *bouncedir; + direntry *d; + unsigned int no; + + if (!stralloc_copys(&fndir,workdir)) die_nomem(); + if (!stralloc_cats(&fndir,"/bounce")) die_nomem(); + if (!stralloc_0(&fndir)) die_nomem(); + bouncedir = opendir(fndir.s); + if (!bouncedir) + if (errno != error_noent) + strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": "); + else + strerr_die3x(111,FATAL,fndir.s,ERR_NOEXIST); + + no = MAX_MAIN_BOUNCES; /* no more than this many allowed */ + while (no && (d = readdir(bouncedir))) { + if (str_equal(d->d_name,".")) continue; + if (str_equal(d->d_name,"..")) continue; + --no; + } + closedir(bouncedir); + if (!no) /* max no of bounces exceeded */ + strerr_die2x(0,INFO,ERR_MAX_BOUNCE); + /* save bounce */ + if (!stralloc_copys(&fndate,workdir)) die_nomem(); + if (!stralloc_cats(&fndate,"/bounce/d")) die_nomem(); + pos = fndate.len - 1; + if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,when))) die_nomem(); + if (!stralloc_cats(&fndate,".")) die_nomem(); + if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,(unsigned long) getpid()))) + die_nomem(); + if (addrno) { /* so that pre-VERP bounces make a d... file per address */ + /* for the first one we use the std-style fname */ + if (!stralloc_cats(&fndate,".")) die_nomem(); + if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,addrno))) die_nomem(); + } + addrno++; /* get ready for next */ + if (!stralloc_0(&fndate)) die_nomem(); + if (!stralloc_copy(&fndatenew,&fndate)) die_nomem(); + fndatenew.s[pos] = 'D'; + + fd = open_trunc(fndatenew.s); + if (fd == -1) die_datenew(); + substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf)); + if (substdio_puts(&ssout,addr) == -1) die_datenew(); + if (substdio_put(&ssout,"",1) == -1) die_datenew(); + if (substdio_put(&ssout,strnum,fmt_ulong(strnum,msgnum)) == -1) + die_datenew(); + if (substdio_put(&ssout,"",1) == -1) die_datenew(); + + if (substdio_puts(&ssout,"Return-Path: <") == -1) die_datenew(); + if (!quote2("ed,sender)) die_nomem(); + if (substdio_put(&ssout,quoted.s,quoted.len) == -1) die_datenew(); + if (substdio_puts(&ssout,">\n") == -1) die_datenew(); + if (substdio_put(&ssout,bounce->s,bounce->len) == -1) die_datenew(); + if (substdio_flush(&ssout) == -1) die_datenew(); + if (fsync(fd) == -1) die_datenew(); + if (close(fd) == -1) die_datenew(); /* NFS stupidity */ + if (rename(fndatenew.s,fndate.s) == -1) + strerr_die6sys(111,FATAL,ERR_MOVE,fndatenew.s," to ",fndate.s,": "); +} + +void main(argc,argv) +int argc; +char **argv; +{ + char *local; + char *host; + char *action; + char *def; + int flagdig = 1; + int flaghaveintro; + int flaghaveheader; + int match; + unsigned long msgnum; + unsigned int i; + unsigned int len; + char *cp; + + umask(022); + sig_pipeignore(); + + when = (unsigned long) now(); + + dir = argv[1]; + if (!dir) die_usage(); + if (*dir == '-') { + if (dir[1] == 'd') { + flagdig = 2; + } else if (dir[1] == 'D') { + flagdig = 0; + } else + die_usage(); + dir = argv[2]; + if (!dir) die_usage(); + } + if (chdir(dir) == -1) + strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": "); + + sender = env_get("SENDER"); + def = env_get("DEFAULT"); + local = env_get("LOCAL"); + + getconf_line(&outhost,"outhost",1,FATAL,dir); + getconf_line(&outlocal,"outlocal",1,FATAL,dir); + workdir = dir; + if (def) { /* qmail>=1.02 */ + action = def; /* now see if -digest-return- */ + if (flagdig == 1) { + flagdig = 0; + if (str_len(local) >= str_len(def) + 14) + if (str_start(local + str_len(local) - 14 - str_len(def),"digest-")) + flagdig = 2; + } + } else { /* older version of qmail */ + getconf_line(&inlocal,"inlocal",1,FATAL,dir); + if (inlocal.len > str_len(local)) die_badaddr(); + if (case_diffb(inlocal.s,inlocal.len,local)) die_badaddr(); + action = local + inlocal.len; + if (flagdig == 1) { + flagdig = 0; + if (str_start(action,"-digest")) { + flagdig = 2; + action += 7; + } + } + if (!str_start(action,"-return-")) die_badaddr(); + action += 8; + } + if (flagdig) { + if (!stralloc_copys(&ddir,dir)) die_nomem(); + if (!stralloc_cats(&ddir,"/digest")) die_nomem(); + if (!stralloc_0(&ddir)) die_nomem(); + workdir = ddir.s; + if (!stralloc_cats(&outlocal,"-digest")) die_nomem(); + } + if (!*action) die_trash(); + + substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); + + if (!case_diffs(action,"receipt")) { + host = sender + str_rchr(sender,'@'); + if (*host) + *(host++) = '\0'; + cp = sender; + /* check recipient in case it's a bounce*/ + while (*(cp++)) { /* decode sender */ + cp += str_chr(cp,'-'); + if (case_starts(cp,"-return-")) { + if (!scan_ulong(cp + 8,&msgnum)) + strerr_die2x(100,FATAL,"bad VERP format for receipt"); + *cp = '\0'; + if (!stralloc_copys(&listaddr,sender)) die_nomem(); + if (!stralloc_append(&listaddr,"@")) die_nomem(); + if (!stralloc_cats(&listaddr,host)) die_nomem(); + if (!stralloc_0(&listaddr)) die_nomem(); + break; + } + } + for(;;) { /* Get X-tag from hdr*/ + if (getln(&ssin,&line,&match,'\n') == -1) die_msgin(); + if (!match) + break; + + if (line.len == 1) break; + if (case_startb(line.s,line.len,TXT_TAG)) { + len = str_len(TXT_TAG); + if (!stralloc_catb(&tagline,line.s +len,line.len - len -1)) die_nomem(); + /* NOTE: tagline is dirty! We quote it for sql and rely on */ + /* std log clean for maillog */ + break; + } + } + /* feedback ok even if not sub. Will be filtered by subreceipt*/ + /* For instance, main list feedback is ok, but !issub. */ + subreceipt(workdir,msgnum,&tagline,listaddr.s,2,INFO,FATAL); + closesql(); + _exit(0); + } + /* not receipt - maybe bounce */ + /* no need to lock. dttt.pid can be assumed */ + /* to be unique and if not would be over- */ + /* written even with lock */ + action += scan_ulong(action,&msgnum); + if (*action != '-') die_badaddr(); + ++action; + /* scan bounce for tag. It'll be in the BODY! */ + for (;;) { + if (getln(&ssin,&line,&match,'\n') == -1) + strerr_die2sys(111,FATAL,ERR_READ_INPUT); + if (!match) break; + if (case_startb(line.s,line.len,TXT_TAG)) { + len = str_len(TXT_TAG); + if (!stralloc_catb(&tagline,line.s +len,line.len - len -1)) die_nomem(); + /* NOTE: tagline is dirty! We quote it for sql and rely on */ + /* std log clean for maillog */ + break; + } + } + if (seek_begin(0) == -1) + strerr_die2sys(111,FATAL,ERR_SEEK_INPUT); + substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); + + if (*action) { /* normal bounce */ + + if (slurpclose(0,&bounce,1024) == -1) die_msgin(); + i = str_rchr(action,'='); + if (!stralloc_copyb(&listaddr,action,i)) die_nomem(); + if (action[i]) { + if (!stralloc_cats(&listaddr,"@")) die_nomem(); + if (!stralloc_cats(&listaddr,action + i + 1)) die_nomem(); + } + if (!stralloc_0(&listaddr)) die_nomem(); + /* don't check for sub, since issub() doesn't see sublists */ + switch (subreceipt(workdir,msgnum,&tagline,listaddr.s,-1,INFO,FATAL)) { + case -1: strerr_die2x(0,INFO,ERR_COOKIE); + case -2: strerr_die2x(0,INFO,ERR_NOT_ACTIVE); + default: doit(listaddr.s,msgnum,when,&bounce); + } + closesql(); + _exit(0); + } /* pre-VERP bounce, in QSBMF format */ + + flaghaveheader = 0; + flaghaveintro = 0; + + for (;;) { + if (!stralloc_copys(¶graph,"")) die_nomem(); + for (;;) { + if (getln(&ssin,&line,&match,'\n') == -1) + strerr_die2sys(111,FATAL,ERR_READ_INPUT); + if (!match) die_trash(); + if (!stralloc_cat(¶graph,&line)) die_nomem(); + if (line.len <= 1) break; + } + + if (!flaghaveheader) { + if (!stralloc_copy(&header,¶graph)) die_nomem(); + flaghaveheader = 1; + continue; + } + + if (!flaghaveintro) { + if (paragraph.s[0] == '-' && paragraph.s[1] == '-') + continue; /* skip MIME boundary if it exists */ + if (paragraph.len < 15) die_trash(); + if (str_diffn(paragraph.s,"Hi. This is the",15)) die_trash(); + if (!stralloc_copy(&intro,¶graph)) die_nomem(); + flaghaveintro = 1; + continue; + } + + if (paragraph.s[0] == '-') + break; + + if (paragraph.s[0] == '<') { /* find address */ + if (!stralloc_copy(&failure,¶graph)) die_nomem(); + + if (!stralloc_copy(&bounce,&header)) die_nomem(); + if (!stralloc_cat(&bounce,&intro)) die_nomem(); + if (!stralloc_cat(&bounce,&failure)) die_nomem(); + + i = byte_chr(failure.s,failure.len,'\n'); + if (i < 3) die_trash(); + + if (!stralloc_copyb(&listaddr,failure.s + 1,i - 3)) die_nomem(); + if (byte_chr(listaddr.s,listaddr.len,'\0') == listaddr.len) { + if (!stralloc_0(&listaddr)) die_nomem(); + if (subreceipt(workdir,msgnum,&tagline,listaddr.s,-1,INFO,FATAL) == 0) + doit(listaddr.s,msgnum,when,&bounce); + } + } + } + closesql(); + _exit(0); +} +