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