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