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