diff options
Diffstat (limited to 'miscutils/crond.c')
-rw-r--r-- | miscutils/crond.c | 1016 |
1 files changed, 1016 insertions, 0 deletions
diff --git a/miscutils/crond.c b/miscutils/crond.c new file mode 100644 index 000000000..fa7964e4e --- /dev/null +++ b/miscutils/crond.c | |||
@@ -0,0 +1,1016 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * crond -d[#] -c <crondir> -f -b | ||
4 | * | ||
5 | * run as root, but NOT setuid root | ||
6 | * | ||
7 | * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) | ||
8 | * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 | ||
9 | * | ||
10 | * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. | ||
11 | */ | ||
12 | |||
13 | #define VERSION "2.3.2" | ||
14 | |||
15 | #include "busybox.h" | ||
16 | #include <sys/syslog.h> | ||
17 | |||
18 | #define arysize(ary) (sizeof(ary)/sizeof((ary)[0])) | ||
19 | |||
20 | #ifndef CRONTABS | ||
21 | #define CRONTABS "/var/spool/cron/crontabs" | ||
22 | #endif | ||
23 | #ifndef TMPDIR | ||
24 | #define TMPDIR "/var/spool/cron" | ||
25 | #endif | ||
26 | #ifndef SENDMAIL | ||
27 | #define SENDMAIL "/usr/sbin/sendmail" | ||
28 | #endif | ||
29 | #ifndef SENDMAIL_ARGS | ||
30 | #define SENDMAIL_ARGS "-ti", "oem" | ||
31 | #endif | ||
32 | #ifndef CRONUPDATE | ||
33 | #define CRONUPDATE "cron.update" | ||
34 | #endif | ||
35 | #ifndef MAXLINES | ||
36 | #define MAXLINES 256 /* max lines in non-root crontabs */ | ||
37 | #endif | ||
38 | |||
39 | typedef struct CronFile { | ||
40 | struct CronFile *cf_Next; | ||
41 | struct CronLine *cf_LineBase; | ||
42 | char *cf_User; /* username */ | ||
43 | int cf_Ready; /* bool: one or more jobs ready */ | ||
44 | int cf_Running; /* bool: one or more jobs running */ | ||
45 | int cf_Deleted; /* marked for deletion, ignore */ | ||
46 | } CronFile; | ||
47 | |||
48 | typedef struct CronLine { | ||
49 | struct CronLine *cl_Next; | ||
50 | char *cl_Shell; /* shell command */ | ||
51 | pid_t cl_Pid; /* running pid, 0, or armed (-1) */ | ||
52 | int cl_MailFlag; /* running pid is for mail */ | ||
53 | int cl_MailPos; /* 'empty file' size */ | ||
54 | char cl_Mins[60]; /* 0-59 */ | ||
55 | char cl_Hrs[24]; /* 0-23 */ | ||
56 | char cl_Days[32]; /* 1-31 */ | ||
57 | char cl_Mons[12]; /* 0-11 */ | ||
58 | char cl_Dow[7]; /* 0-6, beginning sunday */ | ||
59 | } CronLine; | ||
60 | |||
61 | #define RUN_RANOUT 1 | ||
62 | #define RUN_RUNNING 2 | ||
63 | #define RUN_FAILED 3 | ||
64 | |||
65 | #define DaemonUid 0 | ||
66 | |||
67 | #if ENABLE_DEBUG_CROND_OPTION | ||
68 | static unsigned DebugOpt; | ||
69 | #endif | ||
70 | |||
71 | static unsigned LogLevel = 8; | ||
72 | static const char *LogFile; | ||
73 | static const char *CDir = CRONTABS; | ||
74 | |||
75 | static void startlogger(void); | ||
76 | |||
77 | static void CheckUpdates(void); | ||
78 | static void SynchronizeDir(void); | ||
79 | static int TestJobs(time_t t1, time_t t2); | ||
80 | static void RunJobs(void); | ||
81 | static int CheckJobs(void); | ||
82 | |||
83 | static void RunJob(const char *user, CronLine * line); | ||
84 | |||
85 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | ||
86 | static void EndJob(const char *user, CronLine * line); | ||
87 | #else | ||
88 | #define EndJob(user, line) line->cl_Pid = 0 | ||
89 | #endif | ||
90 | |||
91 | static void DeleteFile(const char *userName); | ||
92 | |||
93 | static CronFile *FileBase; | ||
94 | |||
95 | |||
96 | static void crondlog(const char *ctl, ...) | ||
97 | { | ||
98 | va_list va; | ||
99 | const char *fmt; | ||
100 | int level = (int) (ctl[0] & 0xf); | ||
101 | int type = level == 20 ? | ||
102 | LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE); | ||
103 | |||
104 | |||
105 | va_start(va, ctl); | ||
106 | fmt = ctl + 1; | ||
107 | if (level >= LogLevel) { | ||
108 | |||
109 | #if ENABLE_DEBUG_CROND_OPTION | ||
110 | if (DebugOpt) { | ||
111 | vfprintf(stderr, fmt, va); | ||
112 | } else | ||
113 | #endif | ||
114 | if (LogFile == 0) { | ||
115 | vsyslog(type, fmt, va); | ||
116 | } else { | ||
117 | int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); | ||
118 | if (logfd >= 0) { | ||
119 | vdprintf(logfd, fmt, va); | ||
120 | close(logfd); | ||
121 | #if ENABLE_DEBUG_CROND_OPTION | ||
122 | } else { | ||
123 | bb_perror_msg("can't open log file"); | ||
124 | #endif | ||
125 | } | ||
126 | } | ||
127 | } | ||
128 | va_end(va); | ||
129 | if (ctl[0] & 0200) { | ||
130 | exit(20); | ||
131 | } | ||
132 | } | ||
133 | |||
134 | int crond_main(int ac, char **av) | ||
135 | { | ||
136 | unsigned opt; | ||
137 | char *lopt, *Lopt, *copt; | ||
138 | |||
139 | #if ENABLE_DEBUG_CROND_OPTION | ||
140 | char *dopt; | ||
141 | |||
142 | opt_complementary = "f-b:b-f:S-L:L-S:d-l"; | ||
143 | #else | ||
144 | opt_complementary = "f-b:b-f:S-L:L-S"; | ||
145 | #endif | ||
146 | |||
147 | opterr = 0; /* disable getopt 'errors' message. */ | ||
148 | opt = getopt32(ac, av, "l:L:fbSc:" | ||
149 | #if ENABLE_DEBUG_CROND_OPTION | ||
150 | "d:" | ||
151 | #endif | ||
152 | , &lopt, &Lopt, &copt | ||
153 | #if ENABLE_DEBUG_CROND_OPTION | ||
154 | , &dopt | ||
155 | #endif | ||
156 | ); | ||
157 | if (opt & 1) { | ||
158 | LogLevel = xatou(lopt); | ||
159 | } | ||
160 | if (opt & 2) { | ||
161 | if (*Lopt != 0) { | ||
162 | LogFile = Lopt; | ||
163 | } | ||
164 | } | ||
165 | if (opt & 32) { | ||
166 | if (*copt != 0) { | ||
167 | CDir = copt; | ||
168 | } | ||
169 | } | ||
170 | #if ENABLE_DEBUG_CROND_OPTION | ||
171 | if (opt & 64) { | ||
172 | DebugOpt = xatou(dopt); | ||
173 | LogLevel = 0; | ||
174 | } | ||
175 | #endif | ||
176 | |||
177 | /* | ||
178 | * change directory | ||
179 | */ | ||
180 | |||
181 | xchdir(CDir); | ||
182 | signal(SIGHUP, SIG_IGN); /* hmm.. but, if kill -HUP original | ||
183 | * version - his died. ;( | ||
184 | */ | ||
185 | /* | ||
186 | * close stdin and stdout, stderr. | ||
187 | * close unused descriptors - don't need. | ||
188 | * optional detach from controlling terminal | ||
189 | */ | ||
190 | |||
191 | if (!(opt & 4)) { | ||
192 | #ifdef BB_NOMMU | ||
193 | /* reexec for vfork() do continue parent */ | ||
194 | vfork_daemon_rexec(1, 0, ac, av, "-f"); | ||
195 | #else | ||
196 | xdaemon(1, 0); | ||
197 | #endif | ||
198 | } | ||
199 | |||
200 | (void) startlogger(); /* need if syslog mode selected */ | ||
201 | |||
202 | /* | ||
203 | * main loop - synchronize to 1 second after the minute, minimum sleep | ||
204 | * of 1 second. | ||
205 | */ | ||
206 | |||
207 | crondlog("\011%s " VERSION " dillon, started, log level %d\n", | ||
208 | applet_name, LogLevel); | ||
209 | |||
210 | SynchronizeDir(); | ||
211 | |||
212 | { | ||
213 | time_t t1 = time(NULL); | ||
214 | time_t t2; | ||
215 | long dt; | ||
216 | int rescan = 60; | ||
217 | short sleep_time = 60; | ||
218 | |||
219 | for (;;) { | ||
220 | sleep((sleep_time + 1) - (short) (time(NULL) % sleep_time)); | ||
221 | |||
222 | t2 = time(NULL); | ||
223 | dt = t2 - t1; | ||
224 | |||
225 | /* | ||
226 | * The file 'cron.update' is checked to determine new cron | ||
227 | * jobs. The directory is rescanned once an hour to deal | ||
228 | * with any screwups. | ||
229 | * | ||
230 | * check for disparity. Disparities over an hour either way | ||
231 | * result in resynchronization. A reverse-indexed disparity | ||
232 | * less then an hour causes us to effectively sleep until we | ||
233 | * match the original time (i.e. no re-execution of jobs that | ||
234 | * have just been run). A forward-indexed disparity less then | ||
235 | * an hour causes intermediate jobs to be run, but only once | ||
236 | * in the worst case. | ||
237 | * | ||
238 | * when running jobs, the inequality used is greater but not | ||
239 | * equal to t1, and less then or equal to t2. | ||
240 | */ | ||
241 | |||
242 | if (--rescan == 0) { | ||
243 | rescan = 60; | ||
244 | SynchronizeDir(); | ||
245 | } | ||
246 | CheckUpdates(); | ||
247 | #if ENABLE_DEBUG_CROND_OPTION | ||
248 | if (DebugOpt) | ||
249 | crondlog("\005Wakeup dt=%d\n", dt); | ||
250 | #endif | ||
251 | if (dt < -60 * 60 || dt > 60 * 60) { | ||
252 | t1 = t2; | ||
253 | crondlog("\111time disparity of %d minutes detected\n", dt / 60); | ||
254 | } else if (dt > 0) { | ||
255 | TestJobs(t1, t2); | ||
256 | RunJobs(); | ||
257 | sleep(5); | ||
258 | if (CheckJobs() > 0) { | ||
259 | sleep_time = 10; | ||
260 | } else { | ||
261 | sleep_time = 60; | ||
262 | } | ||
263 | t1 = t2; | ||
264 | } | ||
265 | } | ||
266 | } | ||
267 | return 0; /* not reached */ | ||
268 | } | ||
269 | |||
270 | static int ChangeUser(const char *user) | ||
271 | { | ||
272 | struct passwd *pas; | ||
273 | const char *err_msg; | ||
274 | |||
275 | /* | ||
276 | * Obtain password entry and change privileges | ||
277 | */ | ||
278 | pas = getpwnam(user); | ||
279 | if (pas == 0) { | ||
280 | crondlog("\011failed to get uid for %s", user); | ||
281 | return -1; | ||
282 | } | ||
283 | setenv("USER", pas->pw_name, 1); | ||
284 | setenv("HOME", pas->pw_dir, 1); | ||
285 | setenv("SHELL", DEFAULT_SHELL, 1); | ||
286 | |||
287 | /* | ||
288 | * Change running state to the user in question | ||
289 | */ | ||
290 | err_msg = change_identity_e2str(pas); | ||
291 | if (err_msg) { | ||
292 | crondlog("\011%s for user %s", err_msg, user); | ||
293 | return -1; | ||
294 | } | ||
295 | if (chdir(pas->pw_dir) < 0) { | ||
296 | crondlog("\011chdir failed: %s: %m", pas->pw_dir); | ||
297 | if (chdir(TMPDIR) < 0) { | ||
298 | crondlog("\011chdir failed: %s: %m", TMPDIR); | ||
299 | return -1; | ||
300 | } | ||
301 | } | ||
302 | return pas->pw_uid; | ||
303 | } | ||
304 | |||
305 | static void startlogger(void) | ||
306 | { | ||
307 | if (LogFile == 0) { | ||
308 | openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON); | ||
309 | } | ||
310 | #if ENABLE_DEBUG_CROND_OPTION | ||
311 | else { /* test logfile */ | ||
312 | int logfd; | ||
313 | |||
314 | if ((logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600)) >= 0) { | ||
315 | close(logfd); | ||
316 | } else { | ||
317 | bb_perror_msg("failed to open log file '%s': ", LogFile); | ||
318 | } | ||
319 | } | ||
320 | #endif | ||
321 | } | ||
322 | |||
323 | |||
324 | static const char *const DowAry[] = { | ||
325 | "sun", | ||
326 | "mon", | ||
327 | "tue", | ||
328 | "wed", | ||
329 | "thu", | ||
330 | "fri", | ||
331 | "sat", | ||
332 | |||
333 | "Sun", | ||
334 | "Mon", | ||
335 | "Tue", | ||
336 | "Wed", | ||
337 | "Thu", | ||
338 | "Fri", | ||
339 | "Sat", | ||
340 | NULL | ||
341 | }; | ||
342 | |||
343 | static const char *const MonAry[] = { | ||
344 | "jan", | ||
345 | "feb", | ||
346 | "mar", | ||
347 | "apr", | ||
348 | "may", | ||
349 | "jun", | ||
350 | "jul", | ||
351 | "aug", | ||
352 | "sep", | ||
353 | "oct", | ||
354 | "nov", | ||
355 | "dec", | ||
356 | |||
357 | "Jan", | ||
358 | "Feb", | ||
359 | "Mar", | ||
360 | "Apr", | ||
361 | "May", | ||
362 | "Jun", | ||
363 | "Jul", | ||
364 | "Aug", | ||
365 | "Sep", | ||
366 | "Oct", | ||
367 | "Nov", | ||
368 | "Dec", | ||
369 | NULL | ||
370 | }; | ||
371 | |||
372 | static char *ParseField(char *user, char *ary, int modvalue, int off, | ||
373 | const char *const *names, char *ptr) | ||
374 | { | ||
375 | char *base = ptr; | ||
376 | int n1 = -1; | ||
377 | int n2 = -1; | ||
378 | |||
379 | if (base == NULL) { | ||
380 | return NULL; | ||
381 | } | ||
382 | |||
383 | while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { | ||
384 | int skip = 0; | ||
385 | |||
386 | /* Handle numeric digit or symbol or '*' */ | ||
387 | |||
388 | if (*ptr == '*') { | ||
389 | n1 = 0; /* everything will be filled */ | ||
390 | n2 = modvalue - 1; | ||
391 | skip = 1; | ||
392 | ++ptr; | ||
393 | } else if (*ptr >= '0' && *ptr <= '9') { | ||
394 | if (n1 < 0) { | ||
395 | n1 = strtol(ptr, &ptr, 10) + off; | ||
396 | } else { | ||
397 | n2 = strtol(ptr, &ptr, 10) + off; | ||
398 | } | ||
399 | skip = 1; | ||
400 | } else if (names) { | ||
401 | int i; | ||
402 | |||
403 | for (i = 0; names[i]; ++i) { | ||
404 | if (strncmp(ptr, names[i], strlen(names[i])) == 0) { | ||
405 | break; | ||
406 | } | ||
407 | } | ||
408 | if (names[i]) { | ||
409 | ptr += strlen(names[i]); | ||
410 | if (n1 < 0) { | ||
411 | n1 = i; | ||
412 | } else { | ||
413 | n2 = i; | ||
414 | } | ||
415 | skip = 1; | ||
416 | } | ||
417 | } | ||
418 | |||
419 | /* handle optional range '-' */ | ||
420 | |||
421 | if (skip == 0) { | ||
422 | crondlog("\111failed user %s parsing %s\n", user, base); | ||
423 | return NULL; | ||
424 | } | ||
425 | if (*ptr == '-' && n2 < 0) { | ||
426 | ++ptr; | ||
427 | continue; | ||
428 | } | ||
429 | |||
430 | /* | ||
431 | * collapse single-value ranges, handle skipmark, and fill | ||
432 | * in the character array appropriately. | ||
433 | */ | ||
434 | |||
435 | if (n2 < 0) { | ||
436 | n2 = n1; | ||
437 | } | ||
438 | if (*ptr == '/') { | ||
439 | skip = strtol(ptr + 1, &ptr, 10); | ||
440 | } | ||
441 | /* | ||
442 | * fill array, using a failsafe is the easiest way to prevent | ||
443 | * an endless loop | ||
444 | */ | ||
445 | |||
446 | { | ||
447 | int s0 = 1; | ||
448 | int failsafe = 1024; | ||
449 | |||
450 | --n1; | ||
451 | do { | ||
452 | n1 = (n1 + 1) % modvalue; | ||
453 | |||
454 | if (--s0 == 0) { | ||
455 | ary[n1 % modvalue] = 1; | ||
456 | s0 = skip; | ||
457 | } | ||
458 | } | ||
459 | while (n1 != n2 && --failsafe); | ||
460 | |||
461 | if (failsafe == 0) { | ||
462 | crondlog("\111failed user %s parsing %s\n", user, base); | ||
463 | return NULL; | ||
464 | } | ||
465 | } | ||
466 | if (*ptr != ',') { | ||
467 | break; | ||
468 | } | ||
469 | ++ptr; | ||
470 | n1 = -1; | ||
471 | n2 = -1; | ||
472 | } | ||
473 | |||
474 | if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { | ||
475 | crondlog("\111failed user %s parsing %s\n", user, base); | ||
476 | return NULL; | ||
477 | } | ||
478 | |||
479 | while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') { | ||
480 | ++ptr; | ||
481 | } | ||
482 | #if ENABLE_DEBUG_CROND_OPTION | ||
483 | if (DebugOpt) { | ||
484 | int i; | ||
485 | |||
486 | for (i = 0; i < modvalue; ++i) { | ||
487 | crondlog("\005%d", ary[i]); | ||
488 | } | ||
489 | crondlog("\005\n"); | ||
490 | } | ||
491 | #endif | ||
492 | |||
493 | return ptr; | ||
494 | } | ||
495 | |||
496 | static void FixDayDow(CronLine * line) | ||
497 | { | ||
498 | int i; | ||
499 | int weekUsed = 0; | ||
500 | int daysUsed = 0; | ||
501 | |||
502 | for (i = 0; i < (int)(arysize(line->cl_Dow)); ++i) { | ||
503 | if (line->cl_Dow[i] == 0) { | ||
504 | weekUsed = 1; | ||
505 | break; | ||
506 | } | ||
507 | } | ||
508 | for (i = 0; i < (int)(arysize(line->cl_Days)); ++i) { | ||
509 | if (line->cl_Days[i] == 0) { | ||
510 | daysUsed = 1; | ||
511 | break; | ||
512 | } | ||
513 | } | ||
514 | if (weekUsed && !daysUsed) { | ||
515 | memset(line->cl_Days, 0, sizeof(line->cl_Days)); | ||
516 | } | ||
517 | if (daysUsed && !weekUsed) { | ||
518 | memset(line->cl_Dow, 0, sizeof(line->cl_Dow)); | ||
519 | } | ||
520 | } | ||
521 | |||
522 | |||
523 | |||
524 | static void SynchronizeFile(const char *fileName) | ||
525 | { | ||
526 | int maxEntries = MAXLINES; | ||
527 | int maxLines; | ||
528 | char buf[1024]; | ||
529 | |||
530 | if (strcmp(fileName, "root") == 0) { | ||
531 | maxEntries = 65535; | ||
532 | } | ||
533 | maxLines = maxEntries * 10; | ||
534 | |||
535 | if (fileName) { | ||
536 | FILE *fi; | ||
537 | |||
538 | DeleteFile(fileName); | ||
539 | |||
540 | fi = fopen(fileName, "r"); | ||
541 | if (fi != NULL) { | ||
542 | struct stat sbuf; | ||
543 | |||
544 | if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) { | ||
545 | CronFile *file = calloc(1, sizeof(CronFile)); | ||
546 | CronLine **pline; | ||
547 | |||
548 | file->cf_User = strdup(fileName); | ||
549 | pline = &file->cf_LineBase; | ||
550 | |||
551 | while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) { | ||
552 | CronLine line; | ||
553 | char *ptr; | ||
554 | |||
555 | trim(buf); | ||
556 | if (buf[0] == 0 || buf[0] == '#') { | ||
557 | continue; | ||
558 | } | ||
559 | if (--maxEntries == 0) { | ||
560 | break; | ||
561 | } | ||
562 | memset(&line, 0, sizeof(line)); | ||
563 | |||
564 | #if ENABLE_DEBUG_CROND_OPTION | ||
565 | if (DebugOpt) { | ||
566 | crondlog("\111User %s Entry %s\n", fileName, buf); | ||
567 | } | ||
568 | #endif | ||
569 | |||
570 | /* parse date ranges */ | ||
571 | ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf); | ||
572 | ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr); | ||
573 | ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr); | ||
574 | ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr); | ||
575 | ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr); | ||
576 | |||
577 | /* check failure */ | ||
578 | if (ptr == NULL) { | ||
579 | continue; | ||
580 | } | ||
581 | |||
582 | /* | ||
583 | * fix days and dow - if one is not * and the other | ||
584 | * is *, the other is set to 0, and vise-versa | ||
585 | */ | ||
586 | |||
587 | FixDayDow(&line); | ||
588 | |||
589 | *pline = calloc(1, sizeof(CronLine)); | ||
590 | **pline = line; | ||
591 | |||
592 | /* copy command */ | ||
593 | (*pline)->cl_Shell = strdup(ptr); | ||
594 | |||
595 | #if ENABLE_DEBUG_CROND_OPTION | ||
596 | if (DebugOpt) { | ||
597 | crondlog("\111 Command %s\n", ptr); | ||
598 | } | ||
599 | #endif | ||
600 | |||
601 | pline = &((*pline)->cl_Next); | ||
602 | } | ||
603 | *pline = NULL; | ||
604 | |||
605 | file->cf_Next = FileBase; | ||
606 | FileBase = file; | ||
607 | |||
608 | if (maxLines == 0 || maxEntries == 0) { | ||
609 | crondlog("\111Maximum number of lines reached for user %s\n", fileName); | ||
610 | } | ||
611 | } | ||
612 | fclose(fi); | ||
613 | } | ||
614 | } | ||
615 | } | ||
616 | |||
617 | static void CheckUpdates(void) | ||
618 | { | ||
619 | FILE *fi; | ||
620 | char buf[256]; | ||
621 | |||
622 | fi = fopen(CRONUPDATE, "r"); | ||
623 | if (fi != NULL) { | ||
624 | remove(CRONUPDATE); | ||
625 | while (fgets(buf, sizeof(buf), fi) != NULL) { | ||
626 | SynchronizeFile(strtok(buf, " \t\r\n")); | ||
627 | } | ||
628 | fclose(fi); | ||
629 | } | ||
630 | } | ||
631 | |||
632 | static void SynchronizeDir(void) | ||
633 | { | ||
634 | /* Attempt to delete the database. */ | ||
635 | |||
636 | for (;;) { | ||
637 | CronFile *file; | ||
638 | |||
639 | for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next); | ||
640 | if (file == NULL) { | ||
641 | break; | ||
642 | } | ||
643 | DeleteFile(file->cf_User); | ||
644 | } | ||
645 | |||
646 | /* | ||
647 | * Remove cron update file | ||
648 | * | ||
649 | * Re-chdir, in case directory was renamed & deleted, or otherwise | ||
650 | * screwed up. | ||
651 | * | ||
652 | * scan directory and add associated users | ||
653 | */ | ||
654 | |||
655 | remove(CRONUPDATE); | ||
656 | if (chdir(CDir) < 0) { | ||
657 | crondlog("\311cannot find %s\n", CDir); | ||
658 | } | ||
659 | { | ||
660 | DIR *dir = opendir("."); | ||
661 | struct dirent *den; | ||
662 | |||
663 | if (dir) { | ||
664 | while ((den = readdir(dir))) { | ||
665 | if (strchr(den->d_name, '.') != NULL) { | ||
666 | continue; | ||
667 | } | ||
668 | if (getpwnam(den->d_name)) { | ||
669 | SynchronizeFile(den->d_name); | ||
670 | } else { | ||
671 | crondlog("\007ignoring %s\n", den->d_name); | ||
672 | } | ||
673 | } | ||
674 | closedir(dir); | ||
675 | } else { | ||
676 | crondlog("\311cannot open current dir!\n"); | ||
677 | } | ||
678 | } | ||
679 | } | ||
680 | |||
681 | |||
682 | /* | ||
683 | * DeleteFile() - delete user database | ||
684 | * | ||
685 | * Note: multiple entries for same user may exist if we were unable to | ||
686 | * completely delete a database due to running processes. | ||
687 | */ | ||
688 | |||
689 | static void DeleteFile(const char *userName) | ||
690 | { | ||
691 | CronFile **pfile = &FileBase; | ||
692 | CronFile *file; | ||
693 | |||
694 | while ((file = *pfile) != NULL) { | ||
695 | if (strcmp(userName, file->cf_User) == 0) { | ||
696 | CronLine **pline = &file->cf_LineBase; | ||
697 | CronLine *line; | ||
698 | |||
699 | file->cf_Running = 0; | ||
700 | file->cf_Deleted = 1; | ||
701 | |||
702 | while ((line = *pline) != NULL) { | ||
703 | if (line->cl_Pid > 0) { | ||
704 | file->cf_Running = 1; | ||
705 | pline = &line->cl_Next; | ||
706 | } else { | ||
707 | *pline = line->cl_Next; | ||
708 | free(line->cl_Shell); | ||
709 | free(line); | ||
710 | } | ||
711 | } | ||
712 | if (file->cf_Running == 0) { | ||
713 | *pfile = file->cf_Next; | ||
714 | free(file->cf_User); | ||
715 | free(file); | ||
716 | } else { | ||
717 | pfile = &file->cf_Next; | ||
718 | } | ||
719 | } else { | ||
720 | pfile = &file->cf_Next; | ||
721 | } | ||
722 | } | ||
723 | } | ||
724 | |||
725 | /* | ||
726 | * TestJobs() | ||
727 | * | ||
728 | * determine which jobs need to be run. Under normal conditions, the | ||
729 | * period is about a minute (one scan). Worst case it will be one | ||
730 | * hour (60 scans). | ||
731 | */ | ||
732 | |||
733 | static int TestJobs(time_t t1, time_t t2) | ||
734 | { | ||
735 | int nJobs = 0; | ||
736 | time_t t; | ||
737 | |||
738 | /* Find jobs > t1 and <= t2 */ | ||
739 | |||
740 | for (t = t1 - t1 % 60; t <= t2; t += 60) { | ||
741 | if (t > t1) { | ||
742 | struct tm *tp = localtime(&t); | ||
743 | CronFile *file; | ||
744 | CronLine *line; | ||
745 | |||
746 | for (file = FileBase; file; file = file->cf_Next) { | ||
747 | #if ENABLE_DEBUG_CROND_OPTION | ||
748 | if (DebugOpt) | ||
749 | crondlog("\005FILE %s:\n", file->cf_User); | ||
750 | #endif | ||
751 | if (file->cf_Deleted) | ||
752 | continue; | ||
753 | for (line = file->cf_LineBase; line; line = line->cl_Next) { | ||
754 | #if ENABLE_DEBUG_CROND_OPTION | ||
755 | if (DebugOpt) | ||
756 | crondlog("\005 LINE %s\n", line->cl_Shell); | ||
757 | #endif | ||
758 | if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] && | ||
759 | (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) | ||
760 | && line->cl_Mons[tp->tm_mon]) { | ||
761 | #if ENABLE_DEBUG_CROND_OPTION | ||
762 | if (DebugOpt) { | ||
763 | crondlog("\005 JobToDo: %d %s\n", | ||
764 | line->cl_Pid, line->cl_Shell); | ||
765 | } | ||
766 | #endif | ||
767 | if (line->cl_Pid > 0) { | ||
768 | crondlog("\010 process already running: %s %s\n", | ||
769 | file->cf_User, line->cl_Shell); | ||
770 | } else if (line->cl_Pid == 0) { | ||
771 | line->cl_Pid = -1; | ||
772 | file->cf_Ready = 1; | ||
773 | ++nJobs; | ||
774 | } | ||
775 | } | ||
776 | } | ||
777 | } | ||
778 | } | ||
779 | } | ||
780 | return nJobs; | ||
781 | } | ||
782 | |||
783 | static void RunJobs(void) | ||
784 | { | ||
785 | CronFile *file; | ||
786 | CronLine *line; | ||
787 | |||
788 | for (file = FileBase; file; file = file->cf_Next) { | ||
789 | if (file->cf_Ready) { | ||
790 | file->cf_Ready = 0; | ||
791 | |||
792 | for (line = file->cf_LineBase; line; line = line->cl_Next) { | ||
793 | if (line->cl_Pid < 0) { | ||
794 | |||
795 | RunJob(file->cf_User, line); | ||
796 | |||
797 | crondlog("\010USER %s pid %3d cmd %s\n", | ||
798 | file->cf_User, line->cl_Pid, line->cl_Shell); | ||
799 | if (line->cl_Pid < 0) { | ||
800 | file->cf_Ready = 1; | ||
801 | } | ||
802 | else if (line->cl_Pid > 0) { | ||
803 | file->cf_Running = 1; | ||
804 | } | ||
805 | } | ||
806 | } | ||
807 | } | ||
808 | } | ||
809 | } | ||
810 | |||
811 | /* | ||
812 | * CheckJobs() - check for job completion | ||
813 | * | ||
814 | * Check for job completion, return number of jobs still running after | ||
815 | * all done. | ||
816 | */ | ||
817 | |||
818 | static int CheckJobs(void) | ||
819 | { | ||
820 | CronFile *file; | ||
821 | CronLine *line; | ||
822 | int nStillRunning = 0; | ||
823 | |||
824 | for (file = FileBase; file; file = file->cf_Next) { | ||
825 | if (file->cf_Running) { | ||
826 | file->cf_Running = 0; | ||
827 | |||
828 | for (line = file->cf_LineBase; line; line = line->cl_Next) { | ||
829 | if (line->cl_Pid > 0) { | ||
830 | int status; | ||
831 | int r = wait4(line->cl_Pid, &status, WNOHANG, NULL); | ||
832 | |||
833 | if (r < 0 || r == line->cl_Pid) { | ||
834 | EndJob(file->cf_User, line); | ||
835 | if (line->cl_Pid) { | ||
836 | file->cf_Running = 1; | ||
837 | } | ||
838 | } else if (r == 0) { | ||
839 | file->cf_Running = 1; | ||
840 | } | ||
841 | } | ||
842 | } | ||
843 | } | ||
844 | nStillRunning += file->cf_Running; | ||
845 | } | ||
846 | return nStillRunning; | ||
847 | } | ||
848 | |||
849 | |||
850 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | ||
851 | static void | ||
852 | ForkJob(const char *user, CronLine * line, int mailFd, | ||
853 | const char *prog, const char *cmd, const char *arg, const char *mailf) | ||
854 | { | ||
855 | /* Fork as the user in question and run program */ | ||
856 | pid_t pid = fork(); | ||
857 | |||
858 | line->cl_Pid = pid; | ||
859 | if (pid == 0) { | ||
860 | /* CHILD */ | ||
861 | |||
862 | /* Change running state to the user in question */ | ||
863 | |||
864 | if (ChangeUser(user) < 0) { | ||
865 | exit(0); | ||
866 | } | ||
867 | #if ENABLE_DEBUG_CROND_OPTION | ||
868 | if (DebugOpt) { | ||
869 | crondlog("\005Child Running %s\n", prog); | ||
870 | } | ||
871 | #endif | ||
872 | |||
873 | if (mailFd >= 0) { | ||
874 | dup2(mailFd, mailf != NULL); | ||
875 | dup2((mailf ? mailFd : 1), 2); | ||
876 | close(mailFd); | ||
877 | } | ||
878 | execl(prog, prog, cmd, arg, NULL); | ||
879 | crondlog("\024cannot exec, user %s cmd %s %s %s\n", user, prog, cmd, arg); | ||
880 | if (mailf) { | ||
881 | fdprintf(1, "Exec failed: %s -c %s\n", prog, arg); | ||
882 | } | ||
883 | exit(0); | ||
884 | } else if (pid < 0) { | ||
885 | /* FORK FAILED */ | ||
886 | crondlog("\024cannot fork, user %s\n", user); | ||
887 | line->cl_Pid = 0; | ||
888 | if (mailf) { | ||
889 | remove(mailf); | ||
890 | } | ||
891 | } else if (mailf) { | ||
892 | /* PARENT, FORK SUCCESS | ||
893 | * rename mail-file based on pid of process | ||
894 | */ | ||
895 | char mailFile2[128]; | ||
896 | |||
897 | snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid); | ||
898 | rename(mailf, mailFile2); | ||
899 | } | ||
900 | /* | ||
901 | * Close the mail file descriptor.. we can't just leave it open in | ||
902 | * a structure, closing it later, because we might run out of descriptors | ||
903 | */ | ||
904 | |||
905 | if (mailFd >= 0) { | ||
906 | close(mailFd); | ||
907 | } | ||
908 | } | ||
909 | |||
910 | static void RunJob(const char *user, CronLine * line) | ||
911 | { | ||
912 | char mailFile[128]; | ||
913 | int mailFd; | ||
914 | |||
915 | line->cl_Pid = 0; | ||
916 | line->cl_MailFlag = 0; | ||
917 | |||
918 | /* open mail file - owner root so nobody can screw with it. */ | ||
919 | |||
920 | snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, getpid()); | ||
921 | mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); | ||
922 | |||
923 | if (mailFd >= 0) { | ||
924 | line->cl_MailFlag = 1; | ||
925 | fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user, | ||
926 | line->cl_Shell); | ||
927 | line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR); | ||
928 | } else { | ||
929 | crondlog("\024cannot create mail file user %s file %s, output to /dev/null\n", user, mailFile); | ||
930 | } | ||
931 | |||
932 | ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile); | ||
933 | } | ||
934 | |||
935 | /* | ||
936 | * EndJob - called when job terminates and when mail terminates | ||
937 | */ | ||
938 | |||
939 | static void EndJob(const char *user, CronLine * line) | ||
940 | { | ||
941 | int mailFd; | ||
942 | char mailFile[128]; | ||
943 | struct stat sbuf; | ||
944 | |||
945 | /* No job */ | ||
946 | |||
947 | if (line->cl_Pid <= 0) { | ||
948 | line->cl_Pid = 0; | ||
949 | return; | ||
950 | } | ||
951 | |||
952 | /* | ||
953 | * End of job and no mail file | ||
954 | * End of sendmail job | ||
955 | */ | ||
956 | |||
957 | snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, line->cl_Pid); | ||
958 | line->cl_Pid = 0; | ||
959 | |||
960 | if (line->cl_MailFlag != 1) { | ||
961 | return; | ||
962 | } | ||
963 | line->cl_MailFlag = 0; | ||
964 | |||
965 | /* | ||
966 | * End of primary job - check for mail file. If size has increased and | ||
967 | * the file is still valid, we sendmail it. | ||
968 | */ | ||
969 | |||
970 | mailFd = open(mailFile, O_RDONLY); | ||
971 | remove(mailFile); | ||
972 | if (mailFd < 0) { | ||
973 | return; | ||
974 | } | ||
975 | |||
976 | if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid || sbuf.st_nlink != 0 || | ||
977 | sbuf.st_size == line->cl_MailPos || !S_ISREG(sbuf.st_mode)) { | ||
978 | close(mailFd); | ||
979 | return; | ||
980 | } | ||
981 | ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL); | ||
982 | } | ||
983 | #else | ||
984 | /* crond without sendmail */ | ||
985 | |||
986 | static void RunJob(const char *user, CronLine * line) | ||
987 | { | ||
988 | /* Fork as the user in question and run program */ | ||
989 | pid_t pid = fork(); | ||
990 | |||
991 | if (pid == 0) { | ||
992 | /* CHILD */ | ||
993 | |||
994 | /* Change running state to the user in question */ | ||
995 | |||
996 | if (ChangeUser(user) < 0) { | ||
997 | exit(0); | ||
998 | } | ||
999 | #if ENABLE_DEBUG_CROND_OPTION | ||
1000 | if (DebugOpt) { | ||
1001 | crondlog("\005Child Running %s\n", DEFAULT_SHELL); | ||
1002 | } | ||
1003 | #endif | ||
1004 | |||
1005 | execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL); | ||
1006 | crondlog("\024cannot exec, user %s cmd %s -c %s\n", user, | ||
1007 | DEFAULT_SHELL, line->cl_Shell); | ||
1008 | exit(0); | ||
1009 | } else if (pid < 0) { | ||
1010 | /* FORK FAILED */ | ||
1011 | crondlog("\024cannot, user %s\n", user); | ||
1012 | pid = 0; | ||
1013 | } | ||
1014 | line->cl_Pid = pid; | ||
1015 | } | ||
1016 | #endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */ | ||