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