destroy bug [0000381]
[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, 0600);
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_complementally = "f-b:b-f:S-L:L-S:d-l";
161 #else
162         bb_opt_complementally = "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, 0600)) >= 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                                         trim(buf);
593                                         if (buf[0] == 0 || buf[0] == '#') {
594                                                 continue;
595                                         }
596                                         if (--maxEntries == 0) {
597                                                 break;
598                                         }
599                                         memset(&line, 0, sizeof(line));
600
601 #ifdef FEATURE_DEBUG_OPT
602                                         if (DebugOpt) {
603                                                 crondlog("\111User %s Entry %s\n", fileName, buf);
604                                         }
605 #endif
606
607                                         /* parse date ranges */
608                                         ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
609                                         ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
610                                         ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
611                                         ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
612                                         ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
613
614                                         /* check failure */
615                                         if (ptr == NULL) {
616                                                 continue;
617                                         }
618
619                                         /*
620                                          * fix days and dow - if one is not * and the other
621                                          * is *, the other is set to 0, and vise-versa
622                                          */
623
624                                         FixDayDow(&line);
625
626                                         *pline = calloc(1, sizeof(CronLine));
627                                         **pline = line;
628
629                                         /* copy command */
630                                         (*pline)->cl_Shell = strdup(ptr);
631
632 #ifdef FEATURE_DEBUG_OPT
633                                         if (DebugOpt) {
634                                                 crondlog("\111    Command %s\n", ptr);
635                                         }
636 #endif
637
638                                         pline = &((*pline)->cl_Next);
639                                 }
640                                 *pline = NULL;
641
642                                 file->cf_Next = FileBase;
643                                 FileBase = file;
644
645                                 if (maxLines == 0 || maxEntries == 0) {
646                                         crondlog("\111Maximum number of lines reached for user %s\n", fileName);
647                                 }
648                         }
649                         fclose(fi);
650                 }
651         }
652 }
653
654 static void CheckUpdates(void)
655 {
656         FILE *fi;
657         char buf[256];
658
659         fi = fopen(CRONUPDATE, "r");
660         if (fi != NULL) {
661                 remove(CRONUPDATE);
662                 while (fgets(buf, sizeof(buf), fi) != NULL) {
663                         SynchronizeFile(strtok(buf, " \t\r\n"));
664                 }
665                 fclose(fi);
666         }
667 }
668
669 static void SynchronizeDir(void)
670 {
671         /* Attempt to delete the database. */
672
673         for (;;) {
674                 CronFile *file;
675
676                 for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next);
677                 if (file == NULL) {
678                         break;
679                 }
680                 DeleteFile(file->cf_User);
681         }
682
683         /*
684          * Remove cron update file
685          *
686          * Re-chdir, in case directory was renamed & deleted, or otherwise
687          * screwed up.
688          *
689          * scan directory and add associated users
690          */
691
692         remove(CRONUPDATE);
693         if (chdir(CDir) < 0) {
694                 crondlog("\311unable to find %s\n", CDir);
695         }
696         {
697                 DIR *dir = opendir(".");
698                 struct dirent *den;
699
700                 if (dir) {
701                         while ((den = readdir(dir))) {
702                                 if (strchr(den->d_name, '.') != NULL) {
703                                         continue;
704                                 }
705                                 if (getpwnam(den->d_name)) {
706                                         SynchronizeFile(den->d_name);
707                                 } else {
708                                         crondlog("\007ignoring %s\n", den->d_name);
709                                 }
710                         }
711                         closedir(dir);
712                 } else {
713                         crondlog("\311Unable to open current dir!\n");
714                 }
715         }
716 }
717
718
719 /*
720  *  DeleteFile() - delete user database
721  *
722  *  Note: multiple entries for same user may exist if we were unable to
723  *  completely delete a database due to running processes.
724  */
725
726 static void DeleteFile(const char *userName)
727 {
728         CronFile **pfile = &FileBase;
729         CronFile *file;
730
731         while ((file = *pfile) != NULL) {
732                 if (strcmp(userName, file->cf_User) == 0) {
733                         CronLine **pline = &file->cf_LineBase;
734                         CronLine *line;
735
736                         file->cf_Running = 0;
737                         file->cf_Deleted = 1;
738
739                         while ((line = *pline) != NULL) {
740                                 if (line->cl_Pid > 0) {
741                                         file->cf_Running = 1;
742                                         pline = &line->cl_Next;
743                                 } else {
744                                         *pline = line->cl_Next;
745                                         free(line->cl_Shell);
746                                         free(line);
747                                 }
748                         }
749                         if (file->cf_Running == 0) {
750                                 *pfile = file->cf_Next;
751                                 free(file->cf_User);
752                                 free(file);
753                         } else {
754                                 pfile = &file->cf_Next;
755                         }
756                 } else {
757                         pfile = &file->cf_Next;
758                 }
759         }
760 }
761
762 /*
763  * TestJobs()
764  *
765  * determine which jobs need to be run.  Under normal conditions, the
766  * period is about a minute (one scan).  Worst case it will be one
767  * hour (60 scans).
768  */
769
770 static int TestJobs(time_t t1, time_t t2)
771 {
772         short nJobs = 0;
773         time_t t;
774
775         /* Find jobs > t1 and <= t2 */
776
777         for (t = t1 - t1 % 60; t <= t2; t += 60) {
778                 if (t > t1) {
779                         struct tm *tp = localtime(&t);
780                         CronFile *file;
781                         CronLine *line;
782
783                         for (file = FileBase; file; file = file->cf_Next) {
784 #ifdef FEATURE_DEBUG_OPT
785                                 if (DebugOpt)
786                                         crondlog("\005FILE %s:\n", file->cf_User);
787 #endif
788                                 if (file->cf_Deleted)
789                                         continue;
790                                 for (line = file->cf_LineBase; line; line = line->cl_Next) {
791 #ifdef FEATURE_DEBUG_OPT
792                                         if (DebugOpt)
793                                                 crondlog("\005    LINE %s\n", line->cl_Shell);
794 #endif
795                                         if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] &&
796                                                 (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday])
797                                                 && line->cl_Mons[tp->tm_mon]) {
798 #ifdef FEATURE_DEBUG_OPT
799                                                 if (DebugOpt) {
800                                                         crondlog("\005    JobToDo: %d %s\n",
801                                                                 line->cl_Pid, line->cl_Shell);
802                                                 }
803 #endif
804                                                 if (line->cl_Pid > 0) {
805                                                         crondlog("\010    process already running: %s %s\n",
806                                                                 file->cf_User, line->cl_Shell);
807                                                 } else if (line->cl_Pid == 0) {
808                                                         line->cl_Pid = -1;
809                                                         file->cf_Ready = 1;
810                                                         ++nJobs;
811                                                 }
812                                         }
813                                 }
814                         }
815                 }
816         }
817         return (nJobs);
818 }
819
820 static void RunJobs(void)
821 {
822         CronFile *file;
823         CronLine *line;
824
825         for (file = FileBase; file; file = file->cf_Next) {
826                 if (file->cf_Ready) {
827                         file->cf_Ready = 0;
828
829                         for (line = file->cf_LineBase; line; line = line->cl_Next) {
830                                 if (line->cl_Pid < 0) {
831
832                                         RunJob(file->cf_User, line);
833
834                                         crondlog("\010USER %s pid %3d cmd %s\n",
835                                                 file->cf_User, line->cl_Pid, line->cl_Shell);
836                                         if (line->cl_Pid < 0) {
837                                                 file->cf_Ready = 1;
838                                         }
839                                         else if (line->cl_Pid > 0) {
840                                                 file->cf_Running = 1;
841                                         }
842                                 }
843                         }
844                 }
845         }
846 }
847
848 /*
849  * CheckJobs() - check for job completion
850  *
851  * Check for job completion, return number of jobs still running after
852  * all done.
853  */
854
855 static int CheckJobs(void)
856 {
857         CronFile *file;
858         CronLine *line;
859         int nStillRunning = 0;
860
861         for (file = FileBase; file; file = file->cf_Next) {
862                 if (file->cf_Running) {
863                         file->cf_Running = 0;
864
865                         for (line = file->cf_LineBase; line; line = line->cl_Next) {
866                                 if (line->cl_Pid > 0) {
867                                         int status;
868                                         int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
869
870                                         if (r < 0 || r == line->cl_Pid) {
871                                                 EndJob(file->cf_User, line);
872                                                 if (line->cl_Pid) {
873                                                         file->cf_Running = 1;
874                                                 }
875                                         } else if (r == 0) {
876                                                 file->cf_Running = 1;
877                                         }
878                                 }
879                         }
880                 }
881                 nStillRunning += file->cf_Running;
882         }
883         return (nStillRunning);
884 }
885
886
887 #ifdef CONFIG_FEATURE_CROND_CALL_SENDMAIL
888 static void
889 ForkJob(const char *user, CronLine * line, int mailFd,
890                 const char *prog, const char *cmd, const char *arg, const char *mailf)
891 {
892         /* Fork as the user in question and run program */
893         pid_t pid = fork();
894
895         line->cl_Pid = pid;
896         if (pid == 0) {
897                 /* CHILD */
898
899                 /* Change running state to the user in question */
900
901                 if (ChangeUser(user) < 0) {
902                         exit(0);
903                 }
904 #ifdef FEATURE_DEBUG_OPT
905                 if (DebugOpt) {
906                         crondlog("\005Child Running %s\n", prog);
907                 }
908 #endif
909
910                 if (mailFd >= 0) {
911                         dup2(mailFd, mailf != NULL);
912                         dup2((mailf ? mailFd : 1), 2);
913                         close(mailFd);
914                 }
915                 execl(prog, prog, cmd, arg, NULL);
916                 crondlog("\024unable to exec, user %s cmd %s %s %s\n", user, prog, cmd, arg);
917                 if (mailf) {
918                         fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
919                 }
920                 exit(0);
921         } else if (pid < 0) {
922                 /* FORK FAILED */
923                 crondlog("\024couldn't fork, user %s\n", user);
924                 line->cl_Pid = 0;
925                 if (mailf) {
926                         remove(mailf);
927                 }
928         } else if (mailf) {
929                 /* PARENT, FORK SUCCESS
930                  * rename mail-file based on pid of process
931                  */
932                 char mailFile2[128];
933
934                 snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid);
935                 rename(mailf, mailFile2);
936         }
937         /*
938          * Close the mail file descriptor.. we can't just leave it open in
939          * a structure, closing it later, because we might run out of descriptors
940          */
941
942         if (mailFd >= 0) {
943                 close(mailFd);
944         }
945 }
946
947 static void RunJob(const char *user, CronLine * line)
948 {
949         char mailFile[128];
950         int mailFd;
951
952         line->cl_Pid = 0;
953         line->cl_MailFlag = 0;
954
955         /* open mail file - owner root so nobody can screw with it. */
956
957         snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, getpid());
958         mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
959
960         if (mailFd >= 0) {
961                 line->cl_MailFlag = 1;
962                 fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,
963                         line->cl_Shell);
964                 line->cl_MailPos = lseek(mailFd, 0, 1);
965         } else {
966                 crondlog("\024unable to create mail file user %s file %s, output to /dev/null\n", user, mailFile);
967         }
968
969         ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
970 }
971
972 /*
973  * EndJob - called when job terminates and when mail terminates
974  */
975
976 static void EndJob(const char *user, CronLine * line)
977 {
978         int mailFd;
979         char mailFile[128];
980         struct stat sbuf;
981
982         /* No job */
983
984         if (line->cl_Pid <= 0) {
985                 line->cl_Pid = 0;
986                 return;
987         }
988
989         /*
990          * End of job and no mail file
991          * End of sendmail job
992          */
993
994         snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, line->cl_Pid);
995         line->cl_Pid = 0;
996
997         if (line->cl_MailFlag != 1) {
998                 return;
999         }
1000         line->cl_MailFlag = 0;
1001
1002         /*
1003          * End of primary job - check for mail file.  If size has increased and
1004          * the file is still valid, we sendmail it.
1005          */
1006
1007         mailFd = open(mailFile, O_RDONLY);
1008         remove(mailFile);
1009         if (mailFd < 0) {
1010                 return;
1011         }
1012
1013         if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid ||     sbuf.st_nlink != 0 ||
1014                 sbuf.st_size == line->cl_MailPos || !S_ISREG(sbuf.st_mode)) {
1015                 close(mailFd);
1016                 return;
1017         }
1018         ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
1019 }
1020 #else
1021 /* crond without sendmail */
1022
1023 static void RunJob(const char *user, CronLine * line)
1024 {
1025         /* Fork as the user in question and run program */
1026         pid_t pid = fork();
1027
1028         if (pid == 0) {
1029                 /* CHILD */
1030
1031                 /* Change running state to the user in question */
1032
1033                 if (ChangeUser(user) < 0) {
1034                         exit(0);
1035                 }
1036 #ifdef FEATURE_DEBUG_OPT
1037                 if (DebugOpt) {
1038                         crondlog("\005Child Running %s\n", DEFAULT_SHELL);
1039                 }
1040 #endif
1041
1042                 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
1043                 crondlog("\024unable to exec, user %s cmd %s -c %s\n", user,
1044                                  DEFAULT_SHELL, line->cl_Shell);
1045                 exit(0);
1046         } else if (pid < 0) {
1047                 /* FORK FAILED */
1048                 crondlog("\024couldn't fork, user %s\n", user);
1049                 pid = 0;
1050         }
1051         line->cl_Pid = pid;
1052 }
1053 #endif                                                  /* CONFIG_FEATURE_CROND_CALL_SENDMAIL */