481830f4d61751dc26c7c146f0dd0d1e95cb6e09
[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 "libbb.h"
15 #include <syslog.h>
16
17 /* glibc frees previous setenv'ed value when we do next setenv()
18  * of the same variable. uclibc does not do this! */
19 #if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */
20 # define SETENV_LEAKS 0
21 #else
22 # define SETENV_LEAKS 1
23 #endif
24
25
26 #define TMPDIR          CONFIG_FEATURE_CROND_DIR
27 #define CRONTABS        CONFIG_FEATURE_CROND_DIR "/crontabs"
28 #ifndef SENDMAIL
29 # define SENDMAIL       "sendmail"
30 #endif
31 #ifndef SENDMAIL_ARGS
32 # define SENDMAIL_ARGS  "-ti", NULL
33 #endif
34 #ifndef CRONUPDATE
35 # define CRONUPDATE     "cron.update"
36 #endif
37 #ifndef MAXLINES
38 # define MAXLINES       256     /* max lines in non-root crontabs */
39 #endif
40
41
42 typedef struct CronFile {
43         struct CronFile *cf_Next;
44         struct CronLine *cf_LineBase;
45         char *cf_User;                  /* username                     */
46         smallint cf_Ready;              /* bool: one or more jobs ready */
47         smallint cf_Running;            /* bool: one or more jobs running */
48         smallint cf_Deleted;            /* marked for deletion, ignore  */
49 } CronFile;
50
51 typedef struct CronLine {
52         struct CronLine *cl_Next;
53         char *cl_Shell;         /* shell command                        */
54         pid_t cl_Pid;           /* running pid, 0, or armed (-1)        */
55 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
56         int cl_MailPos;         /* 'empty file' size                    */
57         char *cl_MailTo;        /* whom to mail results                 */
58         smallint cl_MailFlag;   /* running pid is for mail              */
59 #endif
60         /* ordered by size, not in natural order. makes code smaller: */
61         char cl_Dow[7];         /* 0-6, beginning sunday                */
62         char cl_Mons[12];       /* 0-11                                 */
63         char cl_Hrs[24];        /* 0-23                                 */
64         char cl_Days[32];       /* 1-31                                 */
65         char cl_Mins[60];       /* 0-59                                 */
66 } CronLine;
67
68
69 #define DaemonUid 0
70
71
72 enum {
73         OPT_l = (1 << 0),
74         OPT_L = (1 << 1),
75         OPT_f = (1 << 2),
76         OPT_b = (1 << 3),
77         OPT_S = (1 << 4),
78         OPT_c = (1 << 5),
79         OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D,
80 };
81 #if ENABLE_FEATURE_CROND_D
82 #define DebugOpt (option_mask32 & OPT_d)
83 #else
84 #define DebugOpt 0
85 #endif
86
87
88 struct globals {
89         unsigned LogLevel; /* = 8; */
90         time_t CDir_mtime;
91         const char *LogFile;
92         const char *CDir; /* = CRONTABS; */
93         CronFile *FileBase;
94 #if SETENV_LEAKS
95         char *env_var_user;
96         char *env_var_home;
97 #endif
98 } FIX_ALIASING;
99 #define G (*(struct globals*)&bb_common_bufsiz1)
100 #define INIT_G() do { \
101         G.LogLevel = 8; \
102         G.CDir = CRONTABS; \
103 } while (0)
104
105
106 /* 0 is the most verbose, default 8 */
107 #define LVL5  "\x05"
108 #define LVL7  "\x07"
109 #define LVL8  "\x08"
110 #define WARN9 "\x49"
111 #define DIE9  "\xc9"
112 /* level >= 20 is "error" */
113 #define ERR20 "\x14"
114
115 static void crondlog(const char *ctl, ...) __attribute__ ((format (printf, 1, 2)));
116 static void crondlog(const char *ctl, ...)
117 {
118         va_list va;
119         int level = (ctl[0] & 0x1f);
120
121         va_start(va, ctl);
122         if (level >= (int)G.LogLevel) {
123                 /* Debug mode: all to (non-redirected) stderr, */
124                 /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */
125                 if (!DebugOpt && G.LogFile) {
126                         /* Otherwise (log to file): we reopen log file at every write: */
127                         int logfd = open3_or_warn(G.LogFile, O_WRONLY | O_CREAT | O_APPEND, 0666);
128                         if (logfd >= 0)
129                                 xmove_fd(logfd, STDERR_FILENO);
130                 }
131                 /* When we log to syslog, level > 8 is logged at LOG_ERR
132                  * syslog level, level <= 8 is logged at LOG_INFO. */
133                 if (level > 8) {
134                         bb_verror_msg(ctl + 1, va, /* strerr: */ NULL);
135                 } else {
136                         char *msg = NULL;
137                         vasprintf(&msg, ctl + 1, va);
138                         bb_info_msg("%s: %s", applet_name, msg);
139                         free(msg);
140                 }
141         }
142         va_end(va);
143         if (ctl[0] & 0x80)
144                 exit(20);
145 }
146
147 #if SETENV_LEAKS
148 /* We set environment *before* vfork (because we want to use vfork),
149  * so we cannot use setenv() - repeated calls to setenv() may leak memory!
150  * Using putenv(), and freeing memory after unsetenv() won't leak */
151 static void safe_setenv(char **pvar_val, const char *var, const char *val)
152 {
153         char *var_val = *pvar_val;
154
155         if (var_val) {
156                 bb_unsetenv_and_free(var_val);
157         }
158         *pvar_val = xasprintf("%s=%s", var, val);
159         putenv(*pvar_val);
160 }
161 #endif
162
163 static void SetEnv(struct passwd *pas)
164 {
165 #if SETENV_LEAKS
166         safe_setenv(&G.env_var_user, "USER", pas->pw_name);
167         safe_setenv(&G.env_var_home, "HOME", pas->pw_dir);
168         /* if we want to set user's shell instead: */
169         /*safe_setenv(G.env_var_shell, "SHELL", pas->pw_shell);*/
170 #else
171         xsetenv("USER", pas->pw_name);
172         xsetenv("HOME", pas->pw_dir);
173 #endif
174         /* currently, we use constant one: */
175         /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */
176 }
177
178 static void ChangeUser(struct passwd *pas)
179 {
180         /* careful: we're after vfork! */
181         change_identity(pas); /* - initgroups, setgid, setuid */
182         if (chdir(pas->pw_dir) < 0) {
183                 crondlog(WARN9 "chdir(%s)", pas->pw_dir);
184                 if (chdir(TMPDIR) < 0) {
185                         crondlog(DIE9 "chdir(%s)", TMPDIR); /* exits */
186                 }
187         }
188 }
189
190 static const char DowAry[] ALIGN1 =
191         "sun""mon""tue""wed""thu""fri""sat"
192         /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */
193 ;
194
195 static const char MonAry[] ALIGN1 =
196         "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
197         /* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */
198 ;
199
200 static void ParseField(char *user, char *ary, int modvalue, int off,
201                                 const char *names, char *ptr)
202 /* 'names' is a pointer to a set of 3-char abbreviations */
203 {
204         char *base = ptr;
205         int n1 = -1;
206         int n2 = -1;
207
208         // this can't happen due to config_read()
209         /*if (base == NULL)
210                 return;*/
211
212         while (1) {
213                 int skip = 0;
214
215                 /* Handle numeric digit or symbol or '*' */
216                 if (*ptr == '*') {
217                         n1 = 0;         /* everything will be filled */
218                         n2 = modvalue - 1;
219                         skip = 1;
220                         ++ptr;
221                 } else if (isdigit(*ptr)) {
222                         char *endp;
223                         if (n1 < 0) {
224                                 n1 = strtol(ptr, &endp, 10) + off;
225                         } else {
226                                 n2 = strtol(ptr, &endp, 10) + off;
227                         }
228                         ptr = endp; /* gcc likes temp var for &endp */
229                         skip = 1;
230                 } else if (names) {
231                         int i;
232
233                         for (i = 0; names[i]; i += 3) {
234                                 /* was using strncmp before... */
235                                 if (strncasecmp(ptr, &names[i], 3) == 0) {
236                                         ptr += 3;
237                                         if (n1 < 0) {
238                                                 n1 = i / 3;
239                                         } else {
240                                                 n2 = i / 3;
241                                         }
242                                         skip = 1;
243                                         break;
244                                 }
245                         }
246                 }
247
248                 /* handle optional range '-' */
249                 if (skip == 0) {
250                         goto err;
251                 }
252                 if (*ptr == '-' && n2 < 0) {
253                         ++ptr;
254                         continue;
255                 }
256
257                 /*
258                  * collapse single-value ranges, handle skipmark, and fill
259                  * in the character array appropriately.
260                  */
261                 if (n2 < 0) {
262                         n2 = n1;
263                 }
264                 if (*ptr == '/') {
265                         char *endp;
266                         skip = strtol(ptr + 1, &endp, 10);
267                         ptr = endp; /* gcc likes temp var for &endp */
268                 }
269
270                 /*
271                  * fill array, using a failsafe is the easiest way to prevent
272                  * an endless loop
273                  */
274                 {
275                         int s0 = 1;
276                         int failsafe = 1024;
277
278                         --n1;
279                         do {
280                                 n1 = (n1 + 1) % modvalue;
281
282                                 if (--s0 == 0) {
283                                         ary[n1 % modvalue] = 1;
284                                         s0 = skip;
285                                 }
286                                 if (--failsafe == 0) {
287                                         goto err;
288                                 }
289                         } while (n1 != n2);
290
291                 }
292                 if (*ptr != ',') {
293                         break;
294                 }
295                 ++ptr;
296                 n1 = -1;
297                 n2 = -1;
298         }
299
300         if (*ptr) {
301  err:
302                 crondlog(WARN9 "user %s: parse error at %s", user, base);
303                 return;
304         }
305
306         if (DebugOpt && (G.LogLevel <= 5)) { /* like LVL5 */
307                 /* can't use crondlog, it inserts '\n' */
308                 int i;
309                 for (i = 0; i < modvalue; ++i)
310                         fprintf(stderr, "%d", (unsigned char)ary[i]);
311                 bb_putchar_stderr('\n');
312         }
313 }
314
315 static void FixDayDow(CronLine *line)
316 {
317         unsigned i;
318         int weekUsed = 0;
319         int daysUsed = 0;
320
321         for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) {
322                 if (line->cl_Dow[i] == 0) {
323                         weekUsed = 1;
324                         break;
325                 }
326         }
327         for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) {
328                 if (line->cl_Days[i] == 0) {
329                         daysUsed = 1;
330                         break;
331                 }
332         }
333         if (weekUsed != daysUsed) {
334                 if (weekUsed)
335                         memset(line->cl_Days, 0, sizeof(line->cl_Days));
336                 else /* daysUsed */
337                         memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
338         }
339 }
340
341 /*
342  * DeleteFile() - delete user database
343  *
344  * Note: multiple entries for same user may exist if we were unable to
345  * completely delete a database due to running processes.
346  */
347 static void DeleteFile(const char *userName)
348 {
349         CronFile **pfile = &G.FileBase;
350         CronFile *file;
351
352         while ((file = *pfile) != NULL) {
353                 if (strcmp(userName, file->cf_User) == 0) {
354                         CronLine **pline = &file->cf_LineBase;
355                         CronLine *line;
356
357                         file->cf_Running = 0;
358                         file->cf_Deleted = 1;
359
360                         while ((line = *pline) != NULL) {
361                                 if (line->cl_Pid > 0) {
362                                         file->cf_Running = 1;
363                                         pline = &line->cl_Next;
364                                 } else {
365                                         *pline = line->cl_Next;
366                                         free(line->cl_Shell);
367                                         free(line);
368                                 }
369                         }
370                         if (file->cf_Running == 0) {
371                                 *pfile = file->cf_Next;
372                                 free(file->cf_User);
373                                 free(file);
374                         } else {
375                                 pfile = &file->cf_Next;
376                         }
377                 } else {
378                         pfile = &file->cf_Next;
379                 }
380         }
381 }
382
383 static void SynchronizeFile(const char *fileName)
384 {
385         struct parser_t *parser;
386         struct stat sbuf;
387         int maxLines;
388         char *tokens[6];
389 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
390         char *mailTo = NULL;
391 #endif
392
393         if (!fileName)
394                 return;
395
396         DeleteFile(fileName);
397         parser = config_open(fileName);
398         if (!parser)
399                 return;
400
401         maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;
402
403         if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
404                 CronFile *file = xzalloc(sizeof(CronFile));
405                 CronLine **pline;
406                 int n;
407
408                 file->cf_User = xstrdup(fileName);
409                 pline = &file->cf_LineBase;
410
411                 while (1) {
412                         CronLine *line;
413
414                         if (!--maxLines)
415                                 break;
416                         n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
417                         if (!n)
418                                 break;
419
420                         if (DebugOpt)
421                                 crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
422
423                         /* check if line is setting MAILTO= */
424                         if (0 == strncmp(tokens[0], "MAILTO=", 7)) {
425 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
426                                 free(mailTo);
427                                 mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
428 #endif /* otherwise just ignore such lines */
429                                 continue;
430                         }
431                         /* check if a minimum of tokens is specified */
432                         if (n < 6)
433                                 continue;
434                         *pline = line = xzalloc(sizeof(*line));
435                         /* parse date ranges */
436                         ParseField(file->cf_User, line->cl_Mins, 60, 0, NULL, tokens[0]);
437                         ParseField(file->cf_User, line->cl_Hrs, 24, 0, NULL, tokens[1]);
438                         ParseField(file->cf_User, line->cl_Days, 32, 0, NULL, tokens[2]);
439                         ParseField(file->cf_User, line->cl_Mons, 12, -1, MonAry, tokens[3]);
440                         ParseField(file->cf_User, line->cl_Dow, 7, 0, DowAry, tokens[4]);
441                         /*
442                          * fix days and dow - if one is not "*" and the other
443                          * is "*", the other is set to 0, and vise-versa
444                          */
445                         FixDayDow(line);
446 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
447                         /* copy mailto (can be NULL) */
448                         line->cl_MailTo = xstrdup(mailTo);
449 #endif
450                         /* copy command */
451                         line->cl_Shell = xstrdup(tokens[5]);
452                         if (DebugOpt) {
453                                 crondlog(LVL5 " command:%s", tokens[5]);
454                         }
455                         pline = &line->cl_Next;
456 //bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]);
457                 }
458                 *pline = NULL;
459
460                 file->cf_Next = G.FileBase;
461                 G.FileBase = file;
462
463                 if (maxLines == 0) {
464                         crondlog(WARN9 "user %s: too many lines", fileName);
465                 }
466         }
467         config_close(parser);
468 }
469
470 static void CheckUpdates(void)
471 {
472         FILE *fi;
473         char buf[256];
474
475         fi = fopen_for_read(CRONUPDATE);
476         if (fi != NULL) {
477                 unlink(CRONUPDATE);
478                 while (fgets(buf, sizeof(buf), fi) != NULL) {
479                         /* use first word only */
480                         SynchronizeFile(strtok(buf, " \t\r\n"));
481                 }
482                 fclose(fi);
483         }
484 }
485
486 static void SynchronizeDir(void)
487 {
488         CronFile *file;
489         /* Attempt to delete the database. */
490  again:
491         for (file = G.FileBase; file; file = file->cf_Next) {
492                 if (!file->cf_Deleted) {
493                         DeleteFile(file->cf_User);
494                         goto again;
495                 }
496         }
497
498         /*
499          * Remove cron update file
500          *
501          * Re-chdir, in case directory was renamed & deleted, or otherwise
502          * screwed up.
503          *
504          * scan directory and add associated users
505          */
506         unlink(CRONUPDATE);
507         if (chdir(G.CDir) < 0) {
508                 crondlog(DIE9 "chdir(%s)", G.CDir);
509         }
510         {
511                 DIR *dir = opendir(".");
512                 struct dirent *den;
513
514                 if (!dir)
515                         crondlog(DIE9 "chdir(%s)", "."); /* exits */
516                 while ((den = readdir(dir)) != NULL) {
517                         if (strchr(den->d_name, '.') != NULL) {
518                                 continue;
519                         }
520                         if (getpwnam(den->d_name)) {
521                                 SynchronizeFile(den->d_name);
522                         } else {
523                                 crondlog(LVL7 "ignoring %s", den->d_name);
524                         }
525                 }
526                 closedir(dir);
527         }
528 }
529
530 /*
531  * Determine which jobs need to be run.  Under normal conditions, the
532  * period is about a minute (one scan).  Worst case it will be one
533  * hour (60 scans).
534  */
535 static void TestJobs(time_t t1, time_t t2)
536 {
537         time_t t;
538
539         /* Find jobs > t1 and <= t2 */
540
541         for (t = t1 - t1 % 60; t <= t2; t += 60) {
542                 struct tm *ptm;
543                 CronFile *file;
544                 CronLine *line;
545
546                 if (t <= t1)
547                         continue;
548
549                 ptm = localtime(&t);
550                 for (file = G.FileBase; file; file = file->cf_Next) {
551                         if (DebugOpt)
552                                 crondlog(LVL5 "file %s:", file->cf_User);
553                         if (file->cf_Deleted)
554                                 continue;
555                         for (line = file->cf_LineBase; line; line = line->cl_Next) {
556                                 if (DebugOpt)
557                                         crondlog(LVL5 " line %s", line->cl_Shell);
558                                 if (line->cl_Mins[ptm->tm_min] && line->cl_Hrs[ptm->tm_hour]
559                                  && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday])
560                                  && line->cl_Mons[ptm->tm_mon]
561                                 ) {
562                                         if (DebugOpt) {
563                                                 crondlog(LVL5 " job: %d %s",
564                                                         (int)line->cl_Pid, line->cl_Shell);
565                                         }
566                                         if (line->cl_Pid > 0) {
567                                                 crondlog(LVL8 "user %s: process already running: %s",
568                                                         file->cf_User, line->cl_Shell);
569                                         } else if (line->cl_Pid == 0) {
570                                                 line->cl_Pid = -1;
571                                                 file->cf_Ready = 1;
572                                         }
573                                 }
574                         }
575                 }
576         }
577 }
578
579 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
580 static void
581 ForkJob(const char *user, CronLine *line, int mailFd,
582                 const char *prog, const char *cmd, const char *arg,
583                 const char *mail_filename);
584 /*
585  * EndJob - called when job terminates and when mail terminates
586  */
587 static void EndJob(const char *user, CronLine *line)
588 {
589         int mailFd;
590         char mailFile[128];
591         struct stat sbuf;
592
593         /* No job */
594         if (line->cl_Pid <= 0) {
595                 line->cl_Pid = 0;
596                 return;
597         }
598
599         /*
600          * End of job and no mail file
601          * End of sendmail job
602          */
603         snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, line->cl_Pid);
604         line->cl_Pid = 0;
605
606         if (line->cl_MailFlag == 0) {
607                 return;
608         }
609         line->cl_MailFlag = 0;
610
611         /*
612          * End of primary job - check for mail file.  If size has increased and
613          * the file is still valid, we sendmail it.
614          */
615         mailFd = open(mailFile, O_RDONLY);
616         unlink(mailFile);
617         if (mailFd < 0) {
618                 return;
619         }
620
621         if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid
622          || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos
623          || !S_ISREG(sbuf.st_mode)
624         ) {
625                 close(mailFd);
626                 return;
627         }
628         if (line->cl_MailTo)
629                 ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
630 }
631 #else
632 # define EndJob(user, line)  ((line)->cl_Pid = 0)
633 #endif
634
635 /*
636  * Check for job completion, return number of jobs still running after
637  * all done.
638  */
639 static int CheckJobs(void)
640 {
641         CronFile *file;
642         CronLine *line;
643         int nStillRunning = 0;
644
645         for (file = G.FileBase; file; file = file->cf_Next) {
646                 if (file->cf_Running) {
647                         file->cf_Running = 0;
648
649                         for (line = file->cf_LineBase; line; line = line->cl_Next) {
650                                 int status, r;
651                                 if (line->cl_Pid <= 0)
652                                         continue;
653
654                                 r = waitpid(line->cl_Pid, &status, WNOHANG);
655                                 if (r < 0 || r == line->cl_Pid) {
656                                         EndJob(file->cf_User, line);
657                                         if (line->cl_Pid) {
658                                                 file->cf_Running = 1;
659                                         }
660                                 } else if (r == 0) {
661                                         file->cf_Running = 1;
662                                 }
663                         }
664                 }
665                 nStillRunning += file->cf_Running;
666         }
667         return nStillRunning;
668 }
669
670 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
671
672 // TODO: sendmail should be _run-time_ option, not compile-time!
673
674 static void
675 ForkJob(const char *user, CronLine *line, int mailFd,
676                 const char *prog, const char *cmd, const char *arg,
677                 const char *mail_filename)
678 {
679         struct passwd *pas;
680         pid_t pid;
681
682         /* prepare things before vfork */
683         pas = getpwnam(user);
684         if (!pas) {
685                 crondlog(WARN9 "can't get uid for %s", user);
686                 goto err;
687         }
688         SetEnv(pas);
689
690         pid = vfork();
691         if (pid == 0) {
692                 /* CHILD */
693                 /* change running state to the user in question */
694                 ChangeUser(pas);
695                 if (DebugOpt) {
696                         crondlog(LVL5 "child running %s", prog);
697                 }
698                 if (mailFd >= 0) {
699                         xmove_fd(mailFd, mail_filename ? 1 : 0);
700                         dup2(1, 2);
701                 }
702                 /* crond 3.0pl1-100 puts tasks in separate process groups */
703                 bb_setpgrp();
704                 execlp(prog, prog, cmd, arg, (char *) NULL);
705                 crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, prog, cmd, arg);
706                 if (mail_filename) {
707                         fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
708                 }
709                 _exit(EXIT_SUCCESS);
710         }
711
712         line->cl_Pid = pid;
713         if (pid < 0) {
714                 /* FORK FAILED */
715                 crondlog(ERR20 "can't vfork");
716  err:
717                 line->cl_Pid = 0;
718                 if (mail_filename) {
719                         unlink(mail_filename);
720                 }
721         } else {
722                 /* PARENT, FORK SUCCESS */
723                 if (mail_filename) {
724                         /* rename mail-file based on pid of process */
725                         char *mailFile2 = xasprintf("%s/cron.%s.%d", TMPDIR, user, (int)pid);
726                         rename(mail_filename, mailFile2); // TODO: xrename?
727                         free(mailFile2);
728                 }
729         }
730
731         /*
732          * Close the mail file descriptor.. we can't just leave it open in
733          * a structure, closing it later, because we might run out of descriptors
734          */
735         if (mailFd >= 0) {
736                 close(mailFd);
737         }
738 }
739
740 static void RunJob(const char *user, CronLine *line)
741 {
742         char mailFile[128];
743         int mailFd = -1;
744
745         line->cl_Pid = 0;
746         line->cl_MailFlag = 0;
747
748         if (line->cl_MailTo) {
749                 /* open mail file - owner root so nobody can screw with it. */
750                 snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid());
751                 mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
752
753                 if (mailFd >= 0) {
754                         line->cl_MailFlag = 1;
755                         fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_MailTo,
756                                 line->cl_Shell);
757                         line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR);
758                 } else {
759                         crondlog(ERR20 "can't create mail file %s for user %s, "
760                                         "discarding output", mailFile, user);
761                 }
762         }
763
764         ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
765 }
766
767 #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
768
769 static void RunJob(const char *user, CronLine *line)
770 {
771         struct passwd *pas;
772         pid_t pid;
773
774         /* prepare things before vfork */
775         pas = getpwnam(user);
776         if (!pas) {
777                 crondlog(WARN9 "can't get uid for %s", user);
778                 goto err;
779         }
780         SetEnv(pas);
781
782         /* fork as the user in question and run program */
783         pid = vfork();
784         if (pid == 0) {
785                 /* CHILD */
786                 /* change running state to the user in question */
787                 ChangeUser(pas);
788                 if (DebugOpt) {
789                         crondlog(LVL5 "child running %s", DEFAULT_SHELL);
790                 }
791                 /* crond 3.0pl1-100 puts tasks in separate process groups */
792                 bb_setpgrp();
793                 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, (char *) NULL);
794                 crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user,
795                                  DEFAULT_SHELL, "-c", line->cl_Shell);
796                 _exit(EXIT_SUCCESS);
797         }
798         if (pid < 0) {
799                 /* FORK FAILED */
800                 crondlog(ERR20 "can't vfork");
801  err:
802                 pid = 0;
803         }
804         line->cl_Pid = pid;
805 }
806
807 #endif /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
808
809 static void RunJobs(void)
810 {
811         CronFile *file;
812         CronLine *line;
813
814         for (file = G.FileBase; file; file = file->cf_Next) {
815                 if (!file->cf_Ready)
816                         continue;
817
818                 file->cf_Ready = 0;
819                 for (line = file->cf_LineBase; line; line = line->cl_Next) {
820                         if (line->cl_Pid >= 0)
821                                 continue;
822
823                         RunJob(file->cf_User, line);
824                         crondlog(LVL8 "USER %s pid %3d cmd %s",
825                                 file->cf_User, (int)line->cl_Pid, line->cl_Shell);
826                         if (line->cl_Pid < 0) {
827                                 file->cf_Ready = 1;
828                         } else if (line->cl_Pid > 0) {
829                                 file->cf_Running = 1;
830                         }
831                 }
832         }
833 }
834
835 int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
836 int crond_main(int argc UNUSED_PARAM, char **argv)
837 {
838         time_t t2;
839         int rescan;
840         int sleep_time;
841         unsigned opts;
842
843         INIT_G();
844
845         /* "-b after -f is ignored", and so on for every pair a-b */
846         opt_complementary = "f-b:b-f:S-L:L-S" IF_FEATURE_CROND_D(":d-l")
847                         ":l+:d+"; /* -l and -d have numeric param */
848         opts = getopt32(argv, "l:L:fbSc:" IF_FEATURE_CROND_D("d:"),
849                         &G.LogLevel, &G.LogFile, &G.CDir
850                         IF_FEATURE_CROND_D(,&G.LogLevel));
851         /* both -d N and -l N set the same variable: G.LogLevel */
852
853         if (!(opts & OPT_f)) {
854                 /* close stdin, stdout, stderr.
855                  * close unused descriptors - don't need them. */
856                 bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
857         }
858
859         if (!(opts & OPT_d) && G.LogFile == NULL) {
860                 /* logging to syslog */
861                 openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
862                 logmode = LOGMODE_SYSLOG;
863         }
864
865         xchdir(G.CDir);
866         //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
867         xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */
868         crondlog(LVL8 "crond (busybox "BB_VER") started, log level %d", G.LogLevel);
869         SynchronizeDir();
870         write_pidfile("/var/run/crond.pid");
871
872         /* Main loop */
873         t2 = time(NULL);
874         rescan = 60;
875         sleep_time = 60;
876         for (;;) {
877                 struct stat sbuf;
878                 time_t t1;
879                 long dt;
880
881                 t1 = t2;
882
883                 /* Synchronize to 1 minute, minimum 1 second */
884                 sleep(sleep_time - (time(NULL) % sleep_time) + 1);
885
886                 t2 = time(NULL);
887                 dt = (long)t2 - (long)t1;
888
889                 /*
890                  * The file 'cron.update' is checked to determine new cron
891                  * jobs.  The directory is rescanned once an hour to deal
892                  * with any screwups.
893                  *
894                  * Check for time jump.  Disparities over an hour either way
895                  * result in resynchronization.  A negative disparity
896                  * less than an hour causes us to effectively sleep until we
897                  * match the original time (i.e. no re-execution of jobs that
898                  * have just been run).  A positive disparity less than
899                  * an hour causes intermediate jobs to be run, but only once
900                  * in the worst case.
901                  *
902                  * When running jobs, the inequality used is greater but not
903                  * equal to t1, and less then or equal to t2.
904                  */
905                 if (stat(G.CDir, &sbuf) == 0 && G.CDir_mtime != sbuf.st_mtime) {
906                         G.CDir_mtime = sbuf.st_mtime;
907                         rescan = 1;
908                 }
909                 if (--rescan == 0) {
910                         rescan = 60;
911                         SynchronizeDir();
912                 }
913                 CheckUpdates();
914                 if (DebugOpt)
915                         crondlog(LVL5 "wakeup dt=%ld", dt);
916                 if (dt < -60 * 60 || dt > 60 * 60) {
917                         crondlog(WARN9 "time disparity of %ld minutes detected", dt / 60);
918                         /* and we do not run any jobs in this case */
919                 } else if (dt > 0) {
920                         /* Usual case: time advances forward, as expected */
921                         TestJobs(t1, t2);
922                         RunJobs();
923                         if (CheckJobs() > 0) {
924                                 sleep_time = 10;
925                         } else {
926                                 sleep_time = 60;
927                         }
928                 }
929                 /* else: time jumped back, do not run any jobs */
930         } /* for (;;) */
931
932         return 0; /* not reached */
933 }