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