12 #include "readwrite.h"
13 #include "auto_qmail.h"
14 #include "auto_cron.h"
18 #define FATAL "ezmlm-cron: fatal: "
22 strerr_die2x(100,FATAL,
23 "usage: ezmlm-cron [-cCdDlLvV] [-w dow] [-t hh:mm] [-i hrs] listadr code");
28 strerr_die2x(100,FATAL,ERR_DOW);
31 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
33 unsigned long deltah = 24L; /* default interval 24h */
34 unsigned long hh = 4L; /* default time 04:12 */
35 unsigned long mm = 12L;
36 char *dow = "*"; /* day of week */
37 char *qmail_inject = "/usr/sbin/qmail-inject ";
38 char strnum[FMT_ULONG];
39 unsigned long uid,euid;
47 stralloc listaddr = {0};
49 struct passwd *ppasswd;
66 unsigned int pos,pos2,poslocal,len;
67 unsigned int lenhost,lenlocal;
68 unsigned int part0start,part0len;
69 int fdlock,fdin,fdout;
71 char *local = (char *) 0; /* list = local@host */
72 char *host = (char *) 0;
73 char *code = (char *) 0; /* digest code */
78 if (!stralloc_0(&line)) die_nomem();
79 strerr_die5x(100,FATAL,TXT_EZCRONRC," ",ERR_SYNTAX,line.s);
84 strerr_die2x(100,FATAL,ERR_NOT_CLEAN);
87 int isclean(addr,flagaddr)
88 /* assures that addr has only letters, digits, "-_" */
89 /* also checks allows single '@' if flagaddr = 1 */
90 /* returns 1 if clean, 0 otherwise */
92 int flagaddr; /* 1 for addresses with '@', 0 for other args */
97 if (flagaddr) { /* shoud have one '@' */
98 pos = str_chr(addr,'@');
99 if (!pos || !addr[pos])
100 return 0; /* at least 1 char for local */
102 return 0; /* host must be at least 1 char */
104 case_lowerb(addr+pos,str_len(addr)-pos);
107 pos += str_chr(addr + pos,'@');
108 if (addr[pos]) /* but no more */
111 while ((ch = *(cp++)))
112 if (!(ch >= 'a' && ch <= 'z') &&
113 !(ch >= 'A' && ch <= 'Z') &&
114 !(ch >= '0' && ch <= '9') &&
115 ch != '.' && ch != '-' && ch != '_' && ch != '@')
138 while ((opt = getopt(argc,argv,"cCdDi:lLt:w:vV")) != opteof)
140 case 'c': flagconfig = 1; break;
141 case 'C': flagconfig = 0; break;
142 case 'd': flagdelete = 1; break;
143 case 'D': flagdelete = 0; break;
144 case 'i': scan_ulong(optarg,&deltah); break;
145 case 'l': flaglist = 1; break;
146 case 'L': flaglist = 0; break;
148 pos = scan_ulong(optarg,&hh);
149 if (!optarg[pos++] == ':') die_usage();
150 pos = scan_ulong(optarg + pos,&mm);
156 if (*cp >= '0' && *cp <= '7') {
157 if (flagdigit) die_dow();
159 } else if (*cp == ',') {
160 if (!flagdigit) die_dow();
167 case 'V': strerr_die2x(100,"ezmlm-cron version: ",EZIDX_VERSION);
171 if (flaglist + flagdelete + flagconfig > 1)
172 strerr_die2x(100,FATAL,ERR_EXCLUSIVE);
174 if (uid && !(euid = geteuid()))
175 strerr_die2x(100,FATAL,ERR_SUID);
176 if (!(ppasswd = getpwuid(uid)))
177 strerr_die2x(100,FATAL,ERR_UID);
178 if (!stralloc_copys(&user,ppasswd->pw_name)) die_nomem();
179 if (!stralloc_0(&user)) die_nomem();
180 if (!(ppasswd = getpwuid(euid)))
181 strerr_die2x(100,FATAL,ERR_EUID);
182 if (!stralloc_copys(&dir.s,ppasswd->pw_dir)) die_nomem();
183 if (!stralloc_0(&dir)) die_nomem();
184 if (!stralloc_copys(&euser,ppasswd->pw_name)) die_nomem();
185 if (!stralloc_0(&euser)) die_nomem();
187 if (chdir(dir.s) == -1)
188 strerr_die4sys(111,FATAL,ERR_SWITCH,dir.s,": ");
190 local = argv[optind++]; /* list address, optional for -c & -l */
192 if (!flagconfig && !flaglist)
197 if (!stralloc_copys(&listaddr,local)) die_nomem();
198 if (!isclean(local,1))
200 pos = str_chr(local,'@');
203 host = local + pos + 1;
204 lenhost = str_len(host);
206 if (!code) { /* ignored for -l, -c, and -d */
207 if (flagdelete || flaglist || flagconfig)
208 /* get away with not putting code for delete */
209 code = "a"; /* a hack - so what! */
213 if (!isclean(code,0))
216 if ((fdin = open_read(TXT_EZCRONRC)) == -1)
217 strerr_die6sys(111,FATAL,ERR_OPEN,dir.s,"/",TXT_EZCRONRC,": ");
218 /* first line is special */
219 substdio_fdbuf(&ssin,read,fdin,inbuf,sizeof(inbuf));
220 if (getln(&ssin,&line,&match,'\n') == -1)
221 strerr_die6sys(111,FATAL,ERR_READ,dir.s,"/",TXT_EZCRONRC,": ");
224 strerr_die6sys(111,FATAL,ERR_READ,dir.s,"/",TXT_EZCRONRC,": ");
225 /* (since we have match line.len has to be >= 1) */
226 line.s[line.len - 1] = '\0';
227 if (!isclean(line.s,0)) /* host for bounces */
228 strerr_die4x(100,ERR_CFHOST,dir.s,"/",TXT_EZCRONRC);
229 if (!stralloc_copys(&rp,line.s)) die_nomem();
233 if (!match) break; /* to allow last line without '\n' */
234 if (getln(&ssin,&line,&match,'\n') == -1)
235 strerr_die6sys(111,FATAL,ERR_READ,dir.s,"/",TXT_EZCRONRC,": ");
238 line.s[line.len-1] = '\0';
239 if (!case_startb(line.s,line.len,user.s))
242 if (pos >= line.len || line.s[pos] != ':')
244 founduser = 1; /* got user line */
249 strerr_die2x(100,FATAL,ERR_BADUSER);
252 line.s[line.len-1] = '\n'; /* not very elegant ;-) */
253 substdio_fdbuf(&ssout,write,1,outbuf,sizeof(outbuf));
254 if (substdio_put(&ssout,line.s,line.len) == -1)
255 strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: ");
256 if (substdio_flush(&ssout) == -1)
257 strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: ");
260 ++pos; /* points to first ':' */
261 len = str_chr(line.s+pos,':'); /* second ':' */
262 if (!line.s[pos + len])
264 if (!local) { /* only -d and std left */
269 if (len <= str_len(local))
270 if (!str_diffn(line.s+pos,local,len))
274 len = str_chr(line.s + pos,':'); /* third */
275 if (!line.s[pos + len])
277 if (local) { /* check host */
278 if (len == 0) /* empty host => any host */
281 if (len == str_len(host))
282 if (!case_diffb(line.s+pos,len,host))
286 pos += scan_ulong(line.s+pos,&maxlists);
287 if (line.s[pos]) { /* check additional lists */
288 if (line.s[pos] != ':')
290 if (line.s[pos+1+str_chr(line.s+pos+1,':')])
291 die_syntax(); /* reminder lists are not separated by ':' */
292 /* otherwise a ':' or arg miscount will die */
295 while (++pos < line.len) {
296 len = str_chr(line.s + pos,'@');
297 if (len == lenlocal && !str_diffn(line.s + pos,local,len)) {
299 if (!line.s[pos]) break;
301 len = str_chr(line.s+pos,',');
302 if (len == lenhost && !case_diffb(line.s+pos,len,host)) {
313 strerr_die2x(100,FATAL,ERR_BADHOST);
315 strerr_die2x(100,FATAL,ERR_BADLOCAL);
317 /* assemble correct line */
319 if (!stralloc_copyb(&addr,strnum,fmt_ulong(strnum,mm))) die_nomem();
320 if (!stralloc_cats(&addr," ")) die_nomem();
322 if (deltah <= 3L) dh = deltah;
323 else if (deltah <= 6L) dh = 6L;
324 else if (deltah <= 12L) dh = 12L;
325 else if (deltah <= 24L) dh = 24L;
326 else if (deltah <= 48L) {
327 if (dow[0] == '*') dow = "1,3,5";
328 } else if (deltah <= 72L) {
329 if (dow[0] == '*') dow = "1,4";
331 if (dow[0] == '*') dow = "1";
334 if (!stralloc_cats(&addr,"*")) die_nomem();
336 if (!stralloc_catb(&addr,strnum,fmt_ulong(strnum,hh))) die_nomem();
337 for (t = hh + dh; t < hh + 24L; t+=dh) {
338 if (!stralloc_cats(&addr,",")) die_nomem();
339 if (!stralloc_catb(&addr,strnum,fmt_ulong(strnum,t % 24L))) die_nomem();
342 if (!stralloc_cats(&addr," * * ")) die_nomem();
343 if (!stralloc_cats(&addr,dow)) die_nomem();
344 if (!stralloc_cats(&addr," ")) die_nomem();
345 part0start = addr.len; /* /var/qmail/bin/qmail-inject */
346 if (!stralloc_cats(&addr,qmail_inject)) die_nomem();
347 part0len = addr.len - part0start;
348 if (!stralloc_cats(&addr,local)) die_nomem();
349 if (!stralloc_cats(&addr,"-dig-")) die_nomem();
350 if (!stralloc_cats(&addr,code)) die_nomem();
351 if (!stralloc_cats(&addr,"@")) die_nomem();
352 if (!stralloc_cats(&addr,host)) die_nomem();
353 /* feed 'Return-Path: <user@host>' to qmail-inject */
354 if (!stralloc_cats(&addr,"%Return-path: <")) die_nomem();
355 if (!stralloc_cats(&addr,user.s)) die_nomem();
356 if (!stralloc_cats(&addr,"@")) die_nomem();
357 if (!stralloc_cat(&addr,&rp)) die_nomem();
358 if (!stralloc_cats(&addr,">\n")) die_nomem();
360 if (!stralloc_0(&addr)) die_nomem();
363 /* now to rewrite crontab we need to lock */
364 fdlock = open_append("crontabl");
366 strerr_die4sys(111,FATAL,ERR_OPEN,dir.s,"/crontabl: ");
367 if (lock_ex(fdlock) == -1) {
369 strerr_die4sys(111,FATAL,ERR_OBTAIN,dir.s,"/crontabl: ");
372 if ((fdin = open_read("crontab")) == -1) {
373 if (errno != error_noent)
374 strerr_die4sys(111,FATAL,ERR_READ,dir.s,"/crontab: ");
376 substdio_fdbuf(&ssin,read,fdin,inbuf,sizeof(inbuf));
378 substdio_fdbuf(&ssout,write,1,outbuf,sizeof(outbuf));
380 if ((fdout = open_trunc("crontabn")) == -1)
381 strerr_die4sys(111,FATAL,ERR_WRITE,dir.s,"/crontabn: ");
382 substdio_fdbuf(&ssout,write,fdout,outbuf,sizeof(outbuf));
388 if (!flaglist && line.len) {
389 line.s[line.len-1] = '\n';
390 if (substdio_put(&ssout,line.s,line.len) == -1)
391 strerr_die4sys(111,FATAL,ERR_WRITE,dir.s,"/crontabn: ");
393 if (getln(&ssin,&line,&match,'\n') == -1)
394 strerr_die4sys(111,FATAL,ERR_READ,dir.s,"/crontab: ");
397 flagours = 0; /* assume entry is not ours */
399 line.s[line.len - 1] = '\0'; /* match so at least 1 char */
401 while (line.s[pos] == ' ' && line.s[pos] == '\t') ++pos;
402 if (line.s[pos] == '#')
403 continue; /* cron comment */
404 pos = str_chr(line.s,'/');
405 if (!str_start(line.s+pos,qmail_inject)) continue;
406 pos += str_len(qmail_inject);
408 pos = byte_rchr(line.s,line.len,'<'); /* should be Return-Path: < */
410 continue; /* not ezmlm-cron line */
412 len = str_chr(line.s+pos,'@');
413 if (len == user.len - 1 && !str_diffn(line.s+pos,user.s,len)) {
415 ++nolists; /* belongs to this user */
420 pos = poslocal + str_chr(line.s+poslocal,'@');
421 if (pos + lenhost +1 >= line.len) continue;
422 if (case_diffb(line.s+pos+1,lenhost,host)) continue;
423 if (line.s[pos+lenhost+1] != '%') continue;
425 if (poslocal + lenlocal + 5 >= line.len) continue;
426 if (!str_start(line.s+poslocal,local)) continue;
427 pos2 = poslocal+lenlocal;
428 if (!str_start(line.s+pos2,"-dig-")) continue;
433 if (flaglist && (local || flagours)) {
434 if (substdio_put(&ssout,line.s,line.len) == -1)
435 strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: ");
436 if (substdio_put(&ssout,"\n",1) == -1)
437 strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: ");
439 line.len = 0; /* same - kill line */
447 if (substdio_flush(&ssout) == -1)
448 strerr_die3sys(111,FATAL,ERR_FLUSH,"stdout: ");
449 if (foundmatch) /* means we had a match */
452 strerr_die2x(100,FATAL,ERR_NO_MATCH);
454 /* only -d and regular use left */
456 if (nolists >= maxlists && !flagdelete)
457 strerr_die2x(100,FATAL,ERR_LISTNO);
459 if (substdio_put(&ssout,addr.s,addr.len-1) == -1)
460 strerr_die4sys(111,FATAL,ERR_WRITE,dir.s,"/crontabn: ");
461 if (flagdelete && !foundlocal)
462 strerr_die2x(111,FATAL,ERR_NO_MATCH);
463 if (substdio_flush(&ssout) == -1)
464 strerr_die4sys(111,FATAL,ERR_FLUSH,dir.s,"/crontabn: ");
465 if (fsync(fdout) == -1)
466 strerr_die4sys(111,FATAL,ERR_SYNC,dir.s,"/crontabn++: ");
467 if (close(fdout) == -1)
468 strerr_die4sys(111,FATAL,ERR_CLOSE,dir.s,"/crontabn: ");
469 if (rename("crontabn","crontab") == -1)
470 strerr_die4sys(111,FATAL,ERR_MOVE,dir.s,"/crontabn: ");
474 if (!stralloc_copys(&line,auto_cron)) die_nomem();
475 if (!stralloc_cats(&line,"/crontab '")) die_nomem();
476 if (!stralloc_cats(&line,dir.s)) die_nomem();
477 if (!stralloc_cats(&line,"/crontab'")) die_nomem();
478 if (!stralloc_0(&line)) die_nomem();
479 sendargs[2] = line.s;
481 switch(child = fork()) {
483 strerr_die2sys(111,FATAL,ERR_FORK);
485 if (setreuid(euid,euid) == -1)
486 strerr_die2sys(100,FATAL,ERR_SETUID);
487 execvp(*sendargs,sendargs);
488 if (errno == error_txtbsy || errno == error_nomem ||
490 strerr_die4sys(111,FATAL,ERR_EXECUTE,sendargs[2],": ");
492 strerr_die4sys(100,FATAL,ERR_EXECUTE,sendargs[2],": ");
495 wait_pid(&wstat,child);
496 if (wait_crashed(wstat))
497 strerr_die2x(111,FATAL,ERR_CHILD_CRASHED);
498 switch(wait_exitcode(wstat)) {
502 strerr_die2x(111,FATAL,ERR_CRONTAB);