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