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