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