ezmlmx 0.68
ezmlmx
Loading...
Searching...
No Matches
ezmlm-cron.c
Go to the documentation of this file.
1#include <sys/types.h>
2#include <sys/stat.h>
3#include <pwd.h>
4#include <unistd.h>
5#include "stralloc.h"
6#include "getoptb.h"
7#include "buffer.h"
8#include "error.h"
9#include "str.h"
10#include "fmt.h"
11#include "wait.h"
12#include "readwrite.h"
13#include "sig.h"
14#include "case.h"
15#include "scan.h"
16#include "open.h"
17#include "lock.h"
18#include "byte.h"
19#include "getln.h"
20#include "auto_qmail.h"
21#include "auto_cron.h"
22#include "auto_version.h"
23#include "errtxt.h"
24#include "idx.h"
25#include "wrap.h"
26#include "logmsg.h"
27#include "lockfile.h"
28
29#define WHO "ezmlm-cron"
30
31static void die_usage() { logmsg(WHO,100,USAGE,"ezmlm-cron [-cCdDlLvV] [-w dow] [-t hh:mm] [-i hrs] listadr code"); }
32static void die_dow() { logmsg(WHO,100,FATAL,ERR_DOW); }
33static void die_nomem() { logmsg(WHO,111,FATAL,ERR_NOMEM); }
34
35unsigned long deltah = 24L; /* default interval 24h */
36unsigned long hh = 4L; /* default time 04:12 */
37unsigned long mm = 12L;
38const char *dow = "*"; /* day of week */
39const char *qmail_inject = "/bin/qmail-inject ";
40char strnum[FMT_ULONG];
41unsigned long uid,euid;
42
43stralloc line = {0};
44stralloc rp = {0};
45stralloc addr = {0};
46stralloc user = {0};
47stralloc euser = {0};
48stralloc dir = {0};
49stralloc listaddr = {0};
50
51struct passwd *ppasswd;
52
56unsigned long dh,t;
57int founduser = 0;
58int listmatch = 0;
59int flagconfig = 0;
60int flagdelete = 0;
61int flaglist = 0;
62int flagdigit = 0;
65int foundmatch = 0;
66int nolists = 0;
67unsigned long maxlists;
68unsigned int pos, pos2, poslocal, len;
69unsigned int lenhost, lenlocal;
70unsigned int part0start, part0len;
72
73char *local = (char *) 0; /* list = local@host */
74const char *host = (char *) 0;
75const char *code = (char *) 0; /* digest code */
76const char *cp;
77
79{
80 if (!stralloc_0(&line)) die_nomem();
81 logmsg(WHO,100,FATAL,B(TXT_EZCRONRC ERR_SYNTAX,line.s));
82}
83
84void die_argument() { logmsg(WHO,100,FATAL,ERR_NOT_CLEAN); }
85
92
93int isclean(char *addr,int flagaddr)
94{
95 unsigned int pos;
96 char ch;
97 char *cp;
98
99 if (flagaddr) { /* shoud have one '@' */
100 pos = str_chr(addr,'@');
101 if (!pos || !addr[pos])
102 return 0; /* at least 1 char for local */
103 if (!addr[pos+1])
104 return 0; /* host must be at least 1 char */
105 pos++;
106 case_lowerb(addr+pos,str_len(addr)-pos);
107 } else
108 pos = 0;
109
110 pos += str_chr(addr + pos,'@');
111 if (addr[pos]) /* but no more */
112 return 0;
113 cp = addr;
114
115 while ((ch = *(cp++)))
116 if (!(ch >= 'a' && ch <= 'z') &&
117 !(ch >= 'A' && ch <= 'Z') &&
118 !(ch >= '0' && ch <= '9') &&
119 ch != '.' && ch != '-' && ch != '_' && ch != '@')
120 return 0;
121 return 1;
122}
123
124char inbuf[512];
125buffer bi;
126char outbuf[512];
127buffer bo;
128
129int main(int argc,char **argv)
130{
131 int child;
132 const char *sendargs[4];
133
134 umask(077);
135 sig_pipeignore();
136
137 while ((opt = getoptb(argc,argv,"cCdDi:lLt:w:vV")) != opteof)
138 switch (opt) {
139 case 'c': flagconfig = 1; break;
140 case 'C': flagconfig = 0; break;
141 case 'd': flagdelete = 1; break;
142 case 'D': flagdelete = 0; break;
143 case 'i': scan_ulong(optarg,&deltah); break;
144 case 'l': flaglist = 1; break;
145 case 'L': flaglist = 0; break;
146 case 't': pos = scan_ulong(optarg,&hh);
147 if (!optarg[pos++] == ':') die_usage();
148 pos = scan_ulong(optarg + pos,&mm);
149 break;
150 case 'w': dow = optarg;
151 cp = optarg - 1;
152 while (*(++cp)) {
153 if (*cp >= '0' && *cp <= '7') {
154 if (flagdigit) die_dow();
155 flagdigit = 1;
156 } else if (*cp == ',') {
157 if (!flagdigit) die_dow();
158 flagdigit = 0;
159 } else
160 die_dow();
161 }
162 break;
163 case 'v':
164 case 'V': logmsg(WHO,0,VERSION,auto_version);
165 default: die_usage();
166 }
167 if (flaglist + flagdelete + flagconfig > 1)
168 logmsg(WHO,100,FATAL,ERR_EXCLUSIVE);
169 uid = getuid();
170 if (uid && !(euid = geteuid()))
171 logmsg(WHO,100,FATAL,ERR_SUID);
172 if (!(ppasswd = getpwuid(uid)))
173 logmsg(WHO,100,FATAL,ERR_UID);
174 if (!stralloc_copys(&user,ppasswd->pw_name)) die_nomem();
175 if (!stralloc_0(&user)) die_nomem();
176 if (!(ppasswd = getpwuid(euid)))
177 logmsg(WHO,100,FATAL,ERR_EUID);
178 if (!stralloc_copys(&dir,ppasswd->pw_dir)) die_nomem();
179 if (!stralloc_0(&dir)) die_nomem();
180 if (!stralloc_copys(&euser,ppasswd->pw_name)) die_nomem();
181 if (!stralloc_0(&euser)) die_nomem();
182
183 if (chdir(dir.s) == -1)
184 logmsg(WHO,111,FATAL,B(ERR_SWITCH,dir.s));
185
186 local = argv[optind++]; /* list address, optional for -c & -l */
187 if (!local) {
188 if (!flagconfig && !flaglist) die_usage();
189 lenlocal = 0;
190 lenhost = 0;
191 } else {
192 if (!stralloc_copys(&listaddr,local)) die_nomem();
193 if (!isclean(local,1)) die_argument();
194 pos = str_chr(local,'@');
195 lenlocal = pos;
196 local[pos] = '\0';
197 host = local + pos + 1;
198 lenhost = str_len(host);
199 code = argv[optind];
200 if (!code) { /* ignored for -l, -c, and -d */
202 /* get away with not putting code for delete */
203 code = "a"; /* a hack - so what! */
204 else
205 die_usage();
206 } else
207 if (!isclean(code,0))
208 die_argument();
209 }
210 if ((fdin = open_read(TXT_EZCRONRC)) == -1)
211 logmsg(WHO,111,FATAL,B(ERR_OPEN,dir.s,"/","TXT_EZCRONRC"));
212 /* first line is special */
213 buffer_init(&bi,buffer_unixread,fdin,inbuf,sizeof(inbuf));
214 if (getln(&bi,&line,&match,'\n') == -1)
215 logmsg(WHO,111,FATAL,B(ERR_OPEN,dir.s,"/","TXT_EZCRONRC"));
216
217 if (!match)
218 logmsg(WHO,111,FATAL,B(ERR_OPEN,dir.s,"/","TXT_EZCRONRC"));
219 /* (since we have match line.len has to be >= 1) */
220 line.s[line.len - 1] = '\0';
221 if (!isclean(line.s,0)) /* host for bounces */
222 logmsg(WHO,100,INFO,B(ERR_CFHOST,dir.s,"/","TXT_EZCRONRC"));
223 if (!stralloc_copys(&rp,line.s)) die_nomem();
224
225 match = 1;
226 for(;;) {
227 if (!match) break; /* to allow last line without '\n' */
228 if (getln(&bi,&line,&match,'\n') == -1)
229 logmsg(WHO,111,FATAL,B(ERR_READ,dir.s,"/","TXT_EZCRONRC"));
230 if (!line.len)
231 break;
232 line.s[line.len-1] = '\0';
233 if (!case_startb(line.s,line.len,user.s))
234 continue;
235 pos = user.len - 1;
236 if (pos >= line.len || line.s[pos] != ':')
237 continue;
238 founduser = 1; /* got user line */
239 break;
240 }
241 close(fdin);
242 if (!founduser)
243 logmsg(WHO,100,FATAL,ERR_BADUSER);
244
245 if (flagconfig) {
246 line.s[line.len-1] = '\n'; /* not very elegant ;-) */
247 buffer_init(&bo,buffer_unixwrite,1,outbuf,sizeof(outbuf));
248 if (buffer_put(&bo,line.s,line.len) == -1)
249 logmsg(WHO,111,FATAL,B(ERR_WRITE,"stdout"));
250 if (buffer_flush(&bo) == -1)
251 logmsg(WHO,111,FATAL,B(ERR_WRITE,"stdout"));
252 _exit(0);
253 }
254 ++pos; /* points to first ':' */
255 len = str_chr(line.s+pos,':'); /* second ':' */
256 if (!line.s[pos + len])
257 die_syntax();
258 if (!local) { /* only -d and std left */
259 localmatch = 1;
260 hostmatch = 1;
261 } else {
262 hostmatch = 0;
263 if (len <= str_len(local))
264 if (!str_diffn(line.s+pos,local,len))
265 localmatch = 1;
266 }
267 pos += len + 1;
268 len = str_chr(line.s + pos,':'); /* third */
269 if (!line.s[pos + len])
270 die_syntax();
271 if (local) { /* check host */
272 if (len == 0) /* empty host => any host */
273 hostmatch = 1;
274 else
275 if (len == str_len(host))
276 if (!case_diffb(line.s+pos,len,host))
277 hostmatch = 1;
278 }
279 pos += len + 1;
280 pos += scan_ulong(line.s+pos,&maxlists);
281 if (line.s[pos]) { /* check additional lists */
282 if (line.s[pos] != ':')
283 die_syntax();
284 if (line.s[pos+1+str_chr(line.s+pos+1,':')])
285 die_syntax(); /* reminder lists are not separated by ':' */
286 /* otherwise a ':' or arg miscount will die silently */
287 if (local) {
288 while (++pos < line.len) {
289 len = str_chr(line.s + pos,'@');
290 if (len == lenlocal && !str_diffn(line.s + pos,local,len)) {
291 pos += len;
292 if (!line.s[pos]) break;
293 pos++;
294 len = str_chr(line.s+pos,',');
295 if (len == lenhost && !case_diffb(line.s+pos,len,host)) {
296 listmatch = 1;
297 break;
298 }
299 }
300 pos += len;
301 }
302 }
303 }
304 if (!listmatch) {
305 if (!hostmatch)
306 logmsg(WHO,100,FATAL,ERR_BADHOST);
307 if (!localmatch)
308 logmsg(WHO,100,FATAL,ERR_BADLOCAL);
309 }
310 /* assemble correct line */
311 if (!flaglist) {
312 if (!stralloc_copyb(&addr,strnum,fmt_ulong(strnum,mm))) die_nomem();
313 if (!stralloc_cats(&addr," ")) die_nomem();
314 dh = 0L;
315 if (deltah <= 3L) dh = deltah;
316 else if (deltah <= 6L) dh = 6L;
317 else if (deltah <= 12L) dh = 12L;
318 else if (deltah <= 24L) dh = 24L;
319 else if (deltah <= 48L) {
320 if (dow[0] == '*') dow = "1,3,5";
321 } else if (deltah <= 72L) {
322 if (dow[0] == '*') dow = "1,4";
323 } else
324 if (dow[0] == '*') dow = "1";
325
326 if (!dh) {
327 if (!stralloc_cats(&addr,"*")) die_nomem();
328 } else {
329 if (!stralloc_catb(&addr,strnum,fmt_ulong(strnum,hh))) die_nomem();
330 for (t = hh + dh; t < hh + 24L; t+=dh) {
331 if (!stralloc_cats(&addr,",")) die_nomem();
332 if (!stralloc_catb(&addr,strnum,fmt_ulong(strnum,t % 24L))) die_nomem();
333 }
334 }
335 if (!stralloc_cats(&addr," * * ")) die_nomem();
336 if (!stralloc_cats(&addr,dow)) die_nomem();
337 if (!stralloc_cats(&addr," ")) die_nomem();
338 part0start = addr.len; /* /var/qmail/bin/qmail-inject */
339 if (!stralloc_cats(&addr,auto_qmail)) die_nomem();
340 if (!stralloc_cats(&addr,qmail_inject)) die_nomem();
341 part0len = addr.len - part0start;
342 if (!stralloc_cats(&addr,local)) die_nomem();
343 if (!stralloc_cats(&addr,"-dig-")) die_nomem();
344 if (!stralloc_cats(&addr,code)) die_nomem();
345 if (!stralloc_cats(&addr,"@")) die_nomem();
346 if (!stralloc_cats(&addr,host)) die_nomem();
347 /* feed 'Return-Path: <user@host>' to qmail-inject */
348 if (!stralloc_cats(&addr,"%Return-path: <")) die_nomem();
349 if (!stralloc_cats(&addr,user.s)) die_nomem();
350 if (!stralloc_cats(&addr,"@")) die_nomem();
351 if (!stralloc_cat(&addr,&rp)) die_nomem();
352 if (!stralloc_cats(&addr,">\n")) die_nomem();
353 }
354 if (!stralloc_0(&addr)) die_nomem();
355
356 if (!flaglist) {
357 /* now to rewrite crontab we need to lock */
358 fdlock = lockfile("crontabl");
359 } /* if !flaglist */
360 if ((fdin = open_read("crontab")) == -1) {
361 if (errno != ENOENT)
362 logmsg(WHO,111,FATAL,B(ERR_READ,dir.s,"/crontab"));
363 } else
364 buffer_init(&bi,buffer_unixread,fdin,inbuf,sizeof(inbuf));
365 if (flaglist)
366 buffer_init(&bo,buffer_unixwrite,1,outbuf,sizeof(outbuf));
367 else {
368 if ((fdout = open_trunc("crontabn")) == -1)
369 logmsg(WHO,111,FATAL,B(ERR_WRITE,dir.s,"/crontabn "));
370 buffer_init(&bo,buffer_unixwrite,fdout,outbuf,sizeof(outbuf));
371 }
372 line.len = 0;
373
374 if (fdin != -1) {
375 for (;;) {
376 if (!flaglist && line.len) {
377 line.s[line.len-1] = '\n';
378 if (buffer_put(&bo,line.s,line.len) == -1)
379 logmsg(WHO,111,FATAL,B(ERR_WRITE,dir.s,"/crontabn"));
380 }
381 if (getln(&bi,&line,&match,'\n') == -1)
382 logmsg(WHO,111,FATAL,B(ERR_READ,dir.s,"/crontab"));
383 if (!match)
384 break;
385 flagours = 0; /* assume entry is not ours */
386 foundlocal = 0;
387 line.s[line.len - 1] = '\0'; /* match so at least 1 char */
388 pos = 0;
389 while (line.s[pos] == ' ' && line.s[pos] == '\t') ++pos;
390 if (line.s[pos] == '#')
391 continue; /* cron comment */
392 pos = str_chr(line.s,'/');
393 if (!str_start(line.s+pos,auto_qmail)) continue;
394 pos += str_len(auto_qmail);
395 if (!str_start(line.s+pos,qmail_inject)) continue;
396 pos += str_len(qmail_inject);
397 poslocal = pos;
398 pos = byte_rchr(line.s,line.len,'<'); /* should be Return-Path: < */
399 if (pos == line.len)
400 continue; /* not ezmlm-cron line */
401 pos++;
402 len = str_chr(line.s+pos,'@');
403 if (len == user.len - 1 && !str_diffn(line.s+pos,user.s,len)) {
404 flagours = 1;
405 ++nolists; /* belongs to this user */
406 }
407 if (!local) {
408 foundlocal = 1;
409 } else {
410 pos = poslocal + str_chr(line.s+poslocal,'@');
411 if (pos + lenhost +1 >= line.len) continue;
412 if (case_diffb(line.s+pos+1,lenhost,host)) continue;
413 if (line.s[pos+lenhost+1] != '%') continue;
414 /* check local */
415 if (poslocal + lenlocal + 5 >= line.len) continue;
416 if (!str_start(line.s+poslocal,local)) continue;
418 if (!str_start(line.s+pos2,"-dig-")) continue;
419 foundlocal = 1;
420 }
421 if (foundlocal) {
422 foundmatch = 1;
423 if (flaglist && (local || flagours)) {
424 if (buffer_put(&bo,line.s,line.len) == -1)
425 logmsg(WHO,111,FATAL,B(ERR_WRITE,"stdout"));
426 if (buffer_put(&bo,"\n",1) == -1)
427 logmsg(WHO,111,FATAL,B(ERR_WRITE,"stdout"));
428 }
429 line.len = 0; /* same - kill line */
430 if (flagours)
431 --nolists;
432 }
433 }
434 close(fdin);
435 }
436 if (flaglist) {
437 if (buffer_flush(&bo) == -1)
438 logmsg(WHO,111,FATAL,B(ERR_FLUSH,"stdout"));
439 if (foundmatch) /* means we had a match */
440 _exit(0);
441 else
442 logmsg(WHO,100,FATAL,ERR_NO_MATCH);
443 }
444 /* only -d and regular use left */
445
446 if (nolists >= maxlists && !flagdelete)
447 logmsg(WHO,100,FATAL,ERR_LISTNO);
448 if (!flagdelete)
449 if (buffer_put(&bo,addr.s,addr.len-1) == -1)
450 logmsg(WHO,111,FATAL,B(ERR_WRITE,dir.s,"/crontabn"));
451 if (flagdelete && !foundlocal)
452 logmsg(WHO,100,FATAL,ERR_NO_MATCH);
453 if (buffer_flush(&bo) == -1)
454 logmsg(WHO,111,FATAL,B(ERR_FLUSH,dir.s,"/crontabn"));
455 if (fsync(fdout) == -1)
456 logmsg(WHO,111,FATAL,B(ERR_SYNC,dir.s,"/crontabn"));
457 if (close(fdout) == -1)
458 logmsg(WHO,111,FATAL,B(ERR_CLOSE,dir.s,"/crontabn"));
459 if (rename("crontabn","crontab") == -1)
460 logmsg(WHO,111,FATAL,B(ERR_MOVE,dir.s,"/crontabn"));
461 sendargs[0] = "sh";
462 sendargs[1] = "-c";
463
464 if (!stralloc_copys(&line,auto_cron)) die_nomem();
465 if (!stralloc_cats(&line,"/crontab '")) die_nomem();
466 if (!stralloc_cats(&line,dir.s)) die_nomem();
467 if (!stralloc_cats(&line,"/crontab'")) die_nomem();
468 if (!stralloc_0(&line)) die_nomem();
469 sendargs[2] = line.s;
470 sendargs[3] = 0;
471 if ((child = wrap_fork()) == 0) {
472 if (setreuid(euid,euid) == -1)
473 logmsg(WHO,100,FATAL,ERR_SETUID);
474 wrap_execvp(sendargs);
475 }
476 /* parent */
477 switch (wrap_waitpid(child)) {
478 case 0: _exit(0);
479 default: logmsg(WHO,111,FATAL,ERR_CRONTAB);
480 }
481
482 return 0;
483}
#define TXT_EZCRONRC
Definition idx.h:263
const char auto_version[]
void wrap_execvp(const char **argv)
Definition wrap_execv.c:30
int wrap_fork(void)
Definition wrap_fork.c:15
int wrap_waitpid(int pid)
Error messages. If you translate these, I would urge you to keep the English version as well....
#define ERR_FLUSH
Definition errtxt.h:20
#define ERR_NOMEM
Definition errtxt.h:14
#define ERR_SYNC
Definition errtxt.h:22
#define ERR_OPEN
Definition errtxt.h:30
#define ERR_MOVE
Definition errtxt.h:29
#define ERR_EUID
Definition errtxt.h:133
#define ERR_CRONTAB
Definition errtxt.h:142
#define ERR_EXCLUSIVE
Definition errtxt.h:141
#define ERR_LISTNO
Definition errtxt.h:137
#define ERR_CFHOST
Definition errtxt.h:140
#define ERR_BADHOST
Definition errtxt.h:135
#define ERR_READ
Definition errtxt.h:18
#define ERR_SETUID
Definition errtxt.h:139
#define ERR_SYNTAX
Definition errtxt.h:121
#define ERR_DOW
Definition errtxt.h:129
#define ERR_UID
Definition errtxt.h:132
#define ERR_SWITCH
Definition errtxt.h:42
#define ERR_WRITE
Definition errtxt.h:17
#define ERR_CLOSE
Definition errtxt.h:16
#define ERR_NO_MATCH
Definition errtxt.h:138
#define ERR_SUID
Definition errtxt.h:131
#define ERR_BADUSER
Definition errtxt.h:134
#define ERR_BADLOCAL
Definition errtxt.h:136
#define ERR_NOT_CLEAN
Definition errtxt.h:130
int lockfile(const char *)
Definition lockfile.c:15
void die_nomem()
Definition getconf.c:17
#define WHO
Definition author.c:1
char inbuf[1024]
char outbuf[1024]
char * dir
buffer bi
buffer bo
int main()
Definition ezmlm-weed.c:69
stralloc listaddr
Definition ezmlm-cron.c:49
unsigned long t
Definition ezmlm-cron.c:56
int opt
Definition ezmlm-cron.c:53
int nolists
Definition ezmlm-cron.c:66
void die_syntax()
Definition ezmlm-cron.c:78
unsigned int lenhost
Definition ezmlm-cron.c:69
const char * cp
Definition ezmlm-cron.c:76
int fdin
Definition ezmlm-cron.c:71
unsigned int part0start
Definition ezmlm-cron.c:70
int flaglist
Definition ezmlm-cron.c:61
unsigned int poslocal
Definition ezmlm-cron.c:68
int founduser
Definition ezmlm-cron.c:57
stralloc user
Definition ezmlm-cron.c:46
int fdout
Definition ezmlm-cron.c:71
unsigned int lenlocal
Definition ezmlm-cron.c:69
unsigned int len
Definition ezmlm-cron.c:68
int foundmatch
Definition ezmlm-cron.c:65
int flagours
Definition ezmlm-cron.c:63
unsigned int part0len
Definition ezmlm-cron.c:70
int hostmatch
Definition ezmlm-cron.c:54
int flagdigit
Definition ezmlm-cron.c:62
unsigned long deltah
Definition ezmlm-cron.c:35
int localmatch
Definition ezmlm-cron.c:55
unsigned long hh
Definition ezmlm-cron.c:36
int listmatch
Definition ezmlm-cron.c:58
int foundlocal
Definition ezmlm-cron.c:64
int flagconfig
Definition ezmlm-cron.c:59
struct passwd * ppasswd
Definition ezmlm-cron.c:51
unsigned int pos2
Definition ezmlm-cron.c:68
const char * code
Definition ezmlm-cron.c:75
const char * dow
Definition ezmlm-cron.c:38
unsigned long maxlists
Definition ezmlm-cron.c:67
stralloc rp
Definition ezmlm-cron.c:44
stralloc addr
Definition ezmlm-cron.c:45
const char * qmail_inject
Definition ezmlm-cron.c:39
int isclean(char *addr, int flagaddr)
Definition ezmlm-cron.c:93
int fdlock
Definition ezmlm-cron.c:71
unsigned long dh
Definition ezmlm-cron.c:56
void die_argument()
Definition ezmlm-cron.c:84
int flagdelete
Definition ezmlm-cron.c:60
stralloc euser
Definition ezmlm-cron.c:47
unsigned long mm
Definition ezmlm-cron.c:37
char * host
Definition ezmlm-cgi.c:107
char * local
Definition ezmlm-cgi.c:106
int child
Definition ezmlm-cgi.c:143
unsigned long uid
Definition ezmlm-cgi.c:134
int match
Definition ezmlm-cgi.c:140
unsigned long euid
Definition ezmlm-cgi.c:134
const char auto_qmail[]
Definition auto_qmail.c:1
const char auto_cron[]
Definition auto_cron.c:1
const char * logmsg(const char *dir, unsigned long num, unsigned long listno, unsigned long subs, int done)
Definition loginfo.c:32