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