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