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