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