X-Git-Url: https://git.distorted.org.uk/~mdw/ezmlm/blobdiff_plain/5b62e993b0af39700031c2875d7f6654e6a02850..f8beb284087c279acfb30506f5bb32baa4949b44:/ezmlm-manage.c diff --git a/ezmlm-manage.c b/ezmlm-manage.c index d40ba0f..134c5a0 100644 --- a/ezmlm-manage.c +++ b/ezmlm-manage.c @@ -1,3 +1,6 @@ +/*$Id: ezmlm-manage.c,v 1.86 1999/12/23 02:43:55 lindberg Exp $*/ +/*$Name: ezmlm-idx-040 $*/ + #include #include #include "error.h" @@ -22,13 +25,56 @@ #include "fmt.h" #include "subscribe.h" #include "cookie.h" +#include "sgetopt.h" +#include "copy.h" +#include "errtxt.h" +#include "idx.h" #define FATAL "ezmlm-manage: fatal: " -void die_usage() { strerr_die1x(100,"ezmlm-manage: usage: ezmlm-manage dir"); } -void die_nomem() { strerr_die2x(111,FATAL,"out of memory"); } +#define INFO "ezmlm-manage: info: " + +int flagverbose = 0; /* default: Owner not informed about subdb changes */ + /* 1 => notified for failed unsub, 2 => for all */ +int flagnotify = 1; /* notify subscriber of completed events. 0 also */ + /* suppresses all subscriber communication for */ + /* [un]sub if -U/-S is used */ +int flagbottom = 1; /* default: copy request & admin info to message */ +int flaglist = 0; /* default: do not reply to -list */ +int flagget = 1; /* default: service -get requests */ +int flagsubconf = 1; /* default: require user-confirm for subscribe */ +int flagunsubconf = 1; /* default: require user-confirm for unsubscribe */ +int flagunsubismod = 0; /* default: do not require moderator approval to */ + /* unsubscribe from moderated list */ +int flagedit = 0; /* default: text file edit not allowed */ +int flagstorefrom = 1; /* default: store from: line for subscribes */ +char flagcd = '\0'; /* default: do not use _Q_uoted printable or _B_ase64 */ +char encin = '\0'; /* encoding of incoming message */ +int flagdig = 0; /* request is not for digest list */ + +static const char hex[]="0123456789ABCDEF"; +char urlstr[] = "%00"; /* to build a url-encoded version of a char */ + +int act = AC_NONE; /* desired action */ +unsigned int actlen = 0;/* str_len of above */ +char *dir; +char *workdir; +char *sender; +void *psql = (void *) 0; + +void die_usage() { + strerr_die1x(100,"ezmlm-manage: usage: ezmlm-manage " + "[-bBcCdDeEfFlLmMnNqQsSuUvV] dir"); } + +void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); } + void die_badaddr() { - strerr_die2x(100,FATAL,"I do not accept messages at this address (#5.1.1)"); + strerr_die2x(100,FATAL,ERR_BAD_ADDRESS); +} + +void die_cookie() +{ + strerr_die2x(100,FATAL,ERR_MOD_COOKIE); } stralloc inhost = {0}; @@ -37,34 +83,200 @@ stralloc inlocal = {0}; stralloc outlocal = {0}; stralloc key = {0}; stralloc mailinglist = {0}; +stralloc mydtline = {0}; +stralloc target = {0}; +stralloc verptarget = {0}; +stralloc confirm = {0}; +stralloc line = {0}; +stralloc qline = {0}; +stralloc quoted = {0}; +stralloc moddir = {0}; +stralloc ddir = {0}; +stralloc modsub = {0}; +stralloc remote = {0}; +stralloc from = {0}; +stralloc to = {0}; +stralloc owner = {0}; +stralloc fromline = {0}; +stralloc text = {0}; +stralloc fnedit = {0}; +stralloc fneditn = {0}; +stralloc charset = {0}; datetime_sec when; struct datetime dt; +int match; +unsigned int max; char strnum[FMT_ULONG]; char date[DATE822FMT]; char hash[COOKIE]; +char boundary[COOKIE]; datetime_sec hashdate; -stralloc target = {0}; -stralloc confirm = {0}; -stralloc line = {0}; -stralloc quoted = {0}; -int hashok(action) +char inbuf[1024]; +substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf)); +substdio ssin2 = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf)); + +substdio sstext; /* editing texts and reading "from" */ +char textbuf[512]; + +substdio ssfrom; /* writing "from" */ +char frombuf[512]; + +int fdlock; + +void lock() +{ + fdlock = open_append("lock"); + if (fdlock == -1) + strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/lock: "); + if (lock_ex(fdlock) == -1) + strerr_die4sys(111,FATAL,ERR_OBTAIN,dir,"/lock: "); +} + +void unlock() +{ + close(fdlock); +} + +void make_verptarget() +/* puts target with '=' instead of last '@' into stralloc verptarget */ +/* and does set_cpverptarget */ +{ + unsigned int i; + + i = str_rchr(target.s,'@'); + if (!stralloc_copyb(&verptarget,target.s,i)) die_nomem(); + if (target.s[i]) { + if (!stralloc_append(&verptarget,"=")) die_nomem(); + if (!stralloc_cats(&verptarget,target.s + i + 1)) die_nomem(); + } + if (!stralloc_0(&verptarget)) die_nomem(); + set_cpverptarget(verptarget.s); +} + +void store_from(frl,adr) +/* rewrites the from file removing all that is older than 1000000 secs */ +/* and add the curent from line (frl). Forget it if there is none there.*/ +/* NOTE: This is used only for subscribes to moderated lists! */ +stralloc *frl; /* from line */ +char *adr; +{ + int fdin; + int fdout; + unsigned long linetime; + + if (!flagstorefrom || !frl->len) return; /* nothing to store */ + lock(); + if ((fdout = open_trunc("fromn")) == -1) + strerr_die3sys(111,FATAL,ERR_OPEN,"fromn: "); + substdio_fdbuf(&ssfrom,write,fdout,frombuf,(int) sizeof(frombuf)); + if ((fdin = open_read("from")) == -1) { + if (errno != error_noent) + strerr_die3sys(111,FATAL,ERR_OPEN,"from: "); + } else { + substdio_fdbuf(&sstext,read,fdin,textbuf,(int) sizeof(textbuf)); + for (;;) { + if (getln(&sstext,&line,&match,'\n') == -1) + strerr_die3sys(111,FATAL,ERR_READ,"from: "); + if (!match) break; + (void) scan_ulong(line.s,&linetime); + if (linetime + 1000000 > when && linetime <= when) + if (substdio_bput(&ssfrom,line.s,line.len)) + strerr_die3sys(111,FATAL,ERR_WRITE,"fromn: "); + } + close(fdin); + } /* build new entry */ + if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,when))) die_nomem(); + if (!stralloc_append(&line," ")) die_nomem(); + if (!stralloc_cats(&line,adr)) die_nomem(); + if (!stralloc_0(&line)) die_nomem(); + if (!stralloc_catb(&line,frl->s,frl->len)) die_nomem(); + if (!stralloc_append(&line,"\n")) die_nomem(); + if (substdio_bput(&ssfrom,line.s,line.len) == -1) + strerr_die3sys(111,FATAL,ERR_WRITE,"fromn: "); + if (substdio_flush(&ssfrom) == -1) + strerr_die3sys(111,FATAL,ERR_WRITE,"fromn: "); + if (fsync(fdout) == -1) + strerr_die3sys(111,FATAL,ERR_SYNC,"fromn: "); + if (close(fdout) == -1) + strerr_die3sys(111,FATAL,ERR_CLOSE,"fromn: "); + if (rename("fromn","from") == -1) + strerr_die3sys(111,FATAL,ERR_MOVE,"from: "); + unlock(); +} + +char *get_from(adr,act) +/* If we captured a from line, it will be from the subscriber, except */ +/* when -S is used when it's usually from the subscriber, but of course */ +/* could be from anyone. The matching to stored data is required only */ +/* to support moderated lists, and in cases where a new -sc is issued */ +/* because an old one was invalid. In this case, we read through the */ +/* from file trying to match up a timestamp with that starting in */ +/* *(act+3). If the time stamp matches, we compare the target address */ +/* itself. act + 3 must be a legal part of the string returns pointer to*/ +/* fromline, NULL if not found. Since the execution time from when to */ +/* storage may differ, we can't assume that the timestamps are in order.*/ + +char *adr; /* target address */ +char *act; /* action */ +{ + int fd; + char *fl; + unsigned int pos; + unsigned long thistime; + unsigned long linetime; + + if (!flagstorefrom) return 0; + if (fromline.len) { /* easy! We got it in this message */ + if (!stralloc_0(&fromline)) die_nomem(FATAL); + return fromline.s; + } /* need to recover it from DIR/from */ + fl = 0; + (void) scan_ulong(act+3,&thistime); + if ((fd = open_read("from")) == -1) + if (errno == error_noent) + return 0; + else + strerr_die3x(111,FATAL,ERR_READ,"from: "); + substdio_fdbuf(&sstext,read,fd,textbuf,(int) sizeof(textbuf)); + for (;;) { + if (getln(&sstext,&fromline,&match,'\n') == -1) + strerr_die3sys(111,FATAL,ERR_READ,"from: "); + if (!match) break; + fromline.s[fromline.len - 1] = (char) 0; + /* now:time addr\0fromline\0 read all. They can be out of order! */ + pos = scan_ulong(fromline.s,&linetime); + if (linetime != thistime) continue; + if (!str_diff(fromline.s + pos + 1,adr)) { + pos = str_len(fromline.s); + if (pos < fromline.len) { + fl = fromline.s + pos + 1; + break; + } + } + } + close(fd); + return fl; +} + +int hashok(action,ac) char *action; +char *ac; { char *x; - unsigned long u; + datetime_sec u; - x = action + 4; + x = action + 3; x += scan_ulong(x,&u); hashdate = u; if (hashdate > when) return 0; if (hashdate < when - 1000000) return 0; u = hashdate; - strnum[fmt_ulong(strnum,u)] = 0; - cookie(hash,key.s,key.len,strnum,target.s,action + 1); + strnum[fmt_ulong(strnum,(unsigned long) u)] = 0; + cookie(hash,key.s,key.len - flagdig,strnum,target.s,ac); if (*x == '.') ++x; if (str_len(x) != COOKIE) return 0; @@ -77,127 +289,528 @@ int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len; qmail_put(&qq,buf,len); return len; } + char qqbuf[1]; -substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf)); +substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,(int) sizeof(qqbuf)); -char inbuf[1024]; -substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf)); -substdio ssin2 = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf)); +int code_qput(s,n) +char *s; +unsigned int n; +{ + if (!flagcd) + qmail_put(&qq,s,n); + else { + if (flagcd == 'B') + encodeB(s,n,&qline,0,FATAL); + else + encodeQ(s,n,&qline,FATAL); + qmail_put(&qq,qline.s,qline.len); + } + return 0; /* always succeeds */ +} -substdio sstext; -char textbuf[1024]; +int subto(s,l) +char *s; +unsigned int l; +{ + qmail_put(&qq,"T",1); + qmail_put(&qq,s,l); + qmail_put(&qq,"",1); + return (int) l; +} -void copy(fn) -char *fn; +int code_subto(s,l) +char *s; +unsigned int l; { - int fd; - int match; + code_qput(s,l); + code_qput("\n",1); + return (int) l; +} - fd = open_read(fn); - if (fd == -1) - strerr_die4sys(111,FATAL,"unable to open ",fn,": "); +int dummy_to(s,l) +char *s; /* ignored */ +unsigned int l; +{ + return (int) l; +} - substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf)); - for (;;) { - if (getln(&sstext,&line,&match,'\n') == -1) - strerr_die4sys(111,FATAL,"unable to read ",fn,": "); - - if (match) - if (line.s[0] == '!') { - if (line.s[1] == 'R') { - qmail_puts(&qq," "); - qmail_puts(&qq,confirm.s); - qmail_puts(&qq,"\n"); - continue; - } - if (line.s[1] == 'A') { - qmail_puts(&qq," "); - qmail_puts(&qq,target.s); - qmail_puts(&qq,"\n"); - continue; - } +void transferenc() +{ + if (flagcd) { + qmail_puts(&qq,"\n--"); + qmail_put(&qq,boundary,COOKIE); + qmail_puts(&qq,"\nContent-Type: text/plain; charset="); + qmail_puts(&qq,charset.s); + qmail_puts(&qq,"\nContent-Transfer-Encoding: "); + if (flagcd == 'Q') + qmail_puts(&qq,"quoted-printable\n\n"); + else + qmail_puts(&qq,"base64\n\n"); + } else + qmail_puts(&qq,"\n"); +} + +void to_owner() +{ + if (!stralloc_copy(&owner,&outlocal)) die_nomem(); + if (!stralloc_cats(&owner,"-owner@")) die_nomem(); + if (!stralloc_cat(&owner,&outhost)) die_nomem(); + if (!stralloc_0(&owner)) die_nomem(); + qmail_to(&qq,owner.s); +} + +void mod_bottom() +{ + copy(&qq,"text/mod-sub",flagcd,FATAL); + copy(&qq,"text/bottom",flagcd,FATAL); + code_qput(TXT_SUPPRESSED,str_len(TXT_SUPPRESSED)); + if (flagcd) { + qmail_puts(&qq,"\n--"); + qmail_put(&qq,boundary,COOKIE); + qmail_puts(&qq,"--\n"); } + if (flagcd == 'B') { + encodeB("",0,&line,2,FATAL); /* flush */ + qmail_put(&qq,line.s,line.len); + } + qmail_from(&qq,from.s); +} +void msg_headers() + /* Writes all the headers up to but not including subject */ +{ + int flaggoodfield; + int flagfromline; + int flaggetfrom; + unsigned int pos; + qmail_puts(&qq,"Mailing-List: "); + qmail_put(&qq,mailinglist.s,mailinglist.len); + if(getconf_line(&line,"listid",0,FATAL,dir)) { + qmail_puts(&qq,"\nList-ID: "); qmail_put(&qq,line.s,line.len); + } + if (!quote("ed,&outlocal)) die_nomem(); /* quoted has outlocal */ + qmail_puts(&qq,"\nList-Help: \nList-Post: \nList-Subscribe: \nDate: "); + datetime_tai(&dt,when); + qmail_put(&qq,date,date822fmt(date,&dt)); + qmail_puts(&qq,"Message-ID: <"); + if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,(unsigned long) when))) + die_nomem(); + if (!stralloc_append(&line,".")) die_nomem(); + if (!stralloc_catb(&line,strnum, + fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem(); + if (!stralloc_cats(&line,".ezmlm@")) die_nomem(); + if (!stralloc_cat(&line,&outhost)) die_nomem(); + if (!stralloc_0(&line)) die_nomem(); + qmail_puts(&qq,line.s); + /* "unique" MIME boundary as hash of messageid */ + cookie(boundary,"",0,"",line.s,""); + qmail_puts(&qq,">\nFrom: "); + qmail_put(&qq,quoted.s,quoted.len); + if (act == AC_HELP) /* differnt "From:" for help to break auto- */ + qmail_puts(&qq,"-return-@"); /* responder loops */ + else + qmail_puts(&qq,"-help@"); + qmail_put(&qq,outhost.s,outhost.len); + qmail_puts(&qq,"\nTo: "); + if (!quote2("ed,target.s)) die_nomem(); + qmail_put(&qq,quoted.s,quoted.len); + qmail_puts(&qq,"\n"); + if (!stralloc_copys(&mydtline,"Delivered-To: responder for ")) die_nomem(); + if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem(); + if (!stralloc_cats(&mydtline,"@")) die_nomem(); + if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem(); + if (!stralloc_cats(&mydtline,"\n")) die_nomem(); + qmail_put(&qq,mydtline.s,mydtline.len); - if (!match) - break; + flaggoodfield = 0; + flagfromline = 0; + /* do it for -sc, but if the -S flag is used, do it for -subscribe */ + flaggetfrom = flagstorefrom && + ((act == AC_SC) || ((act == AC_SUBSCRIBE) && !flagsubconf)); + for (;;) { + if (getln(&ssin,&line,&match,'\n') == -1) + strerr_die2sys(111,FATAL,ERR_READ_INPUT); + if (!match) break; + if (line.len == 1) break; + if ((line.s[0] != ' ') && (line.s[0] != '\t')) { + flagfromline = 0; + flaggoodfield = 0; + if (case_startb(line.s,line.len,"mailing-list:")) + strerr_die2x(100,FATAL,ERR_MAILING_LIST); + if (line.len == mydtline.len) + if (byte_equal(line.s,line.len,mydtline.s)) + strerr_die2x(100,FATAL,ERR_LOOPING); + if (case_startb(line.s,line.len,"delivered-to:")) + flaggoodfield = 1; + else if (case_startb(line.s,line.len,"received:")) + flaggoodfield = 1; + else if (case_startb(line.s,line.len,"content-transfer-encoding:")) { + pos = 26; + while (line.s[pos] == ' ' || line.s[pos] == '\t') ++pos; + if (case_startb(line.s+pos,line.len-pos,"base64")) + encin = 'B'; + else if (case_startb(line.s+pos,line.len-pos,"quoted-printable")) + encin = 'Q'; + } else if (flaggetfrom && case_startb(line.s,line.len,"from:")) { + flagfromline = 1; /* for logging subscriber data */ + pos = 5; + while (line.s[pos] == ' ' || line.s[pos] == '\t') ++pos; + if (!stralloc_copyb(&fromline,line.s + pos,line.len - pos - 1)) + die_nomem(); + } + } else { + if (flagfromline == 1) /* scrap terminal '\n' */ + if (!stralloc_catb(&fromline,line.s,line.len - 1)) die_nomem(); + } + if (flaggoodfield) + qmail_put(&qq,line.s,line.len); + } + qmail_puts(&qq,"MIME-Version: 1.0\n"); + if (flagcd) { + qmail_puts(&qq,"Content-Type: multipart/mixed; charset="); + qmail_puts(&qq,charset.s); + qmail_puts(&qq,";\n\tboundary="); + qmail_put(&qq,boundary,COOKIE); + } else { + qmail_puts(&qq,"Content-type: text/plain; charset="); + qmail_puts(&qq,charset.s); } + qmail_puts(&qq,"\n"); +} - close(fd); +int geton(action) +char *action; +{ + char *fl; + int r; + unsigned int i; + unsigned char ch; + + fl = get_from(target.s,action); /* try to match up */ + switch((r = subscribe(workdir,target.s,1,fl,"+",1,-1,(char *) 0,FATAL))) { + case 1: + qmail_puts(&qq,"List-Unsubscribe: &/:%+#",ch) < 10 || + (ch <= ' ') || (ch & 0x80)) { + urlstr[1] = hex[ch / 16]; + urlstr[2] = hex[ch & 0xf]; + qmail_put(&qq,urlstr,3); + } else { + qmail_put(&qq,verptarget.s + i, 1); + } + } + qmail_puts(&qq,"@"); + qmail_put(&qq,outhost.s,outhost.len); /* safe */ + qmail_puts(&qq,">\n"); + qmail_puts(&qq,TXT_WELCOME); + if (!quote("ed,&outlocal)) die_nomem(); + qmail_put(&qq,quoted.s,quoted.len); + qmail_puts(&qq,"@"); + qmail_put(&qq,outhost.s,outhost.len); + qmail_puts(&qq,"\n"); + transferenc(); + if (!stralloc_copy(&confirm,&outlocal)) die_nomem(); + if (!stralloc_append(&confirm,"unsubscribe-")) die_nomem(); + if (!stralloc_cats(&confirm,verptarget.s)) die_nomem(); + if (!stralloc_append(&confirm,"@")) die_nomem(); + if (!stralloc_cat(&confirm,&outhost)) die_nomem(); + if (!stralloc_0(&confirm)) die_nomem(); + set_cpconfirm(confirm.s); /* for !R in copy */ + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/sub-ok",flagcd,FATAL); + break; + default: + if (str_start(action,ACTION_TC)) + strerr_die2x(0,INFO,ERR_SUB_NOP); + qmail_puts(&qq,TXT_EZMLM_RESPONSE); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/sub-nop",flagcd,FATAL); + break; + } + if (flagdig == FLD_DENY || flagdig == FLD_ALLOW) + strerr_die3x(0,INFO,ERR_EXTRA_SUB,target.s); + return r; } -stralloc mydtline = {0}; +int getoff(action) +char *action; +{ + int r; + + switch((r = subscribe(workdir,target.s,0,"","-",1,-1,(char *) 0,FATAL))) { + /* no comment for unsubscribe */ + case 1: + qmail_puts(&qq,TXT_GOODBYE); + if (!quote("ed,&outlocal)) die_nomem(); + qmail_put(&qq,quoted.s,quoted.len); + qmail_puts(&qq,"@"); + qmail_put(&qq,outhost.s,outhost.len); + qmail_puts(&qq,"\n\n"); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/unsub-ok",flagcd,FATAL); + break; + default: + qmail_puts(&qq,TXT_EZMLM_RESPONSE); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/unsub-nop",flagcd,FATAL); + break; + } + if (flagdig == FLD_DENY || flagdig == FLD_ALLOW) + strerr_die3x(0,INFO,ERR_EXTRA_UNSUB,target.s); + return r; +} -void main(argc,argv) +void doconfirm(act) +/* This should only be called with valid act for sub/unsub confirms. If act */ +/* is not ACTION_SC or ACTION_TC, it is assumed to be an unsubscribe conf.*/ +char *act; /* first letter of desired confirm request only as STRING! */ +{ + unsigned int i; + + strnum[fmt_ulong(strnum,(unsigned long) when)] = 0; + cookie(hash,key.s,key.len-flagdig,strnum,target.s,act); + if (!stralloc_copy(&confirm,&outlocal)) die_nomem(); + if (!stralloc_append(&confirm,"-")) die_nomem(); + if (!stralloc_catb(&confirm,act,1)) die_nomem(); + if (!stralloc_cats(&confirm,"c.")) die_nomem(); + if (!stralloc_cats(&confirm,strnum)) die_nomem(); + if (!stralloc_append(&confirm,".")) die_nomem(); + if (!stralloc_catb(&confirm,hash,COOKIE)) die_nomem(); + if (!stralloc_append(&confirm,"-")) die_nomem(); + if (!stralloc_cats(&confirm,verptarget.s)) die_nomem(); + if (!stralloc_append(&confirm,"@")) die_nomem(); + if (!stralloc_cat(&confirm,&outhost)) die_nomem(); + if (!stralloc_0(&confirm)) die_nomem(); + set_cpconfirm(confirm.s); /* for copy */ + + qmail_puts(&qq,"Reply-To: "); + if (!quote2("ed,confirm.s)) die_nomem(); + qmail_put(&qq,quoted.s,quoted.len); + qmail_puts(&qq,"\n"); + if (!stralloc_0(&confirm)) die_nomem(); + + qmail_puts(&qq,"Subject: "); + if (*act == ACTION_SC[0] || *act == ACTION_UC[0]) + qmail_puts(&qq,TXT_USRCONFIRM); + else + qmail_puts(&qq,TXT_MODCONFIRM); + if (*act == ACTION_SC[0] || *act == ACTION_TC[0]) + qmail_puts(&qq,TXT_SUBSCRIBE_TO); + else + qmail_puts(&qq,TXT_UNSUBSCRIBE_FROM); + if (!quote("ed,&outlocal)) die_nomem(); + qmail_put(&qq,quoted.s,quoted.len); + qmail_puts(&qq,"@"); + qmail_put(&qq,outhost.s,outhost.len); + qmail_puts(&qq,"\n"); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); +} + +void sendtomods() +{ + putsubs(moddir.s,0L,52L,subto,1,FATAL); +} + +void copybottom() +{ + if (flagbottom || act == AC_HELP) { + copy(&qq,"text/bottom",flagcd,FATAL); + if (flagcd) { + if (flagcd == 'B') { + encodeB("",0,&line,2,FATAL); /* flush */ + qmail_put(&qq,line.s,line.len); + } + qmail_puts(&qq,"\n--"); + qmail_put(&qq,boundary,COOKIE); + qmail_puts(&qq,"\nContent-Type: message/rfc822"); + qmail_puts(&qq,"\nContent-Disposition: inline; filename=request.msg\n\n"); + } + qmail_puts(&qq,"Return-Path: <"); + if (!quote2("ed,sender)) die_nomem(); + qmail_put(&qq,quoted.s,quoted.len); + qmail_puts(&qq,">\n"); + if (seek_begin(0) == -1) + strerr_die2sys(111,FATAL,ERR_SEEK_INPUT); + if (substdio_copy(&ssqq,&ssin2) != 0) + strerr_die2sys(111,FATAL,ERR_READ_INPUT); + if (flagcd) { + qmail_puts(&qq,"\n--"); + qmail_put(&qq,boundary,COOKIE); + qmail_puts(&qq,"--\n"); + } + } else { + if (flagcd == 'B') { + encodeB("",0,&line,2,FATAL); /* flush even if no bottom */ + qmail_put(&qq,line.s,line.len); + } + } + + qmail_from(&qq,from.s); +} + +int main(argc,argv) int argc; char **argv; { - char *dir; - char *sender; - char *host; char *local; + char *def; char *action; + char *x, *y; + char *fname; + char *pmod; + char *err; + char *cp,*cpfirst,*cplast,*cpnext,*cpafter; + int flagmod; + int flagremote; + int flagpublic; + int opt,r; + unsigned int i; + unsigned int len; int fd; - int i; - int flagconfirm; - int flaghashok; - int flaggoodfield; - int match; + int flagdone; + register char ch; - umask(022); + (void) umask(022); sig_pipeignore(); when = now(); - dir = argv[1]; + while ((opt = getopt(argc,argv,"bBcCdDeEfFlLmMnNqQsSuUvV")) != opteof) + switch(opt) { + case 'b': flagbottom = 1; break; + case 'B': flagbottom = 0; break; + case 'c': flagget = 1; break; + case 'C': flagget = 0; break; + case 'd': + case 'e': flagedit = 1; break; + case 'D': + case 'E': flagedit = 0; break; + case 'f': flagstorefrom = 1; break; + case 'F': flagstorefrom = 0; break; + case 'l': flaglist = 1; break; + case 'L': flaglist = 0; break; + case 'm': flagunsubismod = 1; break; + case 'M': flagunsubismod = 0; break; + case 'n': flagnotify = 1; break; + case 'N': flagnotify = 0; break; + case 's': flagsubconf = 1; break; + case 'S': flagsubconf = 0; break; + case 'q': flagverbose = 0; break; + case 'Q': flagverbose++; break; + case 'u': flagunsubconf = 1; break; + case 'U': flagunsubconf = 0; break; + case 'v': + case 'V': strerr_die2x(0, + "ezmlm-manage version: ezmlm-0.53+",EZIDX_VERSION); + default: + die_usage(); + } + + dir = argv[optind]; if (!dir) die_usage(); sender = env_get("SENDER"); - if (!sender) strerr_die2x(100,FATAL,"SENDER not set"); + if (!sender) strerr_die2x(100,FATAL,ERR_NOSENDER); local = env_get("LOCAL"); - if (!local) strerr_die2x(100,FATAL,"LOCAL not set"); - host = env_get("HOST"); - if (!host) strerr_die2x(100,FATAL,"HOST not set"); + if (!local) strerr_die2x(100,FATAL,ERR_NOLOCAL); + def = env_get("DEFAULT"); if (!*sender) - strerr_die2x(100,FATAL,"I don't reply to bounce messages (#5.7.2)"); + strerr_die2x(100,FATAL,ERR_BOUNCE); if (!sender[str_chr(sender,'@')]) - strerr_die2x(100,FATAL,"I don't reply to senders without host names (#5.7.2)"); + strerr_die2x(100,FATAL,ERR_ANONYMOUS); if (str_equal(sender,"#@[]")) - strerr_die2x(100,FATAL,"I don't reply to bounce messages (#5.7.2)"); + strerr_die2x(100,FATAL,ERR_BOUNCE); if (chdir(dir) == -1) - strerr_die4sys(111,FATAL,"unable to switch to ",dir,": "); + strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": "); switch(slurp("key",&key,32)) { case -1: - strerr_die4sys(111,FATAL,"unable to read ",dir,"/key: "); + strerr_die4sys(111,FATAL,ERR_READ,dir,"/key: "); case 0: - strerr_die3x(100,FATAL,dir,"/key does not exist"); + strerr_die4x(100,FATAL,dir,"/key",ERR_NOEXIST); } getconf_line(&mailinglist,"mailinglist",1,FATAL,dir); - getconf_line(&inhost,"inhost",1,FATAL,dir); - getconf_line(&inlocal,"inlocal",1,FATAL,dir); getconf_line(&outhost,"outhost",1,FATAL,dir); getconf_line(&outlocal,"outlocal",1,FATAL,dir); + set_cpouthost(&outhost); + if (getconf_line(&charset,"charset",0,FATAL,dir)) { + if (charset.len >= 2 && charset.s[charset.len - 2] == ':') { + if (charset.s[charset.len - 1] == 'B' || + charset.s[charset.len - 1] == 'Q') { + flagcd = charset.s[charset.len - 1]; + charset.s[charset.len - 2] = '\0'; + } + } + } else + if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem(); + if (!stralloc_0(&charset)) die_nomem(); - if (inhost.len != str_len(host)) die_badaddr(); - if (case_diffb(inhost.s,inhost.len,host)) die_badaddr(); - if (inlocal.len > str_len(local)) die_badaddr(); - if (case_diffb(inlocal.s,inlocal.len,local)) die_badaddr(); + if (def) /* qmail-1.02 */ + action = def; /* .qmail-list-default */ + 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 (*(action++) != '-') die_badaddr(); + /* has to be '-' to match link. Check anyway */ + } - action = local + inlocal.len; + if (!stralloc_copys(&ddir,dir)) die_nomem(); - switch(slurp("public",&line,1)) { - case -1: - strerr_die4sys(111,FATAL,"unable to read ",dir,"/public: "); - case 0: - strerr_die2x(100,FATAL,"sorry, I've been told to reject all requests (#5.7.2)"); + if (case_starts(action,"digest")) { /* digest */ + action += 6; + if (!stralloc_cats(&outlocal,"-digest")) die_nomem(); + if (!stralloc_cats(&ddir,"/digest")) die_nomem(); + flagdig = FLD_DIGEST; + } else if (case_starts(action,ACTION_ALLOW)) { /* allow */ + action += str_len(ACTION_ALLOW); + if (!stralloc_append(&outlocal,"-")) die_nomem(); + if (!stralloc_cats(&outlocal,ACTION_ALLOW)) die_nomem(); + if (!stralloc_cats(&ddir,"/allow")) die_nomem(); + flagdig = FLD_ALLOW; + } else if (case_starts(action,ACTION_DENY)) { /* deny */ + action += str_len(ACTION_DENY); + if (!stralloc_append(&outlocal,"-")) die_nomem(); + if (!stralloc_cats(&outlocal,ACTION_DENY)) die_nomem(); + if (!stralloc_cats(&ddir,"/deny")) die_nomem(); + flagdig = FLD_DENY; } + if (flagdig) /* zap '-' after db specifier */ + if (*(action++) != '-') die_badaddr(); + + if (!stralloc_0(&ddir)) die_nomem(); + workdir = ddir.s; + set_cpoutlocal(&outlocal); if (!stralloc_copys(&target,sender)) die_nomem(); if (action[0]) { - i = 1 + str_chr(action + 1,'-'); + i = str_chr(action,'-'); if (action[i]) { action[i] = 0; if (!stralloc_copys(&target,action + i + 1)) die_nomem(); @@ -207,138 +820,488 @@ char **argv; } } if (!stralloc_0(&target)) die_nomem(); - if (!stralloc_copys(&confirm,"")) die_nomem(); + set_cptarget(target.s); /* for copy() */ + make_verptarget(); - if (qmail_open(&qq) == -1) - strerr_die2sys(111,FATAL,"unable to run qmail-queue: "); + flagmod = getconf_line(&modsub,"modsub",0,FATAL,dir); + flagremote = getconf_line(&remote,"remote",0,FATAL,dir); - qmail_puts(&qq,"Mailing-List: "); - qmail_put(&qq,mailinglist.s,mailinglist.len); - qmail_puts(&qq,"\nDate: "); - datetime_tai(&dt,when); - qmail_put(&qq,date,date822fmt(date,&dt)); - qmail_puts(&qq,"Message-ID: <"); - qmail_put(&qq,strnum,fmt_ulong(strnum,(unsigned long) when)); - qmail_puts(&qq,"."); - qmail_put(&qq,strnum,fmt_ulong(strnum,(unsigned long) getpid())); - qmail_puts(&qq,".ezmlm@"); - qmail_put(&qq,outhost.s,outhost.len); - qmail_puts(&qq,">\nFrom: "); - if (!quote("ed,&outlocal)) die_nomem(); - qmail_put(&qq,quoted.s,quoted.len); - qmail_puts(&qq,"-help@"); - qmail_put(&qq,outhost.s,outhost.len); - qmail_puts(&qq,"\nTo: "); - if (!quote2("ed,target.s)) die_nomem(); - qmail_put(&qq,quoted.s,quoted.len); - qmail_puts(&qq,"\n"); + if (case_equals(action,ACTION_LISTN) || + case_equals(action,ALT_LISTN)) + act = AC_LISTN; + else if (case_equals(action,ACTION_LIST) || + case_equals(action,ALT_LIST)) + act = AC_LIST; + else if (case_starts(action,ACTION_GET) || + case_starts(action,ALT_GET)) + act = AC_GET; + else if (case_equals(action,ACTION_HELP) || + case_equals(action,ALT_HELP)) + act = AC_HELP; + else if (case_starts(action,ACTION_EDIT) || + case_starts(action,ALT_EDIT)) + act = AC_EDIT; + else if (case_starts(action,ACTION_LOG)) + { act = AC_LOG; actlen = str_len(ACTION_LOG); } + else if (case_starts(action,ALT_LOG)) + { act = AC_LOG; actlen = str_len(ALT_LOG); } - flaghashok = 1; - if (str_start(action,"-sc.")) flaghashok = hashok(action); - if (str_start(action,"-uc.")) flaghashok = hashok(action); - - flagconfirm = 0; - if (str_equal(action,"-subscribe")) flagconfirm = 1; - if (str_equal(action,"-unsubscribe")) flagconfirm = 1; - if (!flaghashok) flagconfirm = 1; - - if (flagconfirm) { - strnum[fmt_ulong(strnum,(unsigned long) when)] = 0; - cookie(hash,key.s,key.len,strnum,target.s,action + 1); - if (!stralloc_copy(&confirm,&outlocal)) die_nomem(); - if (!stralloc_cats(&confirm,"-")) die_nomem(); - if (!stralloc_catb(&confirm,action + 1,1)) die_nomem(); - if (!stralloc_cats(&confirm,"c.")) die_nomem(); - if (!stralloc_cats(&confirm,strnum)) die_nomem(); - if (!stralloc_cats(&confirm,".")) die_nomem(); - if (!stralloc_catb(&confirm,hash,COOKIE)) die_nomem(); - if (!stralloc_cats(&confirm,"-")) die_nomem(); - i = str_rchr(target.s,'@'); - if (!stralloc_catb(&confirm,target.s,i)) die_nomem(); - if (target.s[i]) { - if (!stralloc_cats(&confirm,"=")) die_nomem(); - if (!stralloc_cats(&confirm,target.s + i + 1)) die_nomem(); + /* NOTE: act is needed in msg_headers(). */ + /* Yes, this needs to be cleaned up! */ + + if (flagmod || flagremote) { + if (modsub.len && modsub.s[0] == '/') { + if (!stralloc_copy(&moddir,&modsub)) die_nomem(); + } else if (remote.len && remote.s[0] == '/') { + if (!stralloc_copy(&moddir,&remote)) die_nomem(); + } else { + if (!stralloc_copys(&moddir,dir)) die_nomem(); + if (!stralloc_cats(&moddir,"/mod")) die_nomem(); } - if (!stralloc_cats(&confirm,"@")) die_nomem(); - if (!stralloc_cat(&confirm,&outhost)) die_nomem(); - if (!stralloc_0(&confirm)) die_nomem(); + if (!stralloc_0(&moddir)) die_nomem(); + /* for these the reply is 'secret' and goes to sender */ + /* This means that they can be triggered from a SENDER */ + /* that is not a mod, but never send to a non-mod */ + if (act == AC_NONE || flagdig == FLD_DENY) /* None of the above */ + pmod = issub(moddir.s,sender,(char *) 0,FATAL); + /* sender = moderator? */ + else + pmod = issub(moddir.s,target.s,(char *) 0,FATAL); + /* target = moderator? */ + } else + pmod = 0; /* always 0 for non-mod/remote lists */ + /* if DIR/public is missing, we still respond*/ + /* to requests from moderators for remote */ + /* admin and modsub lists. Since pmod */ + /* is false for all non-mod lists, only it */ + /* needs to be tested. */ + if ((flagpublic = slurp("public",&line,1)) == -1) + strerr_die4sys(111,FATAL,ERR_READ,dir,"/public: "); + if (!flagpublic && !(pmod && flagremote) && + !case_equals(action,ACTION_HELP)) + strerr_die2x(100,FATAL,ERR_NOT_PUBLIC); - qmail_puts(&qq,"Reply-To: "); - if (!quote2("ed,confirm.s)) die_nomem(); - qmail_put(&qq,quoted.s,quoted.len); - qmail_puts(&qq,"\n"); + if (flagdig == FLD_DENY) + if (!pmod || !flagremote) /* only mods can do */ + strerr_die1x(100,ERR_NOT_ALLOWED); + + if (act == AC_NONE) { /* none of the above */ + if (case_equals(action,ACTION_SUBSCRIBE) || + case_equals(action,ALT_SUBSCRIBE)) + act = AC_SUBSCRIBE; + else if (case_equals(action,ACTION_UNSUBSCRIBE) + || case_equals(action,ALT_UNSUBSCRIBE)) + act = AC_UNSUBSCRIBE; + else if (str_start(action,ACTION_SC)) act = AC_SC; } - if (!stralloc_0(&confirm)) die_nomem(); - qmail_puts(&qq,"Subject: ezmlm response\n"); + if (!stralloc_copy(&from,&outlocal)) die_nomem(); + if (!stralloc_cats(&from,"-return-@")) die_nomem(); + if (!stralloc_cat(&from,&outhost)) die_nomem(); + if (!stralloc_0(&from)) die_nomem(); - if (!stralloc_copys(&mydtline,"Delivered-To: responder for ")) die_nomem(); - if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem(); - if (!stralloc_cats(&mydtline,"@")) die_nomem(); - if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem(); - if (!stralloc_cats(&mydtline,"\n")) die_nomem(); + if (qmail_open(&qq,(stralloc *) 0) == -1) + strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE); + msg_headers(); - qmail_put(&qq,mydtline.s,mydtline.len); + if (act == AC_SUBSCRIBE) { + if (pmod && flagremote) { + doconfirm(ACTION_TC); + copy(&qq,"text/mod-sub-confirm",flagcd,FATAL); + copybottom(); + qmail_to(&qq,pmod); + } else if (flagsubconf) { + doconfirm(ACTION_SC); + copy(&qq,"text/sub-confirm",flagcd,FATAL); + copybottom(); + qmail_to(&qq,target.s); + } else { /* normal subscribe, no confirm */ + r = geton(action); /* should be rarely used. */ + copybottom(); + if (flagnotify) qmail_to(&qq,target.s); + if (r && flagverbose > 1) to_owner(); + } - flaggoodfield = 0; - for (;;) { - if (getln(&ssin,&line,&match,'\n') == -1) - strerr_die2sys(111,FATAL,"unable to read input: "); - if (!match) break; - if (line.len == 1) break; - if ((line.s[0] != ' ') && (line.s[0] != '\t')) { - flaggoodfield = 0; - if (case_startb(line.s,line.len,"mailing-list:")) - strerr_die2x(100,FATAL,"incoming message has Mailing-List (#5.7.2)"); - if (line.len == mydtline.len) - if (byte_equal(line.s,line.len,mydtline.s)) - strerr_die2x(100,FATAL,"this message is looping: it already has my Delivered-To line (#5.4.6)"); - if (case_startb(line.s,line.len,"delivered-to:")) - flaggoodfield = 1; - if (case_startb(line.s,line.len,"received:")) - flaggoodfield = 1; + } else if (act == AC_SC) { + if (hashok(action,ACTION_SC)) { + if (flagmod && !(pmod && str_equal(sender,target.s))) { + store_from(&fromline,target.s); /* save from line, if requested */ + /* since transaction not complete */ + doconfirm(ACTION_TC); + copy(&qq,"text/mod-sub-confirm",flagcd,FATAL); + copybottom(); + sendtomods(); + } else { + r = geton(action); + copybottom(); + qmail_to(&qq,target.s); + if (r && flagverbose > 1) to_owner(); + } + } else { + doconfirm(ACTION_SC); + copy(&qq,"text/sub-bad",flagcd,FATAL); + copybottom(); + qmail_to(&qq,target.s); } - if (flaggoodfield) - qmail_put(&qq,line.s,line.len); - } - if (seek_begin(0) == -1) - strerr_die2sys(111,FATAL,"unable to seek input: "); - qmail_puts(&qq,"\n"); - copy("text/top"); - if (str_equal(action,"-subscribe")) - copy("text/sub-confirm"); - else if (str_equal(action,"-unsubscribe")) - copy("text/unsub-confirm"); - else if (str_start(action,"-sc.")) { - if (!flaghashok) - copy("text/sub-bad"); - else - switch(subscribe(target.s,1)) { - case -1: strerr_die1(111,FATAL,&subscribe_err); - case -2: strerr_die1(100,FATAL,&subscribe_err); - case 1: log("+",target.s); copy("text/sub-ok"); break; - default: copy("text/sub-nop"); break; + } else if (str_start(action,ACTION_TC)) { + if (hashok(action,ACTION_TC)) { + r = geton(action); + mod_bottom(); + if (flagnotify) qmail_to(&qq,target.s); /* unless suppressed */ + if (r && flagverbose > 1) to_owner(); + } else { + if (!pmod || !flagremote) /* else anyone can get a good -tc. */ + die_cookie(); + doconfirm(ACTION_TC); + copy(&qq,"text/sub-bad",flagcd,FATAL); + copybottom(); + qmail_to(&qq,pmod); + } + + } else if (act == AC_UNSUBSCRIBE) { + if (flagunsubconf) { + if (pmod && flagremote) { + doconfirm(ACTION_VC); + copy(&qq,"text/mod-unsub-confirm",flagcd,FATAL); + copybottom(); + qmail_to(&qq,pmod); + } else { + doconfirm(ACTION_UC); + copy(&qq,"text/unsub-confirm",flagcd,FATAL); + copybottom(); + qmail_to(&qq,target.s); } - } - else if (str_start(action,"-uc.")) { - if (!flaghashok) - copy("text/unsub-bad"); - else - switch(subscribe(target.s,0)) { - case -1: strerr_die1(111,FATAL,&subscribe_err); - case -2: strerr_die1(100,FATAL,&subscribe_err); - case 1: log("-",target.s); copy("text/unsub-ok"); break; - default: copy("text/unsub-nop"); break; + } else if (flagunsubismod && flagmod) { + doconfirm(ACTION_VC); + copy(&qq,"text/mod-unsub-confirm",flagcd,FATAL); + copybottom(); + sendtomods(); + } else { + r = getoff(action); + copybottom(); + if (!r || flagnotify) qmail_to(&qq,target.s); + /* tell owner if problems (-Q) or anyway (-QQ) */ + if (flagverbose && (!r || flagverbose > 1)) to_owner(); + } + + } else if (str_start(action,ACTION_UC)) { + if (hashok(action,ACTION_UC)) { + /* unsub is moderated only on moderated list if -m unless the */ + /* target == sender == a moderator */ + if (flagunsubismod && flagmod) { + doconfirm(ACTION_VC); + copy(&qq,"text/mod-unsub-confirm",flagcd,FATAL); + copybottom(); + sendtomods(); + } else { + r = getoff(action); + copybottom(); + if (!r || flagnotify) qmail_to(&qq,target.s); + /* tell owner if problems (-Q) or anyway (-QQ) */ + if (flagverbose && (!r || flagverbose > 1)) to_owner(); } - } - else if (str_start(action,"-get.")) { + } else { + doconfirm(ACTION_UC); + copy(&qq,"text/unsub-bad",flagcd,FATAL); + copybottom(); + qmail_to(&qq,target.s); + } + + } else if (str_start(action,ACTION_VC)) { + if (hashok(action,ACTION_VC)) { + r = getoff(action); + if (!r && flagmod) + strerr_die2x(0,INFO,ERR_UNSUB_NOP); + mod_bottom(); + if (r) { /* success to target */ + qmail_to(&qq,target.s); + if (flagverbose > 1) to_owner(); + } else /* NOP to sender = admin. Will take */ + qmail_to(&qq,sender); /* care of it. No need to tell owner */ + /* if list is moderated skip - otherwise bad with > 1 mod */ + } else { + if (!pmod || !flagremote) /* else anyone can get a good -vc. */ + die_cookie(); + doconfirm(ACTION_VC); + copy(&qq,"text/unsub-bad",flagcd,FATAL); + copybottom(); + qmail_to(&qq,pmod); + } + + } else if (act == AC_LIST || act == AC_LISTN) { + + if (!flaglist || (!flagmod && !flagremote)) + strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE); + if (!pmod) + strerr_die2x(100,FATAL,ERR_NOT_ALLOWED); + qmail_puts(&qq,TXT_EZMLM_RESPONSE); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + + if (act == AC_LIST) { + (void) code_qput(TXT_LISTMEMBERS,str_len(TXT_LISTMEMBERS)); + i = putsubs(workdir,0L,52L,code_subto,1,FATAL); + } else /* listn */ + i = putsubs(workdir,0L,52L,dummy_to,1,FATAL); + + (void) code_qput("\n ======> ",11); + (void) code_qput(strnum,fmt_ulong(strnum,i)); + (void) code_qput("\n",1); + copybottom(); + qmail_to(&qq,pmod); + + } else if (act == AC_LOG) { + action += actlen; + if (*action == '.' || *action == '_') ++action; + if (!flaglist || !flagremote) + strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE); + if (!pmod) + strerr_die2x(100,FATAL,ERR_NOT_ALLOWED); + qmail_puts(&qq,TXT_EZMLM_RESPONSE); + transferenc(); + searchlog(workdir,action,code_subto,FATAL); + copybottom(); + qmail_to(&qq,pmod); + + } else if (act == AC_EDIT) { + /* only remote admins and only if -e is specified may edit */ + if (!flagedit || !flagremote) + strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE); + if (!pmod) + strerr_die2x(100,FATAL,ERR_NOT_ALLOWED); + len = str_len(ACTION_EDIT); + if (!case_starts(action,ACTION_EDIT)) + len = str_len(ALT_EDIT); + if (action[len]) { /* -edit.file, not just -edit */ + if (action[len] != '.') + strerr_die2x(100,FATAL,ERR_BAD_REQUEST); + if (!stralloc_copys(&fnedit,"text/")) die_nomem(); + if (!stralloc_cats(&fnedit,action+len+1)) die_nomem(); + if (!stralloc_0(&fnedit)) die_nomem(); + case_lowerb(fnedit.s,fnedit.len); + i = 5; /* after the "text/" */ + while ((ch = fnedit.s[i++])) { + if (((ch > 'z') || (ch < 'a')) && (ch != '_')) + strerr_die2x(100,FATAL,ERR_BAD_NAME); + if (ch == '_') fnedit.s[i-1] = '-'; + } + switch(slurp(fnedit.s,&text,1024)) { /* entire file! */ + case -1: + strerr_die6sys(111,FATAL,ERR_READ,dir,"/",fnedit.s,": "); + case 0: + strerr_die5x(100,FATAL,dir,"/",fnedit.s,ERR_NOEXIST); + } + if (!stralloc_copy(&line,&text)) die_nomem(); + { /* get rid of nulls to use cookie */ + register char *s; register unsigned int n; + s = line.s; n = line.len; + while(n--) { if (!*s) *s = '_'; ++s; } + } + if (!stralloc_cat(&line,&fnedit)) die_nomem(); /* including '\0' */ + strnum[fmt_ulong(strnum,(unsigned long) when)] = 0; + cookie(hash,key.s,key.len,strnum,line.s,"-e"); + if (!stralloc_copy(&confirm,&outlocal)) die_nomem(); + if (!stralloc_append(&confirm,"-")) die_nomem(); + if (!stralloc_catb(&confirm,ACTION_ED,LENGTH_ED)) die_nomem(); + if (!stralloc_cats(&confirm,strnum)) die_nomem(); + if (!stralloc_append(&confirm,".")) die_nomem(); + /* action part has been checked for bad chars */ + if (!stralloc_cats(&confirm,action + len + 1)) die_nomem(); + if (!stralloc_append(&confirm,".")) die_nomem(); + if (!stralloc_catb(&confirm,hash,COOKIE)) die_nomem(); + if (!stralloc_append(&confirm,"@")) die_nomem(); + if (!stralloc_cat(&confirm,&outhost)) die_nomem(); + if (!stralloc_0(&confirm)) die_nomem(); + set_cpconfirm(confirm.s); + + qmail_puts(&qq,"Reply-To: "); + if (!quote2("ed,confirm.s)) die_nomem(); + qmail_put(&qq,quoted.s,quoted.len); + qmail_puts(&qq,"\n"); + if (!stralloc_0(&confirm)) die_nomem(); + + qmail_puts(&qq,TXT_EDIT_RESPONSE); + qmail_puts(&qq,action+len+1); /* has the '_' not '-' */ + qmail_puts(&qq,TXT_EDIT_FOR); + if (!quote("ed,&outlocal)) die_nomem(); + qmail_put(&qq,quoted.s,quoted.len); + qmail_puts(&qq,"@"); + qmail_put(&qq,outhost.s,outhost.len); + qmail_puts(&qq,"\n"); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/edit-do",flagcd,FATAL); + (void) code_qput(TXT_EDIT_START,str_len(TXT_EDIT_START)); + (void) code_qput("\n",1); + (void) code_qput(text.s,text.len); + (void) code_qput(TXT_EDIT_END,str_len(TXT_EDIT_END)); + (void) code_qput("\n",1); + + } else { /* -edit only, so output list of editable files */ + qmail_puts(&qq,TXT_EDIT_LIST); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/edit-list",flagcd,FATAL); + } + qmail_puts(&qq,"\n\n"); + copybottom(); + qmail_to(&qq,pmod); + + } else if (str_start(action,ACTION_ED)) { + datetime_sec u; + int flaggoodfield; + x = action + LENGTH_ED; + x += scan_ulong(x,&u); + if ((u > when) || (u < when - 100000)) die_cookie(); + if (*x == '.') ++x; + fname = x; + x += str_chr(x,'.'); + if (!*x) die_cookie(); + *x = (char) 0; + ++x; + if (!stralloc_copys(&fnedit,"text/")) die_nomem(); + if (!stralloc_cats(&fnedit,fname)) die_nomem(); + if (!stralloc_0(&fnedit)) die_nomem(); + y = fnedit.s + 5; /* after "text/" */ + while (*++y) { /* Name should be guaranteed by the cookie, */ + /* but better safe than sorry ... */ + if (((*y > 'z') || (*y < 'a')) && (*y != '_')) + strerr_die2x(100,FATAL,ERR_BAD_NAME); + if (*y == '_') *y = '-'; + } + + lock(); /* file must not change while here */ + + switch (slurp(fnedit.s,&text,1024)) { + case -1: + strerr_die6sys(111,FATAL,ERR_READ,dir,"/",fnedit.s,": "); + case 0: + strerr_die5x(100,FATAL,dir,"/",fnedit.s,ERR_NOEXIST); + } + if (!stralloc_copy(&line,&text)) die_nomem(); + { /* get rid of nulls to use cookie */ + register char *s; register unsigned int n; + s = line.s; n = line.len; + while(n--) { if (!*s) *s = '_'; ++s; } + } + if (!stralloc_cat(&line,&fnedit)) die_nomem(); /* including '\0' */ + strnum[fmt_ulong(strnum,(unsigned long) u)] = 0; + cookie(hash,key.s,key.len,strnum,line.s,"-e"); + if (str_len(x) != COOKIE) die_cookie(); + if (byte_diff(hash,COOKIE,x)) die_cookie(); + /* cookie is ok, file exists, lock's on, new file ends in '_' */ + if (!stralloc_copys(&fneditn,fnedit.s)) die_nomem(); + if (!stralloc_append(&fneditn,"_")) die_nomem(); + if (!stralloc_0(&fneditn)) die_nomem(); + fd = open_trunc(fneditn.s); + if (fd == -1) + strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fneditn.s,": "); + substdio_fdbuf(&sstext,write,fd,textbuf,sizeof(textbuf)); + if (!stralloc_copys("ed,"")) die_nomem(); /* clear */ + if (!stralloc_copys(&text,"")) die_nomem(); + + for (;;) { /* get message body */ + if (getln(&ssin,&line,&match,'\n') == -1) + strerr_die2sys(111,FATAL,ERR_READ_INPUT); + if (!match) break; + if (!stralloc_cat(&text,&line)) die_nomem(); + } + if (encin) { /* decode if necessary */ + if (encin == 'B') + decodeB(text.s,text.len,&line,FATAL); + else + decodeQ(text.s,text.len,&line,FATAL); + if (!stralloc_copy(&text,&line)) die_nomem(); + } + cp = text.s; + cpafter = text.s+text.len; + flaggoodfield = 0; + flagdone = 0; + len = 0; + while ((cpnext = cp + byte_chr(cp,cpafter-cp,'\n')) != cpafter) { + i = byte_chr(cp,cpnext-cp,'%'); + if (i != (unsigned int) (cpnext - cp)) { + if (!flaggoodfield) { /* TXT_EDIT_START/END */ + if (case_startb(cp+i,cpnext-cp-i,TXT_EDIT_START)) { + /* start tag. Store users 'quote characters', e.g. '> ' */ + if (!stralloc_copyb("ed,cp,i)) die_nomem(); + flaggoodfield = 1; + cp = cpnext + 1; + cpfirst = cp; + continue; + } + } else + if (case_startb(cp+i,cpnext-cp-i,TXT_EDIT_END)) { + flagdone = 1; + break; + } + } + if (flaggoodfield) { + if ((len += cpnext - cp - quoted.len + 1) > MAXEDIT) + strerr_die1x(100,ERR_EDSIZE); + + if (quoted.len && cpnext-cp >= (int) quoted.len && + !str_diffn(cp,quoted.s,quoted.len)) + cp += quoted.len; /* skip quoting characters */ + cplast = cpnext - 1; + if (*cplast == '\r') /* CRLF -> '\n' for base64 encoding */ + *cplast = '\n'; + else + ++cplast; + if (substdio_put(&sstext,cp,cplast-cp+1) == -1) + strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fneditn.s,": "); + } + cp = cpnext + 1; + } + if (!flagdone) + strerr_die2x(100,FATAL,ERR_NO_MARK); + if (substdio_flush(&sstext) == -1) + strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fneditn.s,": "); + if (fsync(fd) == -1) + strerr_die6sys(111,FATAL,ERR_SYNC,dir,"/",fneditn.s,": "); + if (fchmod(fd, 0600) == -1) + strerr_die6sys(111,FATAL,ERR_CHMOD,dir,"/",fneditn.s,": "); + if (close(fd) == -1) + strerr_die6sys(111,FATAL,ERR_CLOSE,dir,"/",fneditn.s,": "); + if (rename(fneditn.s,fnedit.s) == -1) + strerr_die6sys(111,FATAL,ERR_MOVE,dir,"/",fneditn.s,": "); + + unlock(); + qmail_puts(&qq,TXT_EDIT_SUCCESS); + qmail_puts(&qq,fname); + qmail_puts(&qq,TXT_EDIT_FOR); + if (!quote("ed,&outlocal)) die_nomem(); + qmail_put(&qq,quoted.s,quoted.len); + qmail_puts(&qq,"@"); + qmail_put(&qq,outhost.s,outhost.len); + qmail_puts(&qq,"\n"); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/edit-done",flagcd,FATAL); + copybottom(); + qmail_to(&qq,sender); /* not necessarily from mod */ + + } else if (act == AC_GET) { + unsigned long u; struct stat st; char ch; int r; + unsigned int pos; + + if (!flagget) + strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE); + qmail_puts(&qq,TXT_EZMLM_RESPONSE); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + + pos = str_len(ACTION_GET); + if (!case_starts(action,ACTION_GET)) + pos = str_len(ALT_GET); - scan_ulong(action + 5,&u); + if (action[pos] == '.' || action [pos] == '_') pos++; + scan_ulong(action + pos,&u); if (!stralloc_copys(&line,"archive/")) die_nomem(); if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,u / 100))) die_nomem(); @@ -349,20 +1312,20 @@ char **argv; fd = open_read(line.s); if (fd == -1) if (errno != error_noent) - strerr_die4sys(111,FATAL,"unable to open ",line.s,": "); + strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": "); else - copy("text/get-bad"); + copy(&qq,"text/get-bad",flagcd,FATAL); else { if (fstat(fd,&st) == -1) - copy("text/get-bad"); + copy(&qq,"text/get-bad",flagcd,FATAL); else if (!(st.st_mode & 0100)) - copy("text/get-bad"); + copy(&qq,"text/get-bad",flagcd,FATAL); else { substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf)); qmail_puts(&qq,"> "); for (;;) { r = substdio_get(&sstext,&ch,1); - if (r == -1) strerr_die4sys(111,FATAL,"unable to read ",line.s,": "); + if (r == -1) strerr_die4sys(111,FATAL,ERR_READ,line.s,": "); if (r == 0) break; qmail_put(&qq,&ch,1); if (ch == '\n') qmail_puts(&qq,"> "); @@ -371,33 +1334,72 @@ char **argv; } close(fd); } - } - else - copy("text/help"); + copybottom(); + qmail_to(&qq,target.s); - copy("text/bottom"); + } else if (case_starts(action,ACTION_QUERY) || + case_starts(action,ALT_QUERY)) { + qmail_puts(&qq,TXT_EZMLM_RESPONSE); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + if (pmod) { /* pmod points to static storage in issub(). Need to do this */ + /* before calling issub() again */ + if (!stralloc_copys(&to,pmod)) die_nomem(); + if (!stralloc_0(&to)) die_nomem(); + } else { + if (!stralloc_copy(&to,&target)) die_nomem(); + } + if (issub(workdir,target.s,(char *) 0,FATAL)) + copy(&qq,"text/sub-nop",flagcd,FATAL); + else + copy(&qq,"text/unsub-nop",flagcd,FATAL); + copybottom(); + qmail_to(&qq,to.s); - qmail_puts(&qq,"Return-Path: <"); - if (!quote2("ed,sender)) die_nomem(); - qmail_put(&qq,quoted.s,quoted.len); - qmail_puts(&qq,">\n"); - if (substdio_copy(&ssqq,&ssin2) != 0) - strerr_die2sys(111,FATAL,"unable to read input: "); + } else if (case_starts(action,ACTION_INFO) || + case_starts(action,ALT_INFO)) { + qmail_puts(&qq,TXT_EZMLM_RESPONSE); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/info",flagcd,FATAL); + copybottom(); + qmail_to(&qq,target.s); - if (!stralloc_copy(&line,&outlocal)) die_nomem(); - if (!stralloc_cats(&line,"-return-@")) die_nomem(); - if (!stralloc_cat(&line,&outhost)) die_nomem(); - if (!stralloc_0(&line)) die_nomem(); - qmail_from(&qq,line.s); + } else if (case_starts(action,ACTION_FAQ) || + case_starts(action,ALT_FAQ)) { + qmail_puts(&qq,TXT_EZMLM_RESPONSE); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/faq",flagcd,FATAL); + copybottom(); + qmail_to(&qq,target.s); - qmail_to(&qq,target.s); + } else if (pmod && (act == AC_HELP)) { + qmail_puts(&qq,TXT_EZMLM_RESPONSE); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/mod-help",flagcd,FATAL); + copy(&qq,"text/help",flagcd,FATAL); + copybottom(); + qmail_to(&qq,pmod); - switch(qmail_close(&qq)) { - case 0: + } else { + act = AC_HELP; + qmail_puts(&qq,TXT_EZMLM_RESPONSE); + transferenc(); + copy(&qq,"text/top",flagcd,FATAL); + copy(&qq,"text/help",flagcd,FATAL); + copybottom(); + qmail_to(&qq,sender); + } + + if (*(err = qmail_close(&qq)) == '\0') { strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0; + closesql(); strerr_die2x(0,"ezmlm-manage: info: qp ",strnum); - default: - /* don't worry about undoing actions; everything is idempotent */ - strerr_die2x(111,FATAL,"temporary qmail-queue error"); + } else { + closesql(); + strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1); } } +