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
= "/bin/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
,auto_qmail
)) die_nomem();
347 if (!stralloc_cats(&addr
,qmail_inject
)) die_nomem();
348 part0len
= addr
.len
- part0start
;
349 if (!stralloc_cats(&addr
,local
)) die_nomem();
350 if (!stralloc_cats(&addr
,"-dig-")) die_nomem();
351 if (!stralloc_cats(&addr
,code
)) die_nomem();
352 if (!stralloc_cats(&addr
,"@")) die_nomem();
353 if (!stralloc_cats(&addr
,host
)) die_nomem();
354 /* feed 'Return-Path: <user@host>' to qmail-inject */
355 if (!stralloc_cats(&addr
,"%Return-path: <")) die_nomem();
356 if (!stralloc_cats(&addr
,user
.s
)) die_nomem();
357 if (!stralloc_cats(&addr
,"@")) die_nomem();
358 if (!stralloc_cat(&addr
,&rp
)) die_nomem();
359 if (!stralloc_cats(&addr
,">\n")) die_nomem();
361 if (!stralloc_0(&addr
)) die_nomem();
364 /* now to rewrite crontab we need to lock */
365 fdlock
= open_append("crontabl");
367 strerr_die4sys(111,FATAL
,ERR_OPEN
,dir
.s
,"/crontabl: ");
368 if (lock_ex(fdlock
) == -1) {
370 strerr_die4sys(111,FATAL
,ERR_OBTAIN
,dir
.s
,"/crontabl: ");
373 if ((fdin
= open_read("crontab")) == -1) {
374 if (errno
!= error_noent
)
375 strerr_die4sys(111,FATAL
,ERR_READ
,dir
.s
,"/crontab: ");
377 substdio_fdbuf(&ssin
,read
,fdin
,inbuf
,sizeof(inbuf
));
379 substdio_fdbuf(&ssout
,write
,1,outbuf
,sizeof(outbuf
));
381 if ((fdout
= open_trunc("crontabn")) == -1)
382 strerr_die4sys(111,FATAL
,ERR_WRITE
,dir
.s
,"/crontabn: ");
383 substdio_fdbuf(&ssout
,write
,fdout
,outbuf
,sizeof(outbuf
));
389 if (!flaglist
&& line
.len
) {
390 line
.s
[line
.len
-1] = '\n';
391 if (substdio_put(&ssout
,line
.s
,line
.len
) == -1)
392 strerr_die4sys(111,FATAL
,ERR_WRITE
,dir
.s
,"/crontabn: ");
394 if (getln(&ssin
,&line
,&match
,'\n') == -1)
395 strerr_die4sys(111,FATAL
,ERR_READ
,dir
.s
,"/crontab: ");
398 flagours
= 0; /* assume entry is not ours */
400 line
.s
[line
.len
- 1] = '\0'; /* match so at least 1 char */
402 while (line
.s
[pos
] == ' ' && line
.s
[pos
] == '\t') ++pos
;
403 if (line
.s
[pos
] == '#')
404 continue; /* cron comment */
405 pos
= str_chr(line
.s
,'/');
406 if (!str_start(line
.s
+pos
,auto_qmail
)) continue;
407 pos
+= str_len(auto_qmail
);
408 if (!str_start(line
.s
+pos
,qmail_inject
)) continue;
409 pos
+= str_len(qmail_inject
);
411 pos
= byte_rchr(line
.s
,line
.len
,'<'); /* should be Return-Path: < */
413 continue; /* not ezmlm-cron line */
415 len
= str_chr(line
.s
+pos
,'@');
416 if (len
== user
.len
- 1 && !str_diffn(line
.s
+pos
,user
.s
,len
)) {
418 ++nolists
; /* belongs to this user */
423 pos
= poslocal
+ str_chr(line
.s
+poslocal
,'@');
424 if (pos
+ lenhost
+1 >= line
.len
) continue;
425 if (case_diffb(line
.s
+pos
+1,lenhost
,host
)) continue;
426 if (line
.s
[pos
+lenhost
+1] != '%') continue;
428 if (poslocal
+ lenlocal
+ 5 >= line
.len
) continue;
429 if (!str_start(line
.s
+poslocal
,local
)) continue;
430 pos2
= poslocal
+lenlocal
;
431 if (!str_start(line
.s
+pos2
,"-dig-")) continue;
436 if (flaglist
&& (local
|| flagours
)) {
437 if (substdio_put(&ssout
,line
.s
,line
.len
) == -1)
438 strerr_die3sys(111,FATAL
,ERR_WRITE
,"stdout: ");
439 if (substdio_put(&ssout
,"\n",1) == -1)
440 strerr_die3sys(111,FATAL
,ERR_WRITE
,"stdout: ");
442 line
.len
= 0; /* same - kill line */
450 if (substdio_flush(&ssout
) == -1)
451 strerr_die3sys(111,FATAL
,ERR_FLUSH
,"stdout: ");
452 if (foundmatch
) /* means we had a match */
455 strerr_die2x(100,FATAL
,ERR_NO_MATCH
);
457 /* only -d and regular use left */
459 if (nolists
>= maxlists
&& !flagdelete
)
460 strerr_die2x(100,FATAL
,ERR_LISTNO
);
462 if (substdio_put(&ssout
,addr
.s
,addr
.len
-1) == -1)
463 strerr_die4sys(111,FATAL
,ERR_WRITE
,dir
.s
,"/crontabn: ");
464 if (flagdelete
&& !foundlocal
)
465 strerr_die2x(111,FATAL
,ERR_NO_MATCH
);
466 if (substdio_flush(&ssout
) == -1)
467 strerr_die4sys(111,FATAL
,ERR_FLUSH
,dir
.s
,"/crontabn: ");
468 if (fsync(fdout
) == -1)
469 strerr_die4sys(111,FATAL
,ERR_SYNC
,dir
.s
,"/crontabn++: ");
470 if (close(fdout
) == -1)
471 strerr_die4sys(111,FATAL
,ERR_CLOSE
,dir
.s
,"/crontabn: ");
472 if (rename("crontabn","crontab") == -1)
473 strerr_die4sys(111,FATAL
,ERR_MOVE
,dir
.s
,"/crontabn: ");
477 if (!stralloc_copys(&line
,auto_cron
)) die_nomem();
478 if (!stralloc_cats(&line
,"/crontab '")) die_nomem();
479 if (!stralloc_cats(&line
,dir
.s
)) die_nomem();
480 if (!stralloc_cats(&line
,"/crontab'")) die_nomem();
481 if (!stralloc_0(&line
)) die_nomem();
482 sendargs
[2] = line
.s
;
484 switch(child
= fork()) {
486 strerr_die2sys(111,FATAL
,ERR_FORK
);
488 if (setreuid(euid
,euid
) == -1)
489 strerr_die2sys(100,FATAL
,ERR_SETUID
);
490 execvp(*sendargs
,sendargs
);
491 if (errno
== error_txtbsy
|| errno
== error_nomem
||
493 strerr_die4sys(111,FATAL
,ERR_EXECUTE
,sendargs
[2],": ");
495 strerr_die4sys(100,FATAL
,ERR_EXECUTE
,sendargs
[2],": ");
498 wait_pid(&wstat
,child
);
499 if (wait_crashed(wstat
))
500 strerr_die2x(111,FATAL
,ERR_CHILD_CRASHED
);
501 switch(wait_exitcode(wstat
)) {
505 strerr_die2x(111,FATAL
,ERR_CRONTAB
);