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