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