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