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