blob: 198bc2d85f3ace3f72a439e116bb934bf4b27364 [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
117log(const char *ctl, ...)
118{
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 Andersen35e643b2003-07-28 07:40:39 +0000206#if defined(__uClinux__)
207 } else {
208 /* reexec for vfork() do continue parent */
209 vfork_daemon_rexec(ac, av, "-f");
210 }
211#endif /* uClinux */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000212 }
213
214 (void)startlogger(); /* need if syslog mode selected */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000215
216 /*
217 * main loop - synchronize to 1 second after the minute, minimum sleep
218 * of 1 second.
219 */
220
Eric Andersen35e643b2003-07-28 07:40:39 +0000221 log("\011%s " VERSION " dillon, started, log level %d\n", bb_applet_name,
222 LogLevel);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000223
224 SynchronizeDir();
225
226 {
227 time_t t1 = time(NULL);
228 time_t t2;
229 long dt;
230 short rescan = 60;
231 short sleep_time = 60;
232
233 for (;;) {
234 sleep((sleep_time + 1) - (short)(time(NULL) % sleep_time));
235
236 t2 = time(NULL);
237 dt = t2 - t1;
238
239 /*
240 * The file 'cron.update' is checked to determine new cron
241 * jobs. The directory is rescanned once an hour to deal
242 * with any screwups.
243 *
244 * check for disparity. Disparities over an hour either way
245 * result in resynchronization. A reverse-indexed disparity
246 * less then an hour causes us to effectively sleep until we
247 * match the original time (i.e. no re-execution of jobs that
248 * have just been run). A forward-indexed disparity less then
249 * an hour causes intermediate jobs to be run, but only once
250 * in the worst case.
251 *
252 * when running jobs, the inequality used is greater but not
253 * equal to t1, and less then or equal to t2.
254 */
255
256 if (--rescan == 0) {
257 rescan = 60;
258 SynchronizeDir();
259 }
260 CheckUpdates();
261#ifdef FEATURE_DEBUG_OPT
262 if (DebugOpt)
Eric Andersen35e643b2003-07-28 07:40:39 +0000263 log("\005Wakeup dt=%d\n", dt);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000264#endif
265 if (dt < -60*60 || dt > 60*60) {
266 t1 = t2;
Eric Andersen35e643b2003-07-28 07:40:39 +0000267 log("\111time disparity of %d minutes detected\n", dt / 60);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000268 } else if (dt > 0) {
269 TestJobs(t1, t2);
270 RunJobs();
271 sleep(5);
272 if (CheckJobs() > 0)
273 sleep_time = 10;
274 else
275 sleep_time = 60;
276 t1 = t2;
277 }
278 }
279 }
280 /* not reached */
281}
282
283
Eric Andersen35e643b2003-07-28 07:40:39 +0000284#if defined(FEATURE_DEBUG_OPT) || defined(CONFIG_FEATURE_CROND_CALL_SENDMAIL)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000285/*
Eric Andersen35e643b2003-07-28 07:40:39 +0000286 write to temp file..
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000287*/
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000288static void
289fdprintf(int fd, const char *ctl, ...)
290{
291 va_list va;
292
293 va_start(va, ctl);
294 vdprintf(fd, ctl, va);
295 va_end(va);
296}
Eric Andersen35e643b2003-07-28 07:40:39 +0000297#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000298
299
300static int
Eric Andersen35e643b2003-07-28 07:40:39 +0000301ChangeUser(const char *user)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000302{
303 struct passwd *pas;
304
305 /*
306 * Obtain password entry and change privilages
307 */
308
309 if ((pas = getpwnam(user)) == 0) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000310 log("\011failed to get uid for %s", user);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000311 return(-1);
312 }
313 setenv("USER", pas->pw_name, 1);
314 setenv("HOME", pas->pw_dir, 1);
Eric Andersen35e643b2003-07-28 07:40:39 +0000315 setenv("SHELL", def_sh, 1);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000316
317 /*
318 * Change running state to the user in question
319 */
320
321 if (initgroups(user, pas->pw_gid) < 0) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000322 log("\011initgroups failed: %s %m", user);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000323 return(-1);
324 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000325 /* drop all priviledges */
326 if (setgid(pas->pw_gid) < 0) {
327 log("\011setgid failed: %s %d", user, pas->pw_gid);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000328 return(-1);
329 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000330 if (setuid(pas->pw_uid) < 0) {
331 log("\011setuid failed: %s %d", user, pas->pw_uid);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000332 return(-1);
333 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000334 if (chdir(pas->pw_dir) < 0) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000335 log("\011chdir failed: %s: %m", pas->pw_dir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000336 if (chdir(TMPDIR) < 0) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000337 log("\011chdir failed: %s: %m", TMPDIR);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000338 return(-1);
339 }
340 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000341 return(pas->pw_uid);
342}
343
344static void
345startlogger(void)
346{
Eric Andersen35e643b2003-07-28 07:40:39 +0000347 if (LogFile == 0)
348 openlog(bb_applet_name, LOG_CONS|LOG_PID, LOG_CRON);
349#ifdef FEATURE_DEBUG_OPT
350 else { /* test logfile */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000351 int logfd;
352
Eric Andersen35e643b2003-07-28 07:40:39 +0000353 if ((logfd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 600)) >= 0)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000354 close(logfd);
355 else
Eric Andersen35e643b2003-07-28 07:40:39 +0000356 bb_perror_msg("Failed to open log file '%s' reason", LogFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000357 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000358#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000359}
360
361
362static const char * const DowAry[] = {
363 "sun",
364 "mon",
365 "tue",
366 "wed",
367 "thu",
368 "fri",
369 "sat",
370
371 "Sun",
372 "Mon",
373 "Tue",
374 "Wed",
375 "Thu",
376 "Fri",
377 "Sat",
378 NULL
379};
380
381static const char * const MonAry[] = {
382 "jan",
383 "feb",
384 "mar",
385 "apr",
386 "may",
387 "jun",
388 "jul",
389 "aug",
390 "sep",
391 "oct",
392 "nov",
393 "dec",
394
395 "Jan",
396 "Feb",
397 "Mar",
398 "Apr",
399 "May",
400 "Jun",
401 "Jul",
402 "Aug",
403 "Sep",
404 "Oct",
405 "Nov",
406 "Dec",
407 NULL
408};
409
410static char *
411ParseField(char *user, char *ary, int modvalue, int off,
412 const char * const *names, char *ptr)
413{
414 char *base = ptr;
415 int n1 = -1;
416 int n2 = -1;
417
418 if (base == NULL)
419 return(NULL);
420
421 while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
422 int skip = 0;
423
424 /*
425 * Handle numeric digit or symbol or '*'
426 */
427
428 if (*ptr == '*') {
429 n1 = 0; /* everything will be filled */
430 n2 = modvalue - 1;
431 skip = 1;
432 ++ptr;
433 } else if (*ptr >= '0' && *ptr <= '9') {
434 if (n1 < 0)
435 n1 = strtol(ptr, &ptr, 10) + off;
436 else
437 n2 = strtol(ptr, &ptr, 10) + off;
438 skip = 1;
439 } else if (names) {
440 int i;
441
442 for (i = 0; names[i]; ++i) {
443 if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
444 break;
445 }
446 }
447 if (names[i]) {
448 ptr += strlen(names[i]);
449 if (n1 < 0)
450 n1 = i;
451 else
452 n2 = i;
453 skip = 1;
454 }
455 }
456
457 /*
458 * handle optional range '-'
459 */
460
461 if (skip == 0) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000462 log("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000463 return(NULL);
464 }
465 if (*ptr == '-' && n2 < 0) {
466 ++ptr;
467 continue;
468 }
469
470 /*
471 * collapse single-value ranges, handle skipmark, and fill
472 * in the character array appropriately.
473 */
474
475 if (n2 < 0)
476 n2 = n1;
477
478 if (*ptr == '/')
479 skip = strtol(ptr + 1, &ptr, 10);
480
481 /*
482 * fill array, using a failsafe is the easiest way to prevent
483 * an endless loop
484 */
485
486 {
487 int s0 = 1;
488 int failsafe = 1024;
489
490 --n1;
491 do {
492 n1 = (n1 + 1) % modvalue;
493
494 if (--s0 == 0) {
495 ary[n1 % modvalue] = 1;
496 s0 = skip;
497 }
498 } while (n1 != n2 && --failsafe);
499
500 if (failsafe == 0) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000501 log("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000502 return(NULL);
503 }
504 }
505 if (*ptr != ',')
506 break;
507 ++ptr;
508 n1 = -1;
509 n2 = -1;
510 }
511
512 if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
Eric Andersen35e643b2003-07-28 07:40:39 +0000513 log("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000514 return(NULL);
515 }
516
517 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
518 ++ptr;
519
520#ifdef FEATURE_DEBUG_OPT
521 if (DebugOpt) {
522 int i;
523
524 for (i = 0; i < modvalue; ++i)
Eric Andersen35e643b2003-07-28 07:40:39 +0000525 log("\005%d", ary[i]);
526 log("\005\n");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000527 }
528#endif
529
530 return(ptr);
531}
532
533static void
534FixDayDow(CronLine *line)
535{
536 short i;
537 short weekUsed = 0;
538 short daysUsed = 0;
539
540 for (i = 0; i < arysize(line->cl_Dow); ++i) {
541 if (line->cl_Dow[i] == 0) {
542 weekUsed = 1;
543 break;
544 }
545 }
546 for (i = 0; i < arysize(line->cl_Days); ++i) {
547 if (line->cl_Days[i] == 0) {
548 daysUsed = 1;
549 break;
550 }
551 }
552 if (weekUsed && !daysUsed) {
553 memset(line->cl_Days, 0, sizeof(line->cl_Days));
554 }
555 if (daysUsed && !weekUsed) {
556 memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
557 }
558}
559
560
561
562static void
563SynchronizeFile(const char *fileName)
564{
565 int maxEntries = MAXLINES;
566 int maxLines;
567 char buf[1024];
568
569 if (strcmp(fileName, "root") == 0)
570 maxEntries = 65535;
571 maxLines = maxEntries * 10;
572
573 if (fileName) {
574 FILE *fi;
575
576 DeleteFile(fileName);
577
578 if ((fi = fopen(fileName, "r")) != NULL) {
579 struct stat sbuf;
580
581 if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
582 CronFile *file = calloc(1, sizeof(CronFile));
583 CronLine **pline;
584
585 file->cf_User = strdup(fileName);
586 pline = &file->cf_LineBase;
587
588 while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
589 CronLine line;
590 char *ptr;
591
592 if (buf[0])
593 buf[strlen(buf)-1] = 0;
594
595 if (buf[0] == 0 || buf[0] == '#' || buf[0] == ' ' || buf[0] == '\t')
596 continue;
597
598 if (--maxEntries == 0)
599 break;
600
601 memset(&line, 0, sizeof(line));
602
603#ifdef FEATURE_DEBUG_OPT
604 if (DebugOpt)
Eric Andersen35e643b2003-07-28 07:40:39 +0000605 log("\111User %s Entry %s\n", fileName, buf);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000606#endif
607
608 /*
609 * parse date ranges
610 */
611
612 ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
613 ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
614 ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
615 ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
616 ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
617
618 /*
619 * check failure
620 */
621
622 if (ptr == NULL)
623 continue;
624
625 /*
626 * fix days and dow - if one is not * and the other
627 * is *, the other is set to 0, and vise-versa
628 */
629
630 FixDayDow(&line);
631
632 *pline = calloc(1, sizeof(CronLine));
633 **pline = line;
634
635 /*
636 * copy command
637 */
638
639 (*pline)->cl_Shell = strdup(ptr);
640
641#ifdef FEATURE_DEBUG_OPT
642 if (DebugOpt) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000643 log("\111 Command %s\n", ptr);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000644 }
645#endif
646
647 pline = &((*pline)->cl_Next);
648 }
649 *pline = NULL;
650
651 file->cf_Next = FileBase;
652 FileBase = file;
653
654 if (maxLines == 0 || maxEntries == 0)
Eric Andersen35e643b2003-07-28 07:40:39 +0000655 log("\111Maximum number of lines reached for user %s\n", fileName);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000656 }
657 fclose(fi);
658 }
659 }
660}
661
662static void
663CheckUpdates(void)
664{
665 FILE *fi;
666 char buf[256];
667
668 if ((fi = fopen(CRONUPDATE, "r")) != NULL) {
669 remove(CRONUPDATE);
670 while (fgets(buf, sizeof(buf), fi) != NULL) {
671 SynchronizeFile(strtok(buf, " \t\r\n"));
672 }
673 fclose(fi);
674 }
675}
676
677static void
678SynchronizeDir(void)
679{
680 /*
Eric Andersen35e643b2003-07-28 07:40:39 +0000681 * Attempt to delete the database.
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000682 */
683
684 for (;;) {
685 CronFile *file;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000686
687 for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next)
688 ;
689 if (file == NULL)
690 break;
Eric Andersen35e643b2003-07-28 07:40:39 +0000691 DeleteFile(file->cf_User);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000692 }
693
694 /*
695 * Remove cron update file
696 *
697 * Re-chdir, in case directory was renamed & deleted, or otherwise
698 * screwed up.
699 *
700 * scan directory and add associated users
701 */
702
703 remove(CRONUPDATE);
704 if (chdir(CDir) < 0) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000705 log("\311unable to find %s\n", CDir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000706 }
707 {
708 DIR *dir;
709 struct dirent *den;
710
711 if ((dir = opendir("."))) {
712 while ((den = readdir(dir))) {
713 if (strchr(den->d_name, '.') != NULL)
714 continue;
715 if (getpwnam(den->d_name))
716 SynchronizeFile(den->d_name);
717 else
Eric Andersen35e643b2003-07-28 07:40:39 +0000718 log("\007ignoring %s\n", den->d_name);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000719 }
720 closedir(dir);
721 } else {
Eric Andersen35e643b2003-07-28 07:40:39 +0000722 log("\311Unable to open current dir!\n");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000723 }
724 }
725}
726
727
728/*
729 * DeleteFile() - delete user database
730 *
731 * Note: multiple entries for same user may exist if we were unable to
732 * completely delete a database due to running processes.
733 */
734
735static void
736DeleteFile(const char *userName)
737{
738 CronFile **pfile = &FileBase;
739 CronFile *file;
740
741 while ((file = *pfile) != NULL) {
742 if (strcmp(userName, file->cf_User) == 0) {
743 CronLine **pline = &file->cf_LineBase;
744 CronLine *line;
745
746 file->cf_Running = 0;
747 file->cf_Deleted = 1;
748
749 while ((line = *pline) != NULL) {
750 if (line->cl_Pid > 0) {
751 file->cf_Running = 1;
752 pline = &line->cl_Next;
753 } else {
754 *pline = line->cl_Next;
755 free(line->cl_Shell);
756 free(line);
757 }
758 }
759 if (file->cf_Running == 0) {
760 *pfile = file->cf_Next;
761 free(file->cf_User);
762 free(file);
763 } else {
764 pfile = &file->cf_Next;
765 }
766 } else {
767 pfile = &file->cf_Next;
768 }
769 }
770}
771
772/*
773 * TestJobs()
774 *
775 * determine which jobs need to be run. Under normal conditions, the
776 * period is about a minute (one scan). Worst case it will be one
777 * hour (60 scans).
778 */
779
780static int
781TestJobs(time_t t1, time_t t2)
782{
783 short nJobs = 0;
784 time_t t;
785
786 /*
787 * Find jobs > t1 and <= t2
788 */
789
790 for (t = t1 - t1 % 60; t <= t2; t += 60) {
791 if (t > t1) {
792 struct tm *tp = localtime(&t);
793 CronFile *file;
794 CronLine *line;
795
796 for (file = FileBase; file; file = file->cf_Next) {
797#ifdef FEATURE_DEBUG_OPT
798 if (DebugOpt)
Eric Andersen35e643b2003-07-28 07:40:39 +0000799 log("\005FILE %s:\n", file->cf_User);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000800#endif
801 if (file->cf_Deleted)
802 continue;
803 for (line = file->cf_LineBase; line; line = line->cl_Next) {
804#ifdef FEATURE_DEBUG_OPT
805 if (DebugOpt)
Eric Andersen35e643b2003-07-28 07:40:39 +0000806 log("\005 LINE %s\n", line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000807#endif
808 if (line->cl_Mins[tp->tm_min] &&
809 line->cl_Hrs[tp->tm_hour] &&
810 (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) &&
811 line->cl_Mons[tp->tm_mon]
812 ) {
813#ifdef FEATURE_DEBUG_OPT
814 if (DebugOpt)
Eric Andersen35e643b2003-07-28 07:40:39 +0000815 log("\005 JobToDo: %d %s\n", line->cl_Pid, line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000816#endif
817 if (line->cl_Pid > 0) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000818 log("\010 process already running: %s %s\n",
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000819 file->cf_User,
820 line->cl_Shell
821 );
822 } else if (line->cl_Pid == 0) {
823 line->cl_Pid = -1;
824 file->cf_Ready = 1;
825 ++nJobs;
826 }
827 }
828 }
829 }
830 }
831 }
832 return(nJobs);
833}
834
835static void
836RunJobs(void)
837{
838 CronFile *file;
839 CronLine *line;
840
841 for (file = FileBase; file; file = file->cf_Next) {
842 if (file->cf_Ready) {
843 file->cf_Ready = 0;
844
845 for (line = file->cf_LineBase; line; line = line->cl_Next) {
846 if (line->cl_Pid < 0) {
847
Eric Andersen35e643b2003-07-28 07:40:39 +0000848 RunJob(file->cf_User, line);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000849
Eric Andersen35e643b2003-07-28 07:40:39 +0000850 log("\010USER %s pid %3d cmd %s\n",
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000851 file->cf_User,
852 line->cl_Pid,
853 line->cl_Shell
854 );
855 if (line->cl_Pid < 0)
856 file->cf_Ready = 1;
857 else if (line->cl_Pid > 0)
858 file->cf_Running = 1;
859 }
860 }
861 }
862 }
863}
864
865/*
866 * CheckJobs() - check for job completion
867 *
868 * Check for job completion, return number of jobs still running after
869 * all done.
870 */
871
872static int
873CheckJobs(void)
874{
875 CronFile *file;
876 CronLine *line;
877 int nStillRunning = 0;
878
879 for (file = FileBase; file; file = file->cf_Next) {
880 if (file->cf_Running) {
881 file->cf_Running = 0;
882
883 for (line = file->cf_LineBase; line; line = line->cl_Next) {
884 if (line->cl_Pid > 0) {
885 int status;
886 int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
887
888 if (r < 0 || r == line->cl_Pid) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000889 EndJob(file->cf_User, line);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000890 if (line->cl_Pid)
891 file->cf_Running = 1;
892 } else if (r == 0) {
893 file->cf_Running = 1;
894 }
895 }
896 }
897 }
898 nStillRunning += file->cf_Running;
899 }
900 return(nStillRunning);
901}
902
903
Eric Andersen35e643b2003-07-28 07:40:39 +0000904#ifdef CONFIG_FEATURE_CROND_CALL_SENDMAIL
905static void
906ForkJob(const char *user, CronLine *line, int mailFd,
907 const char *prog, const char *cmd, const char *arg, const char *mailf)
908{
909 /*
910 * Fork as the user in question and run program
911 */
912 pid_t pid = fork();
913
914 line->cl_Pid = pid;
915 if (pid == 0) {
916 /*
917 * CHILD
918 */
919
920 /*
921 * Change running state to the user in question
922 */
923
924 if (ChangeUser(user) < 0)
925 exit(0);
926
927#ifdef FEATURE_DEBUG_OPT
928 if (DebugOpt)
929 log("\005Child Running %s\n", prog);
930#endif
931
932 if (mailFd >= 0) {
933 dup2(mailFd, mailf != NULL);
934 dup2((mailf ? mailFd : 1), 2);
935 close(mailFd);
936 }
937 execl(prog, prog, cmd, arg, NULL);
938 log("\024unable to exec, user %s cmd %s %s %s\n", user,
939 prog, cmd, arg);
940 if(mailf)
941 fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
942 exit(0);
943 } else if (pid < 0) {
944 /*
945 * FORK FAILED
946 */
947 log("\024couldn't fork, user %s\n", user);
948 line->cl_Pid = 0;
949 if(mailf)
950 remove(mailf);
951 } else if(mailf) {
952 /*
953 * PARENT, FORK SUCCESS
954 *
955 * rename mail-file based on pid of process
956 */
957 char mailFile2[128];
958
959 snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d",
960 user, pid);
961 rename(mailf, mailFile2);
962 }
963 /*
964 * Close the mail file descriptor.. we can't just leave it open in
965 * a structure, closing it later, because we might run out of descriptors
966 */
967
968 if (mailFd >= 0)
969 close(mailFd);
970}
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000971
972static void
Eric Andersen35e643b2003-07-28 07:40:39 +0000973RunJob(const char *user, CronLine *line)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000974{
975 char mailFile[128];
976 int mailFd;
977
978 line->cl_Pid = 0;
979 line->cl_MailFlag = 0;
980
981 /*
982 * open mail file - owner root so nobody can screw with it.
983 */
984
985 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
Eric Andersen35e643b2003-07-28 07:40:39 +0000986 user, getpid());
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000987 mailFd = open(mailFile, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_APPEND, 0600);
988
989 if (mailFd >= 0) {
990 line->cl_MailFlag = 1;
Eric Andersen35e643b2003-07-28 07:40:39 +0000991 fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,
992 line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000993 line->cl_MailPos = lseek(mailFd, 0, 1);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000994 } else {
Eric Andersen35e643b2003-07-28 07:40:39 +0000995 log("\024unable to create mail file user %s file %s, output to /dev/null\n",
996 user, mailFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000997 }
998
Eric Andersen35e643b2003-07-28 07:40:39 +0000999 ForkJob(user, line, mailFd, def_sh, "-c", line->cl_Shell, mailFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001000}
1001
1002/*
1003 * EndJob - called when job terminates and when mail terminates
1004 */
1005
1006static void
Eric Andersen35e643b2003-07-28 07:40:39 +00001007EndJob(const char *user, CronLine *line)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001008{
1009 int mailFd;
1010 char mailFile[128];
1011 struct stat sbuf;
1012
1013 /*
1014 * No job
1015 */
1016
1017 if (line->cl_Pid <= 0) {
1018 line->cl_Pid = 0;
1019 return;
1020 }
1021
1022 /*
1023 * End of job and no mail file
1024 * End of sendmail job
1025 */
1026
1027 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
Eric Andersen35e643b2003-07-28 07:40:39 +00001028 user, line->cl_Pid);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001029 line->cl_Pid = 0;
1030
1031 if (line->cl_MailFlag != 1)
1032 return;
1033
1034 line->cl_MailFlag = 0;
1035
1036 /*
1037 * End of primary job - check for mail file. If size has increased and
1038 * the file is still valid, we sendmail it.
1039 */
1040
1041 mailFd = open(mailFile, O_RDONLY);
1042 remove(mailFile);
1043 if (mailFd < 0) {
1044 return;
1045 }
1046
1047 if (fstat(mailFd, &sbuf) < 0 ||
1048 sbuf.st_uid != DaemonUid ||
1049 sbuf.st_nlink != 0 ||
1050 sbuf.st_size == line->cl_MailPos ||
1051 !S_ISREG(sbuf.st_mode)
1052 ) {
1053 close(mailFd);
1054 return;
1055 }
Eric Andersen35e643b2003-07-28 07:40:39 +00001056 ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
1057}
1058#else
1059/* crond whithout sendmail */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001060
Eric Andersen35e643b2003-07-28 07:40:39 +00001061static void
1062RunJob(const char *user, CronLine *line)
1063{
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001064 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001065 * Fork as the user in question and run program
1066 */
1067 pid_t pid = fork();
1068
1069 if (pid == 0) {
1070 /*
1071 * CHILD
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001072 */
1073
1074 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001075 * Change running state to the user in question
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001076 */
1077
Eric Andersen35e643b2003-07-28 07:40:39 +00001078 if (ChangeUser(user) < 0)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001079 exit(0);
1080
Eric Andersen35e643b2003-07-28 07:40:39 +00001081#ifdef FEATURE_DEBUG_OPT
1082 if (DebugOpt)
1083 log("\005Child Running %s\n", def_sh);
1084#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001085
Eric Andersen35e643b2003-07-28 07:40:39 +00001086 execl(def_sh, def_sh, "-c", line->cl_Shell, NULL);
1087 log("\024unable to exec, user %s cmd %s -c %s\n", user,
1088 def_sh, line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001089 exit(0);
Eric Andersen35e643b2003-07-28 07:40:39 +00001090 } else if (pid < 0) {
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001091 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001092 * FORK FAILED
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001093 */
Eric Andersen35e643b2003-07-28 07:40:39 +00001094 log("\024couldn't fork, user %s\n", user);
1095 pid = 0;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001096 }
Eric Andersen35e643b2003-07-28 07:40:39 +00001097 line->cl_Pid = pid;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001098}
Eric Andersen35e643b2003-07-28 07:40:39 +00001099#endif /* CONFIG_FEATURE_CROND_CALL_SENDMAIL */