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