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