+/*$Id: ezmlm-manage.c,v 1.86 1999/12/23 02:43:55 lindberg Exp $*/
+/*$Name: ezmlm-idx-040 $*/
+
#include <sys/types.h>
#include <sys/stat.h>
#include "error.h"
#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};
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;
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: <mailto:"); /* General rfc2369 headers */
+ qmail_put(&qq,quoted.s,quoted.len);
+ qmail_puts(&qq,"-help@");
+ qmail_put(&qq,outhost.s,outhost.len);
+ qmail_puts(&qq,">\nList-Post: <mailto:");
+ qmail_put(&qq,quoted.s,quoted.len);
+ qmail_puts(&qq,"@");
+ qmail_put(&qq,outhost.s,outhost.len);
+ qmail_puts(&qq,">\nList-Subscribe: <mailto:");
+ qmail_put(&qq,quoted.s,quoted.len);
+ qmail_puts(&qq,"-subscribe@");
+ qmail_put(&qq,outhost.s,outhost.len);
+ qmail_puts(&qq,">\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: <mailto:"); /*rfc2369 */
+ qmail_put(&qq,outlocal.s,outlocal.len);
+ qmail_puts(&qq,"-unsubscribe-");
+ /* url-encode since verptarget is controlled by sender */
+ /* note &verptarget ends in '\0', hence len - 1! */
+ for (i = 0; i < verptarget.len - 1; i++) {
+ ch = verptarget.s[i];
+ if (str_chr("\"?;<>&/:%+#",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();
}
}
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();
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,"> ");
}
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);
}
}
+