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