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