X-Git-Url: https://git.distorted.org.uk/~mdw/ezmlm/blobdiff_plain/5b62e993b0af39700031c2875d7f6654e6a02850..f8beb284087c279acfb30506f5bb32baa4949b44:/ezmlm-cron.c diff --git a/ezmlm-cron.c b/ezmlm-cron.c new file mode 100644 index 0000000..e35f7ea --- /dev/null +++ b/ezmlm-cron.c @@ -0,0 +1,507 @@ +#include +#include +#include "strerr.h" +#include "stralloc.h" +#include "sgetopt.h" +#include "substdio.h" +#include "error.h" +#include "str.h" +#include "fmt.h" +#include "fork.h" +#include "wait.h" +#include "readwrite.h" +#include "auto_qmail.h" +#include "auto_cron.h" +#include "errtxt.h" +#include "idx.h" + +#define FATAL "ezmlm-cron: fatal: " + +void die_usage() +{ + strerr_die2x(100,FATAL, + "usage: ezmlm-cron [-cCdDlLvV] [-w dow] [-t hh:mm] [-i hrs] listadr code"); +} + +void die_dow() +{ + strerr_die2x(100,FATAL,ERR_DOW); +} + +void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); } + +unsigned long deltah = 24L; /* default interval 24h */ +unsigned long hh = 4L; /* default time 04:12 */ +unsigned long mm = 12L; +char *dow = "*"; /* day of week */ +char *qmail_inject = "/bin/qmail-inject "; +char strnum[FMT_ULONG]; +unsigned long uid,euid; + +stralloc line = {0}; +stralloc rp = {0}; +stralloc addr = {0}; +stralloc user = {0}; +stralloc euser = {0}; +stralloc dir = {0}; +stralloc listaddr = {0}; + +struct passwd *ppasswd; + +int opt,match; +int hostmatch; +int localmatch; +unsigned long dh,t; +int founduser = 0; +int listmatch = 0; +int flagconfig = 0; +int flagdelete = 0; +int flaglist = 0; +int flagdigit = 0; +int flagours; +int foundlocal; +int foundmatch = 0; +int nolists = 0; +int maxlists; +unsigned int pos,pos2,poslocal,len; +unsigned int lenhost,lenlocal; +unsigned int part0start,part0len; +int fdlock,fdin,fdout; + +char *local = (char *) 0; /* list = local@host */ +char *host = (char *) 0; +char *code = (char *) 0; /* digest code */ +char *cp; + +void die_syntax() +{ + if (!stralloc_0(&line)) die_nomem(); + strerr_die5x(100,FATAL,TXT_EZCRONRC," ",ERR_SYNTAX,line.s); +} + +void die_argument() +{ + strerr_die2x(100,FATAL,ERR_NOT_CLEAN); +} + +int isclean(addr,flagaddr) + /* assures that addr has only letters, digits, "-_" */ + /* also checks allows single '@' if flagaddr = 1 */ + /* returns 1 if clean, 0 otherwise */ + char *addr; + int flagaddr; /* 1 for addresses with '@', 0 for other args */ +{ + unsigned int pos; + register char ch; + register char *cp; + if (flagaddr) { /* shoud have one '@' */ + pos = str_chr(addr,'@'); + if (!pos || !addr[pos]) + return 0; /* at least 1 char for local */ + if (!addr[pos+1]) + return 0; /* host must be at least 1 char */ + pos++; + case_lowerb(addr+pos,str_len(addr)-pos); + } else + pos = 0; + pos += str_chr(addr + pos,'@'); + if (addr[pos]) /* but no more */ + return 0; + cp = addr; + while ((ch = *(cp++))) + if (!(ch >= 'a' && ch <= 'z') && + !(ch >= 'A' && ch <= 'Z') && + !(ch >= '0' && ch <= '9') && + ch != '.' && ch != '-' && ch != '_' && ch != '@') + return 0; + return 1; +} + +char inbuf[512]; +substdio ssin; + +char outbuf[512]; +substdio ssout; + +void main(argc,argv) +int argc; +char **argv; + +{ + int child; + char *sendargs[4]; + int wstat; + + (void) umask(077); + sig_pipeignore(); + + while ((opt = getopt(argc,argv,"cCdDi:lLt:w:vV")) != opteof) + switch (opt) { + case 'c': flagconfig = 1; break; + case 'C': flagconfig = 0; break; + case 'd': flagdelete = 1; break; + case 'D': flagdelete = 0; break; + case 'i': scan_ulong(optarg,&deltah); break; + case 'l': flaglist = 1; break; + case 'L': flaglist = 0; break; + case 't': + pos = scan_ulong(optarg,&hh); + if (!optarg[pos++] == ':') die_usage(); + pos = scan_ulong(optarg + pos,&mm); + break; + case 'w': + dow = optarg; + cp = optarg - 1; + while (*(++cp)) { + if (*cp >= '0' && *cp <= '7') { + if (flagdigit) die_dow(); + flagdigit = 1; + } else if (*cp == ',') { + if (!flagdigit) die_dow(); + flagdigit = 0; + } else + die_dow(); + } + break; + case 'v': + case 'V': strerr_die2x(100,"ezmlm-cron version: ",EZIDX_VERSION); + default: + die_usage(); + } + if (flaglist + flagdelete + flagconfig > 1) + strerr_die2x(100,FATAL,ERR_EXCLUSIVE); + uid = getuid(); + if (uid && !(euid = geteuid())) + strerr_die2x(100,FATAL,ERR_SUID); + if (!(ppasswd = getpwuid(uid))) + strerr_die2x(100,FATAL,ERR_UID); + if (!stralloc_copys(&user,ppasswd->pw_name)) die_nomem(); + if (!stralloc_0(&user)) die_nomem(); + if (!(ppasswd = getpwuid(euid))) + strerr_die2x(100,FATAL,ERR_EUID); + if (!stralloc_copys(&dir.s,ppasswd->pw_dir)) die_nomem(); + if (!stralloc_0(&dir)) die_nomem(); + if (!stralloc_copys(&euser,ppasswd->pw_name)) die_nomem(); + if (!stralloc_0(&euser)) die_nomem(); + + if (chdir(dir.s) == -1) + strerr_die4sys(111,FATAL,ERR_SWITCH,dir.s,": "); + + local = argv[optind++]; /* list address, optional for -c & -l */ + if (!local) { + if (!flagconfig && !flaglist) + die_usage(); + lenlocal = 0; + lenhost = 0; + } else { + if (!stralloc_copys(&listaddr,local)) die_nomem(); + if (!isclean(local,1)) + die_argument(); + pos = str_chr(local,'@'); + lenlocal = pos; + local[pos] = '\0'; + host = local + pos + 1; + lenhost = str_len(host); + code = argv[optind]; + if (!code) { /* ignored for -l, -c, and -d */ + if (flagdelete || flaglist || flagconfig) + /* get away with not putting code for delete */ + code = "a"; /* a hack - so what! */ + else + die_usage(); + } else + if (!isclean(code,0)) + die_argument(); + } + if ((fdin = open_read(TXT_EZCRONRC)) == -1) + strerr_die6sys(111,FATAL,ERR_OPEN,dir.s,"/",TXT_EZCRONRC,": "); + /* first line is special */ + substdio_fdbuf(&ssin,read,fdin,inbuf,sizeof(inbuf)); + if (getln(&ssin,&line,&match,'\n') == -1) + strerr_die6sys(111,FATAL,ERR_READ,dir.s,"/",TXT_EZCRONRC,": "); + + if (!match) + strerr_die6sys(111,FATAL,ERR_READ,dir.s,"/",TXT_EZCRONRC,": "); + /* (since we have match line.len has to be >= 1) */ + line.s[line.len - 1] = '\0'; + if (!isclean(line.s,0)) /* host for bounces */ + strerr_die4x(100,ERR_CFHOST,dir.s,"/",TXT_EZCRONRC); + if (!stralloc_copys(&rp,line.s)) die_nomem(); + + match = 1; + for(;;) { + if (!match) break; /* to allow last line without '\n' */ + if (getln(&ssin,&line,&match,'\n') == -1) + strerr_die6sys(111,FATAL,ERR_READ,dir.s,"/",TXT_EZCRONRC,": "); + if (!line.len) + break; + line.s[line.len-1] = '\0'; + if (!case_startb(line.s,line.len,user.s)) + continue; + pos = user.len - 1; + if (pos >= line.len || line.s[pos] != ':') + continue; + founduser = 1; /* got user line */ + break; + } + close(fdin); + if (!founduser) + strerr_die2x(100,FATAL,ERR_BADUSER); + + if (flagconfig) { + line.s[line.len-1] = '\n'; /* not very elegant ;-) */ + substdio_fdbuf(&ssout,write,1,outbuf,sizeof(outbuf)); + if (substdio_put(&ssout,line.s,line.len) == -1) + strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: "); + if (substdio_flush(&ssout) == -1) + strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: "); + _exit(0); + } + ++pos; /* points to first ':' */ + len = str_chr(line.s+pos,':'); /* second ':' */ + if (!line.s[pos + len]) + die_syntax(); + if (!local) { /* only -d and std left */ + localmatch = 1; + hostmatch = 1; + } else { + hostmatch = 0; + if (len <= str_len(local)) + if (!str_diffn(line.s+pos,local,len)) + localmatch = 1; + } + pos += len + 1; + len = str_chr(line.s + pos,':'); /* third */ + if (!line.s[pos + len]) + die_syntax(); + if (local) { /* check host */ + if (len == 0) /* empty host => any host */ + hostmatch = 1; + else + if (len == str_len(host)) + if (!case_diffb(line.s+pos,len,host)) + hostmatch = 1; + } + pos += len + 1; + pos += scan_ulong(line.s+pos,&maxlists); + if (line.s[pos]) { /* check additional lists */ + if (line.s[pos] != ':') + die_syntax(); + if (line.s[pos+1+str_chr(line.s+pos+1,':')]) + die_syntax(); /* reminder lists are not separated by ':' */ + /* otherwise a ':' or arg miscount will die */ + /* silently */ + if (local) { + while (++pos < line.len) { + len = str_chr(line.s + pos,'@'); + if (len == lenlocal && !str_diffn(line.s + pos,local,len)) { + pos += len; + if (!line.s[pos]) break; + pos++; + len = str_chr(line.s+pos,','); + if (len == lenhost && !case_diffb(line.s+pos,len,host)) { + listmatch = 1; + break; + } + } + pos += len; + } + } + } + if (!listmatch) { + if (!hostmatch) + strerr_die2x(100,FATAL,ERR_BADHOST); + if (!localmatch) + strerr_die2x(100,FATAL,ERR_BADLOCAL); + } + /* assemble correct line */ + if (!flaglist) { + if (!stralloc_copyb(&addr,strnum,fmt_ulong(strnum,mm))) die_nomem(); + if (!stralloc_cats(&addr," ")) die_nomem(); + dh = 0L; + if (deltah <= 3L) dh = deltah; + else if (deltah <= 6L) dh = 6L; + else if (deltah <= 12L) dh = 12L; + else if (deltah <= 24L) dh = 24L; + else if (deltah <= 48L) { + if (dow[0] == '*') dow = "1,3,5"; + } else if (deltah <= 72L) { + if (dow[0] == '*') dow = "1,4"; + } else + if (dow[0] == '*') dow = "1"; + + if (!dh) { + if (!stralloc_cats(&addr,"*")) die_nomem(); + } else { + if (!stralloc_catb(&addr,strnum,fmt_ulong(strnum,hh))) die_nomem(); + for (t = hh + dh; t < hh + 24L; t+=dh) { + if (!stralloc_cats(&addr,",")) die_nomem(); + if (!stralloc_catb(&addr,strnum,fmt_ulong(strnum,t % 24L))) die_nomem(); + } + } + if (!stralloc_cats(&addr," * * ")) die_nomem(); + if (!stralloc_cats(&addr,dow)) die_nomem(); + if (!stralloc_cats(&addr," ")) die_nomem(); + part0start = addr.len; /* /var/qmail/bin/qmail-inject */ + if (!stralloc_cats(&addr,auto_qmail)) die_nomem(); + if (!stralloc_cats(&addr,qmail_inject)) die_nomem(); + part0len = addr.len - part0start; + if (!stralloc_cats(&addr,local)) die_nomem(); + if (!stralloc_cats(&addr,"-dig-")) die_nomem(); + if (!stralloc_cats(&addr,code)) die_nomem(); + if (!stralloc_cats(&addr,"@")) die_nomem(); + if (!stralloc_cats(&addr,host)) die_nomem(); + /* feed 'Return-Path: ' to qmail-inject */ + if (!stralloc_cats(&addr,"%Return-path: <")) die_nomem(); + if (!stralloc_cats(&addr,user.s)) die_nomem(); + if (!stralloc_cats(&addr,"@")) die_nomem(); + if (!stralloc_cat(&addr,&rp)) die_nomem(); + if (!stralloc_cats(&addr,">\n")) die_nomem(); + } + if (!stralloc_0(&addr)) die_nomem(); + + if (!flaglist) { + /* now to rewrite crontab we need to lock */ + fdlock = open_append("crontabl"); + if (fdlock == -1) + strerr_die4sys(111,FATAL,ERR_OPEN,dir.s,"/crontabl: "); + if (lock_ex(fdlock) == -1) { + close(fdlock); + strerr_die4sys(111,FATAL,ERR_OBTAIN,dir.s,"/crontabl: "); + } + } /* if !flaglist */ + if ((fdin = open_read("crontab")) == -1) { + if (errno != error_noent) + strerr_die4sys(111,FATAL,ERR_READ,dir.s,"/crontab: "); + } else + substdio_fdbuf(&ssin,read,fdin,inbuf,sizeof(inbuf)); + if (flaglist) + substdio_fdbuf(&ssout,write,1,outbuf,sizeof(outbuf)); + else { + if ((fdout = open_trunc("crontabn")) == -1) + strerr_die4sys(111,FATAL,ERR_WRITE,dir.s,"/crontabn: "); + substdio_fdbuf(&ssout,write,fdout,outbuf,sizeof(outbuf)); + } + line.len = 0; + + if (fdin != -1) { + for (;;) { + if (!flaglist && line.len) { + line.s[line.len-1] = '\n'; + if (substdio_put(&ssout,line.s,line.len) == -1) + strerr_die4sys(111,FATAL,ERR_WRITE,dir.s,"/crontabn: "); + } + if (getln(&ssin,&line,&match,'\n') == -1) + strerr_die4sys(111,FATAL,ERR_READ,dir.s,"/crontab: "); + if (!match) + break; + flagours = 0; /* assume entry is not ours */ + foundlocal = 0; + line.s[line.len - 1] = '\0'; /* match so at least 1 char */ + pos = 0; + while (line.s[pos] == ' ' && line.s[pos] == '\t') ++pos; + if (line.s[pos] == '#') + continue; /* cron comment */ + pos = str_chr(line.s,'/'); + if (!str_start(line.s+pos,auto_qmail)) continue; + pos += str_len(auto_qmail); + if (!str_start(line.s+pos,qmail_inject)) continue; + pos += str_len(qmail_inject); + poslocal = pos; + pos = byte_rchr(line.s,line.len,'<'); /* should be Return-Path: < */ + if (pos == line.len) + continue; /* not ezmlm-cron line */ + pos++; + len = str_chr(line.s+pos,'@'); + if (len == user.len - 1 && !str_diffn(line.s+pos,user.s,len)) { + flagours = 1; + ++nolists; /* belongs to this user */ + } + if (!local) { + foundlocal = 1; + } else { + pos = poslocal + str_chr(line.s+poslocal,'@'); + if (pos + lenhost +1 >= line.len) continue; + if (case_diffb(line.s+pos+1,lenhost,host)) continue; + if (line.s[pos+lenhost+1] != '%') continue; + /* check local */ + if (poslocal + lenlocal + 5 >= line.len) continue; + if (!str_start(line.s+poslocal,local)) continue; + pos2 = poslocal+lenlocal; + if (!str_start(line.s+pos2,"-dig-")) continue; + foundlocal = 1; + } + if (foundlocal) { + foundmatch = 1; + if (flaglist && (local || flagours)) { + if (substdio_put(&ssout,line.s,line.len) == -1) + strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: "); + if (substdio_put(&ssout,"\n",1) == -1) + strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: "); + } + line.len = 0; /* same - kill line */ + if (flagours) + --nolists; + } + } + close(fdin); + } + if (flaglist) { + if (substdio_flush(&ssout) == -1) + strerr_die3sys(111,FATAL,ERR_FLUSH,"stdout: "); + if (foundmatch) /* means we had a match */ + _exit(0); + else + strerr_die2x(100,FATAL,ERR_NO_MATCH); + } + /* only -d and regular use left */ + + if (nolists >= maxlists && !flagdelete) + strerr_die2x(100,FATAL,ERR_LISTNO); + if (!flagdelete) + if (substdio_put(&ssout,addr.s,addr.len-1) == -1) + strerr_die4sys(111,FATAL,ERR_WRITE,dir.s,"/crontabn: "); + if (flagdelete && !foundlocal) + strerr_die2x(111,FATAL,ERR_NO_MATCH); + if (substdio_flush(&ssout) == -1) + strerr_die4sys(111,FATAL,ERR_FLUSH,dir.s,"/crontabn: "); + if (fsync(fdout) == -1) + strerr_die4sys(111,FATAL,ERR_SYNC,dir.s,"/crontabn++: "); + if (close(fdout) == -1) + strerr_die4sys(111,FATAL,ERR_CLOSE,dir.s,"/crontabn: "); + if (rename("crontabn","crontab") == -1) + strerr_die4sys(111,FATAL,ERR_MOVE,dir.s,"/crontabn: "); + sendargs[0] = "sh"; + sendargs[1] = "-c"; + + if (!stralloc_copys(&line,auto_cron)) die_nomem(); + if (!stralloc_cats(&line,"/crontab '")) die_nomem(); + if (!stralloc_cats(&line,dir.s)) die_nomem(); + if (!stralloc_cats(&line,"/crontab'")) die_nomem(); + if (!stralloc_0(&line)) die_nomem(); + sendargs[2] = line.s; + sendargs[3] = 0; + switch(child = fork()) { + case -1: + strerr_die2sys(111,FATAL,ERR_FORK); + case 0: + if (setreuid(euid,euid) == -1) + strerr_die2sys(100,FATAL,ERR_SETUID); + execvp(*sendargs,sendargs); + if (errno == error_txtbsy || errno == error_nomem || + errno == error_io) + strerr_die4sys(111,FATAL,ERR_EXECUTE,sendargs[2],": "); + else + strerr_die4sys(100,FATAL,ERR_EXECUTE,sendargs[2],": "); + } + /* parent */ + wait_pid(&wstat,child); + if (wait_crashed(wstat)) + strerr_die2x(111,FATAL,ERR_CHILD_CRASHED); + switch(wait_exitcode(wstat)) { + case 0: + _exit(0); + default: + strerr_die2x(111,FATAL,ERR_CRONTAB); + } +}