blob: f0920136fa9cce7c80d2d269e5490057d7be5944 [file] [log] [blame]
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001/*
2 * crond -d[#] -c <crondir> -f -b
3 *
4 * run as root, but NOT setuid root
5 *
6 * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
7 * May be distributed under the GNU General Public License
8 *
9 * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 to be used in busybox
10 */
11
12#define VERSION "2.3.2"
13
14#undef FEATURE_DEBUG_OPT
15
16
17#include <stdio.h>
18#include <stdlib.h>
19#include <stdarg.h>
20#include <string.h>
21#include <errno.h>
22#include <time.h>
23#include <dirent.h>
24#include <fcntl.h>
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000025#include <unistd.h>
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000026#include <syslog.h>
27#include <signal.h>
28#include <getopt.h>
29#include <sys/ioctl.h>
30#include <sys/wait.h>
31#include <sys/stat.h>
32#include <sys/resource.h>
33
34#include "busybox.h"
35
36#define arysize(ary) (sizeof(ary)/sizeof((ary)[0]))
37
38#ifndef CRONTABS
39#define CRONTABS "/var/spool/cron/crontabs"
40#endif
41#ifndef TMPDIR
42#define TMPDIR "/var/spool/cron"
43#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000044#ifndef SENDMAIL
45#define SENDMAIL "/usr/sbin/sendmail"
46#endif
47#ifndef SENDMAIL_ARGS
Eric Andersen35e643b2003-07-28 07:40:39 +000048#define SENDMAIL_ARGS "-ti", "oem"
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000049#endif
50#ifndef CRONUPDATE
51#define CRONUPDATE "cron.update"
52#endif
53#ifndef MAXLINES
54#define MAXLINES 256 /* max lines in non-root crontabs */
55#endif
56
Eric Andersen35e643b2003-07-28 07:40:39 +000057static const char def_sh[] = "/bin/sh";
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000058
59
60typedef struct CronFile {
61 struct CronFile *cf_Next;
62 struct CronLine *cf_LineBase;
63 char *cf_User; /* username */
64 int cf_Ready; /* bool: one or more jobs ready */
65 int cf_Running; /* bool: one or more jobs running */
66 int cf_Deleted; /* marked for deletion, ignore */
67} CronFile;
68
69typedef struct CronLine {
70 struct CronLine *cl_Next;
71 char *cl_Shell; /* shell command */
Eric Andersen35e643b2003-07-28 07:40:39 +000072 pid_t cl_Pid; /* running pid, 0, or armed (-1) */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000073 int cl_MailFlag; /* running pid is for mail */
74 int cl_MailPos; /* 'empty file' size */
75 char cl_Mins[60]; /* 0-59 */
76 char cl_Hrs[24]; /* 0-23 */
77 char cl_Days[32]; /* 1-31 */
78 char cl_Mons[12]; /* 0-11 */
79 char cl_Dow[7]; /* 0-6, beginning sunday */
80} CronLine;
81
82#define RUN_RANOUT 1
83#define RUN_RUNNING 2
84#define RUN_FAILED 3
85
86#define DaemonUid 0
87
88#ifdef FEATURE_DEBUG_OPT
89static short DebugOpt;
90#endif
91
92static short LogLevel = 8;
Eric Andersen35e643b2003-07-28 07:40:39 +000093static const char *LogFile;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000094static const char *CDir = CRONTABS;
95
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000096static void startlogger(void);
97
98static void CheckUpdates(void);
99static void SynchronizeDir(void);
100static int TestJobs(time_t t1, time_t t2);
101static void RunJobs(void);
102static int CheckJobs(void);
Eric Andersen35e643b2003-07-28 07:40:39 +0000103
104static void RunJob(const char *user, CronLine *line);
105#ifdef CONFIG_FEATURE_CROND_CALL_SENDMAIL
106static void EndJob(const char *user, CronLine *line);
107#else
108#define EndJob(user, line) line->cl_Pid = 0
109#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000110
111static void DeleteFile(const char *userName);
112
113static CronFile *FileBase;
114
115
Eric Andersen35e643b2003-07-28 07:40:39 +0000116static void
Eric Andersen9b476612003-07-28 09:37:56 +0000117crondlog(const char *ctl, ...)
Eric Andersen35e643b2003-07-28 07:40:39 +0000118{
119 va_list va;
120 int level = (int)(ctl[0] & 0xf);
121 int type = level == 20 ?
122 LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE);
123
124
125 va_start(va, ctl);
126 if (level >= LogLevel) {
127
128#ifdef FEATURE_DEBUG_OPT
129 if (DebugOpt) vfprintf(stderr, ctl, va);
130 else
131#endif
132 if (LogFile == 0) vsyslog(type, ctl, va);
133 else {
134 int logfd;
135
136 if ((logfd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 600)) >= 0) {
137 vdprintf(logfd, ctl, va);
138 close(logfd);
139#ifdef FEATURE_DEBUG_OPT
140 } else {
141 bb_perror_msg("Can't open log file");
142#endif
143 }
144 }
145 }
146 va_end(va);
147 if(ctl[0] & 0200)
148 exit(20);
149}
150
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000151int
152crond_main(int ac, char **av)
153{
Eric Andersen8876fb22003-06-20 09:01:58 +0000154 unsigned long opt;
155 char *lopt, *Lopt, *copt;
156#ifdef FEATURE_DEBUG_OPT
157 char *dopt;
158 bb_opt_complementaly = "f-b:b-f:S-L:L-S:d-l";
159#else
160 bb_opt_complementaly = "f-b:b-f:S-L:L-S";
161#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000162
163 opterr = 0; /* disable getopt 'errors' message.*/
Eric Andersen8876fb22003-06-20 09:01:58 +0000164 opt = bb_getopt_ulflags(ac, av, "l:L:fbSc:"
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000165#ifdef FEATURE_DEBUG_OPT
166 "d:"
167#endif
Eric Andersen8876fb22003-06-20 09:01:58 +0000168 , &lopt, &Lopt, &copt
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000169#ifdef FEATURE_DEBUG_OPT
Eric Andersen8876fb22003-06-20 09:01:58 +0000170 , &dopt
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000171#endif
Eric Andersen8876fb22003-06-20 09:01:58 +0000172 );
173 if(opt & 1)
174 LogLevel = atoi(lopt);
Eric Andersen35e643b2003-07-28 07:40:39 +0000175 if(opt & 2)
Eric Andersen8876fb22003-06-20 09:01:58 +0000176 if (*Lopt != 0) LogFile = Lopt;
Eric Andersen8876fb22003-06-20 09:01:58 +0000177 if(opt & 32) {
178 if (*copt != 0) CDir = copt;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000179 }
Eric Andersen8876fb22003-06-20 09:01:58 +0000180#ifdef FEATURE_DEBUG_OPT
181 if(opt & 64) {
182 DebugOpt = atoi(dopt);
183 LogLevel = 0;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000184 }
Eric Andersen8876fb22003-06-20 09:01:58 +0000185#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000186
187 /*
188 * change directory
189 */
190
191 if (chdir(CDir) != 0)
Eric Andersen35e643b2003-07-28 07:40:39 +0000192 bb_perror_msg_and_die("%s", CDir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000193
Eric Andersen8876fb22003-06-20 09:01:58 +0000194 signal(SIGHUP,SIG_IGN); /* hmm.. but, if kill -HUP original
195 * version - his died. ;(
196 */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000197 /*
198 * close stdin and stdout, stderr.
199 * close unused descriptors - don't need.
200 * optional detach from controlling terminal
201 */
202
Eric Andersen35e643b2003-07-28 07:40:39 +0000203 if (!(opt & 4)) {
204 if(daemon(1, 0) < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000205 bb_perror_msg_and_die("daemon");
Eric Andersen68d4a852003-07-28 09:31:28 +0000206 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000207#if defined(__uClinux__)
Eric Andersen68d4a852003-07-28 09:31:28 +0000208 else {
Eric Andersen35e643b2003-07-28 07:40:39 +0000209 /* reexec for vfork() do continue parent */
210 vfork_daemon_rexec(ac, av, "-f");
211 }
212#endif /* uClinux */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000213 }
214
215 (void)startlogger(); /* need if syslog mode selected */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000216
217 /*
218 * main loop - synchronize to 1 second after the minute, minimum sleep
219 * of 1 second.
220 */
221
Eric Andersen9b476612003-07-28 09:37:56 +0000222 crondlog("\011%s " VERSION " dillon, started, log level %d\n", bb_applet_name,
Eric Andersen35e643b2003-07-28 07:40:39 +0000223 LogLevel);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000224
225 SynchronizeDir();
226
227 {
228 time_t t1 = time(NULL);
229 time_t t2;
230 long dt;
231 short rescan = 60;
232 short sleep_time = 60;
233
234 for (;;) {
235 sleep((sleep_time + 1) - (short)(time(NULL) % sleep_time));
236
237 t2 = time(NULL);
238 dt = t2 - t1;
239
240 /*
241 * The file 'cron.update' is checked to determine new cron
242 * jobs. The directory is rescanned once an hour to deal
243 * with any screwups.
244 *
245 * check for disparity. Disparities over an hour either way
246 * result in resynchronization. A reverse-indexed disparity
247 * less then an hour causes us to effectively sleep until we
248 * match the original time (i.e. no re-execution of jobs that
249 * have just been run). A forward-indexed disparity less then
250 * an hour causes intermediate jobs to be run, but only once
251 * in the worst case.
252 *
253 * when running jobs, the inequality used is greater but not
254 * equal to t1, and less then or equal to t2.
255 */
256
257 if (--rescan == 0) {
258 rescan = 60;
259 SynchronizeDir();
260 }
261 CheckUpdates();
262#ifdef FEATURE_DEBUG_OPT
263 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000264 crondlog("\005Wakeup dt=%d\n", dt);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000265#endif
266 if (dt < -60*60 || dt > 60*60) {
267 t1 = t2;
Eric Andersen9b476612003-07-28 09:37:56 +0000268 crondlog("\111time disparity of %d minutes detected\n", dt / 60);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000269 } else if (dt > 0) {
270 TestJobs(t1, t2);
271 RunJobs();
272 sleep(5);
273 if (CheckJobs() > 0)
274 sleep_time = 10;
275 else
276 sleep_time = 60;
277 t1 = t2;
278 }
279 }
280 }
281 /* not reached */
282}
283
284
Eric Andersen35e643b2003-07-28 07:40:39 +0000285#if defined(FEATURE_DEBUG_OPT) || defined(CONFIG_FEATURE_CROND_CALL_SENDMAIL)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000286/*
Eric Andersen35e643b2003-07-28 07:40:39 +0000287 write to temp file..
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000288*/
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000289static void
290fdprintf(int fd, const char *ctl, ...)
291{
292 va_list va;
293
294 va_start(va, ctl);
295 vdprintf(fd, ctl, va);
296 va_end(va);
297}
Eric Andersen35e643b2003-07-28 07:40:39 +0000298#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000299
300
301static int
Eric Andersen35e643b2003-07-28 07:40:39 +0000302ChangeUser(const char *user)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000303{
304 struct passwd *pas;
305
306 /*
307 * Obtain password entry and change privilages
308 */
309
310 if ((pas = getpwnam(user)) == 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000311 crondlog("\011failed to get uid for %s", user);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000312 return(-1);
313 }
314 setenv("USER", pas->pw_name, 1);
315 setenv("HOME", pas->pw_dir, 1);
Eric Andersen35e643b2003-07-28 07:40:39 +0000316 setenv("SHELL", def_sh, 1);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000317
318 /*
319 * Change running state to the user in question
320 */
321
322 if (initgroups(user, pas->pw_gid) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000323 crondlog("\011initgroups failed: %s %m", user);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000324 return(-1);
325 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000326 /* drop all priviledges */
327 if (setgid(pas->pw_gid) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000328 crondlog("\011setgid failed: %s %d", user, pas->pw_gid);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000329 return(-1);
330 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000331 if (setuid(pas->pw_uid) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000332 crondlog("\011setuid failed: %s %d", user, pas->pw_uid);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000333 return(-1);
334 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000335 if (chdir(pas->pw_dir) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000336 crondlog("\011chdir failed: %s: %m", pas->pw_dir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000337 if (chdir(TMPDIR) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000338 crondlog("\011chdir failed: %s: %m", TMPDIR);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000339 return(-1);
340 }
341 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000342 return(pas->pw_uid);
343}
344
345static void
346startlogger(void)
347{
Eric Andersen35e643b2003-07-28 07:40:39 +0000348 if (LogFile == 0)
349 openlog(bb_applet_name, LOG_CONS|LOG_PID, LOG_CRON);
350#ifdef FEATURE_DEBUG_OPT
351 else { /* test logfile */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000352 int logfd;
353
Eric Andersen35e643b2003-07-28 07:40:39 +0000354 if ((logfd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 600)) >= 0)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000355 close(logfd);
356 else
Eric Andersen35e643b2003-07-28 07:40:39 +0000357 bb_perror_msg("Failed to open log file '%s' reason", LogFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000358 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000359#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000360}
361
362
363static const char * const DowAry[] = {
364 "sun",
365 "mon",
366 "tue",
367 "wed",
368 "thu",
369 "fri",
370 "sat",
371
372 "Sun",
373 "Mon",
374 "Tue",
375 "Wed",
376 "Thu",
377 "Fri",
378 "Sat",
379 NULL
380};
381
382static const char * const MonAry[] = {
383 "jan",
384 "feb",
385 "mar",
386 "apr",
387 "may",
388 "jun",
389 "jul",
390 "aug",
391 "sep",
392 "oct",
393 "nov",
394 "dec",
395
396 "Jan",
397 "Feb",
398 "Mar",
399 "Apr",
400 "May",
401 "Jun",
402 "Jul",
403 "Aug",
404 "Sep",
405 "Oct",
406 "Nov",
407 "Dec",
408 NULL
409};
410
411static char *
412ParseField(char *user, char *ary, int modvalue, int off,
413 const char * const *names, char *ptr)
414{
415 char *base = ptr;
416 int n1 = -1;
417 int n2 = -1;
418
419 if (base == NULL)
420 return(NULL);
421
422 while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
423 int skip = 0;
424
425 /*
426 * Handle numeric digit or symbol or '*'
427 */
428
429 if (*ptr == '*') {
430 n1 = 0; /* everything will be filled */
431 n2 = modvalue - 1;
432 skip = 1;
433 ++ptr;
434 } else if (*ptr >= '0' && *ptr <= '9') {
435 if (n1 < 0)
436 n1 = strtol(ptr, &ptr, 10) + off;
437 else
438 n2 = strtol(ptr, &ptr, 10) + off;
439 skip = 1;
440 } else if (names) {
441 int i;
442
443 for (i = 0; names[i]; ++i) {
444 if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
445 break;
446 }
447 }
448 if (names[i]) {
449 ptr += strlen(names[i]);
450 if (n1 < 0)
451 n1 = i;
452 else
453 n2 = i;
454 skip = 1;
455 }
456 }
457
458 /*
459 * handle optional range '-'
460 */
461
462 if (skip == 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000463 crondlog("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000464 return(NULL);
465 }
466 if (*ptr == '-' && n2 < 0) {
467 ++ptr;
468 continue;
469 }
470
471 /*
472 * collapse single-value ranges, handle skipmark, and fill
473 * in the character array appropriately.
474 */
475
476 if (n2 < 0)
477 n2 = n1;
478
479 if (*ptr == '/')
480 skip = strtol(ptr + 1, &ptr, 10);
481
482 /*
483 * fill array, using a failsafe is the easiest way to prevent
484 * an endless loop
485 */
486
487 {
488 int s0 = 1;
489 int failsafe = 1024;
490
491 --n1;
492 do {
493 n1 = (n1 + 1) % modvalue;
494
495 if (--s0 == 0) {
496 ary[n1 % modvalue] = 1;
497 s0 = skip;
498 }
499 } while (n1 != n2 && --failsafe);
500
501 if (failsafe == 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000502 crondlog("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000503 return(NULL);
504 }
505 }
506 if (*ptr != ',')
507 break;
508 ++ptr;
509 n1 = -1;
510 n2 = -1;
511 }
512
513 if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
Eric Andersen9b476612003-07-28 09:37:56 +0000514 crondlog("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000515 return(NULL);
516 }
517
518 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
519 ++ptr;
520
521#ifdef FEATURE_DEBUG_OPT
522 if (DebugOpt) {
523 int i;
524
525 for (i = 0; i < modvalue; ++i)
Eric Andersen9b476612003-07-28 09:37:56 +0000526 crondlog("\005%d", ary[i]);
527 crondlog("\005\n");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000528 }
529#endif
530
531 return(ptr);
532}
533
534static void
535FixDayDow(CronLine *line)
536{
537 short i;
538 short weekUsed = 0;
539 short daysUsed = 0;
540
541 for (i = 0; i < arysize(line->cl_Dow); ++i) {
542 if (line->cl_Dow[i] == 0) {
543 weekUsed = 1;
544 break;
545 }
546 }
547 for (i = 0; i < arysize(line->cl_Days); ++i) {
548 if (line->cl_Days[i] == 0) {
549 daysUsed = 1;
550 break;
551 }
552 }
553 if (weekUsed && !daysUsed) {
554 memset(line->cl_Days, 0, sizeof(line->cl_Days));
555 }
556 if (daysUsed && !weekUsed) {
557 memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
558 }
559}
560
561
562
563static void
564SynchronizeFile(const char *fileName)
565{
566 int maxEntries = MAXLINES;
567 int maxLines;
568 char buf[1024];
569
570 if (strcmp(fileName, "root") == 0)
571 maxEntries = 65535;
572 maxLines = maxEntries * 10;
573
574 if (fileName) {
575 FILE *fi;
576
577 DeleteFile(fileName);
578
579 if ((fi = fopen(fileName, "r")) != NULL) {
580 struct stat sbuf;
581
582 if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
583 CronFile *file = calloc(1, sizeof(CronFile));
584 CronLine **pline;
585
586 file->cf_User = strdup(fileName);
587 pline = &file->cf_LineBase;
588
589 while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
590 CronLine line;
591 char *ptr;
592
593 if (buf[0])
594 buf[strlen(buf)-1] = 0;
595
596 if (buf[0] == 0 || buf[0] == '#' || buf[0] == ' ' || buf[0] == '\t')
597 continue;
598
599 if (--maxEntries == 0)
600 break;
601
602 memset(&line, 0, sizeof(line));
603
604#ifdef FEATURE_DEBUG_OPT
605 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000606 crondlog("\111User %s Entry %s\n", fileName, buf);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000607#endif
608
609 /*
610 * parse date ranges
611 */
612
613 ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
614 ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
615 ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
616 ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
617 ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
618
619 /*
620 * check failure
621 */
622
623 if (ptr == NULL)
624 continue;
625
626 /*
627 * fix days and dow - if one is not * and the other
628 * is *, the other is set to 0, and vise-versa
629 */
630
631 FixDayDow(&line);
632
633 *pline = calloc(1, sizeof(CronLine));
634 **pline = line;
635
636 /*
637 * copy command
638 */
639
640 (*pline)->cl_Shell = strdup(ptr);
641
642#ifdef FEATURE_DEBUG_OPT
643 if (DebugOpt) {
Eric Andersen9b476612003-07-28 09:37:56 +0000644 crondlog("\111 Command %s\n", ptr);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000645 }
646#endif
647
648 pline = &((*pline)->cl_Next);
649 }
650 *pline = NULL;
651
652 file->cf_Next = FileBase;
653 FileBase = file;
654
655 if (maxLines == 0 || maxEntries == 0)
Eric Andersen9b476612003-07-28 09:37:56 +0000656 crondlog("\111Maximum number of lines reached for user %s\n", fileName);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000657 }
658 fclose(fi);
659 }
660 }
661}
662
663static void
664CheckUpdates(void)
665{
666 FILE *fi;
667 char buf[256];
668
669 if ((fi = fopen(CRONUPDATE, "r")) != NULL) {
670 remove(CRONUPDATE);
671 while (fgets(buf, sizeof(buf), fi) != NULL) {
672 SynchronizeFile(strtok(buf, " \t\r\n"));
673 }
674 fclose(fi);
675 }
676}
677
678static void
679SynchronizeDir(void)
680{
681 /*
Eric Andersen35e643b2003-07-28 07:40:39 +0000682 * Attempt to delete the database.
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000683 */
684
685 for (;;) {
686 CronFile *file;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000687
688 for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next)
689 ;
690 if (file == NULL)
691 break;
Eric Andersen35e643b2003-07-28 07:40:39 +0000692 DeleteFile(file->cf_User);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000693 }
694
695 /*
696 * Remove cron update file
697 *
698 * Re-chdir, in case directory was renamed & deleted, or otherwise
699 * screwed up.
700 *
701 * scan directory and add associated users
702 */
703
704 remove(CRONUPDATE);
705 if (chdir(CDir) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000706 crondlog("\311unable to find %s\n", CDir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000707 }
708 {
709 DIR *dir;
710 struct dirent *den;
711
712 if ((dir = opendir("."))) {
713 while ((den = readdir(dir))) {
714 if (strchr(den->d_name, '.') != NULL)
715 continue;
716 if (getpwnam(den->d_name))
717 SynchronizeFile(den->d_name);
718 else
Eric Andersen9b476612003-07-28 09:37:56 +0000719 crondlog("\007ignoring %s\n", den->d_name);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000720 }
721 closedir(dir);
722 } else {
Eric Andersen9b476612003-07-28 09:37:56 +0000723 crondlog("\311Unable to open current dir!\n");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000724 }
725 }
726}
727
728
729/*
730 * DeleteFile() - delete user database
731 *
732 * Note: multiple entries for same user may exist if we were unable to
733 * completely delete a database due to running processes.
734 */
735
736static void
737DeleteFile(const char *userName)
738{
739 CronFile **pfile = &FileBase;
740 CronFile *file;
741
742 while ((file = *pfile) != NULL) {
743 if (strcmp(userName, file->cf_User) == 0) {
744 CronLine **pline = &file->cf_LineBase;
745 CronLine *line;
746
747 file->cf_Running = 0;
748 file->cf_Deleted = 1;
749
750 while ((line = *pline) != NULL) {
751 if (line->cl_Pid > 0) {
752 file->cf_Running = 1;
753 pline = &line->cl_Next;
754 } else {
755 *pline = line->cl_Next;
756 free(line->cl_Shell);
757 free(line);
758 }
759 }
760 if (file->cf_Running == 0) {
761 *pfile = file->cf_Next;
762 free(file->cf_User);
763 free(file);
764 } else {
765 pfile = &file->cf_Next;
766 }
767 } else {
768 pfile = &file->cf_Next;
769 }
770 }
771}
772
773/*
774 * TestJobs()
775 *
776 * determine which jobs need to be run. Under normal conditions, the
777 * period is about a minute (one scan). Worst case it will be one
778 * hour (60 scans).
779 */
780
781static int
782TestJobs(time_t t1, time_t t2)
783{
784 short nJobs = 0;
785 time_t t;
786
787 /*
788 * Find jobs > t1 and <= t2
789 */
790
791 for (t = t1 - t1 % 60; t <= t2; t += 60) {
792 if (t > t1) {
793 struct tm *tp = localtime(&t);
794 CronFile *file;
795 CronLine *line;
796
797 for (file = FileBase; file; file = file->cf_Next) {
798#ifdef FEATURE_DEBUG_OPT
799 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000800 crondlog("\005FILE %s:\n", file->cf_User);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000801#endif
802 if (file->cf_Deleted)
803 continue;
804 for (line = file->cf_LineBase; line; line = line->cl_Next) {
805#ifdef FEATURE_DEBUG_OPT
806 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000807 crondlog("\005 LINE %s\n", line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000808#endif
809 if (line->cl_Mins[tp->tm_min] &&
810 line->cl_Hrs[tp->tm_hour] &&
811 (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) &&
812 line->cl_Mons[tp->tm_mon]
813 ) {
814#ifdef FEATURE_DEBUG_OPT
815 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000816 crondlog("\005 JobToDo: %d %s\n", line->cl_Pid, line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000817#endif
818 if (line->cl_Pid > 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000819 crondlog("\010 process already running: %s %s\n",
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000820 file->cf_User,
821 line->cl_Shell
822 );
823 } else if (line->cl_Pid == 0) {
824 line->cl_Pid = -1;
825 file->cf_Ready = 1;
826 ++nJobs;
827 }
828 }
829 }
830 }
831 }
832 }
833 return(nJobs);
834}
835
836static void
837RunJobs(void)
838{
839 CronFile *file;
840 CronLine *line;
841
842 for (file = FileBase; file; file = file->cf_Next) {
843 if (file->cf_Ready) {
844 file->cf_Ready = 0;
845
846 for (line = file->cf_LineBase; line; line = line->cl_Next) {
847 if (line->cl_Pid < 0) {
848
Eric Andersen35e643b2003-07-28 07:40:39 +0000849 RunJob(file->cf_User, line);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000850
Eric Andersen9b476612003-07-28 09:37:56 +0000851 crondlog("\010USER %s pid %3d cmd %s\n",
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000852 file->cf_User,
853 line->cl_Pid,
854 line->cl_Shell
855 );
856 if (line->cl_Pid < 0)
857 file->cf_Ready = 1;
858 else if (line->cl_Pid > 0)
859 file->cf_Running = 1;
860 }
861 }
862 }
863 }
864}
865
866/*
867 * CheckJobs() - check for job completion
868 *
869 * Check for job completion, return number of jobs still running after
870 * all done.
871 */
872
873static int
874CheckJobs(void)
875{
876 CronFile *file;
877 CronLine *line;
878 int nStillRunning = 0;
879
880 for (file = FileBase; file; file = file->cf_Next) {
881 if (file->cf_Running) {
882 file->cf_Running = 0;
883
884 for (line = file->cf_LineBase; line; line = line->cl_Next) {
885 if (line->cl_Pid > 0) {
886 int status;
887 int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
888
889 if (r < 0 || r == line->cl_Pid) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000890 EndJob(file->cf_User, line);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000891 if (line->cl_Pid)
892 file->cf_Running = 1;
893 } else if (r == 0) {
894 file->cf_Running = 1;
895 }
896 }
897 }
898 }
899 nStillRunning += file->cf_Running;
900 }
901 return(nStillRunning);
902}
903
904
Eric Andersen35e643b2003-07-28 07:40:39 +0000905#ifdef CONFIG_FEATURE_CROND_CALL_SENDMAIL
906static void
907ForkJob(const char *user, CronLine *line, int mailFd,
908 const char *prog, const char *cmd, const char *arg, const char *mailf)
909{
910 /*
911 * Fork as the user in question and run program
912 */
913 pid_t pid = fork();
914
915 line->cl_Pid = pid;
916 if (pid == 0) {
917 /*
918 * CHILD
919 */
920
921 /*
922 * Change running state to the user in question
923 */
924
925 if (ChangeUser(user) < 0)
926 exit(0);
927
928#ifdef FEATURE_DEBUG_OPT
929 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000930 crondlog("\005Child Running %s\n", prog);
Eric Andersen35e643b2003-07-28 07:40:39 +0000931#endif
932
933 if (mailFd >= 0) {
934 dup2(mailFd, mailf != NULL);
935 dup2((mailf ? mailFd : 1), 2);
936 close(mailFd);
937 }
938 execl(prog, prog, cmd, arg, NULL);
Eric Andersen9b476612003-07-28 09:37:56 +0000939 crondlog("\024unable to exec, user %s cmd %s %s %s\n", user,
Eric Andersen35e643b2003-07-28 07:40:39 +0000940 prog, cmd, arg);
941 if(mailf)
942 fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
943 exit(0);
944 } else if (pid < 0) {
945 /*
946 * FORK FAILED
947 */
Eric Andersen9b476612003-07-28 09:37:56 +0000948 crondlog("\024couldn't fork, user %s\n", user);
Eric Andersen35e643b2003-07-28 07:40:39 +0000949 line->cl_Pid = 0;
950 if(mailf)
951 remove(mailf);
952 } else if(mailf) {
953 /*
954 * PARENT, FORK SUCCESS
955 *
956 * rename mail-file based on pid of process
957 */
958 char mailFile2[128];
959
960 snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d",
961 user, pid);
962 rename(mailf, mailFile2);
963 }
964 /*
965 * Close the mail file descriptor.. we can't just leave it open in
966 * a structure, closing it later, because we might run out of descriptors
967 */
968
969 if (mailFd >= 0)
970 close(mailFd);
971}
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000972
973static void
Eric Andersen35e643b2003-07-28 07:40:39 +0000974RunJob(const char *user, CronLine *line)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000975{
976 char mailFile[128];
977 int mailFd;
978
979 line->cl_Pid = 0;
980 line->cl_MailFlag = 0;
981
982 /*
983 * open mail file - owner root so nobody can screw with it.
984 */
985
986 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
Eric Andersen35e643b2003-07-28 07:40:39 +0000987 user, getpid());
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000988 mailFd = open(mailFile, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_APPEND, 0600);
989
990 if (mailFd >= 0) {
991 line->cl_MailFlag = 1;
Eric Andersen35e643b2003-07-28 07:40:39 +0000992 fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,
993 line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000994 line->cl_MailPos = lseek(mailFd, 0, 1);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000995 } else {
Eric Andersen9b476612003-07-28 09:37:56 +0000996 crondlog("\024unable to create mail file user %s file %s, output to /dev/null\n",
Eric Andersen35e643b2003-07-28 07:40:39 +0000997 user, mailFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000998 }
999
Eric Andersen35e643b2003-07-28 07:40:39 +00001000 ForkJob(user, line, mailFd, def_sh, "-c", line->cl_Shell, mailFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001001}
1002
1003/*
1004 * EndJob - called when job terminates and when mail terminates
1005 */
1006
1007static void
Eric Andersen35e643b2003-07-28 07:40:39 +00001008EndJob(const char *user, CronLine *line)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001009{
1010 int mailFd;
1011 char mailFile[128];
1012 struct stat sbuf;
1013
1014 /*
1015 * No job
1016 */
1017
1018 if (line->cl_Pid <= 0) {
1019 line->cl_Pid = 0;
1020 return;
1021 }
1022
1023 /*
1024 * End of job and no mail file
1025 * End of sendmail job
1026 */
1027
1028 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
Eric Andersen35e643b2003-07-28 07:40:39 +00001029 user, line->cl_Pid);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001030 line->cl_Pid = 0;
1031
1032 if (line->cl_MailFlag != 1)
1033 return;
1034
1035 line->cl_MailFlag = 0;
1036
1037 /*
1038 * End of primary job - check for mail file. If size has increased and
1039 * the file is still valid, we sendmail it.
1040 */
1041
1042 mailFd = open(mailFile, O_RDONLY);
1043 remove(mailFile);
1044 if (mailFd < 0) {
1045 return;
1046 }
1047
1048 if (fstat(mailFd, &sbuf) < 0 ||
1049 sbuf.st_uid != DaemonUid ||
1050 sbuf.st_nlink != 0 ||
1051 sbuf.st_size == line->cl_MailPos ||
1052 !S_ISREG(sbuf.st_mode)
1053 ) {
1054 close(mailFd);
1055 return;
1056 }
Eric Andersen35e643b2003-07-28 07:40:39 +00001057 ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
1058}
1059#else
1060/* crond whithout sendmail */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001061
Eric Andersen35e643b2003-07-28 07:40:39 +00001062static void
1063RunJob(const char *user, CronLine *line)
1064{
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001065 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001066 * Fork as the user in question and run program
1067 */
1068 pid_t pid = fork();
1069
1070 if (pid == 0) {
1071 /*
1072 * CHILD
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001073 */
1074
1075 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001076 * Change running state to the user in question
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001077 */
1078
Eric Andersen35e643b2003-07-28 07:40:39 +00001079 if (ChangeUser(user) < 0)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001080 exit(0);
1081
Eric Andersen35e643b2003-07-28 07:40:39 +00001082#ifdef FEATURE_DEBUG_OPT
1083 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +00001084 crondlog("\005Child Running %s\n", def_sh);
Eric Andersen35e643b2003-07-28 07:40:39 +00001085#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001086
Eric Andersen35e643b2003-07-28 07:40:39 +00001087 execl(def_sh, def_sh, "-c", line->cl_Shell, NULL);
Eric Andersen9b476612003-07-28 09:37:56 +00001088 crondlog("\024unable to exec, user %s cmd %s -c %s\n", user,
Eric Andersen35e643b2003-07-28 07:40:39 +00001089 def_sh, line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001090 exit(0);
Eric Andersen35e643b2003-07-28 07:40:39 +00001091 } else if (pid < 0) {
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001092 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001093 * FORK FAILED
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001094 */
Eric Andersen9b476612003-07-28 09:37:56 +00001095 crondlog("\024couldn't fork, user %s\n", user);
Eric Andersen35e643b2003-07-28 07:40:39 +00001096 pid = 0;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001097 }
Eric Andersen35e643b2003-07-28 07:40:39 +00001098 line->cl_Pid = pid;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001099}
Eric Andersen35e643b2003-07-28 07:40:39 +00001100#endif /* CONFIG_FEATURE_CROND_CALL_SENDMAIL */