blob: 6de00dfde692ef74e16dcce315afaa5d1485eea6 [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 Andersenf6f7bfb2002-10-22 12:24:59 +000057typedef struct CronFile {
58 struct CronFile *cf_Next;
59 struct CronLine *cf_LineBase;
60 char *cf_User; /* username */
61 int cf_Ready; /* bool: one or more jobs ready */
62 int cf_Running; /* bool: one or more jobs running */
63 int cf_Deleted; /* marked for deletion, ignore */
64} CronFile;
65
66typedef struct CronLine {
67 struct CronLine *cl_Next;
68 char *cl_Shell; /* shell command */
Eric Andersen35e643b2003-07-28 07:40:39 +000069 pid_t cl_Pid; /* running pid, 0, or armed (-1) */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000070 int cl_MailFlag; /* running pid is for mail */
71 int cl_MailPos; /* 'empty file' size */
72 char cl_Mins[60]; /* 0-59 */
73 char cl_Hrs[24]; /* 0-23 */
74 char cl_Days[32]; /* 1-31 */
75 char cl_Mons[12]; /* 0-11 */
76 char cl_Dow[7]; /* 0-6, beginning sunday */
77} CronLine;
78
79#define RUN_RANOUT 1
80#define RUN_RUNNING 2
81#define RUN_FAILED 3
82
83#define DaemonUid 0
84
85#ifdef FEATURE_DEBUG_OPT
86static short DebugOpt;
87#endif
88
89static short LogLevel = 8;
Eric Andersen35e643b2003-07-28 07:40:39 +000090static const char *LogFile;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000091static const char *CDir = CRONTABS;
92
Eric Andersenf6f7bfb2002-10-22 12:24:59 +000093static void startlogger(void);
94
95static void CheckUpdates(void);
96static void SynchronizeDir(void);
97static int TestJobs(time_t t1, time_t t2);
98static void RunJobs(void);
99static int CheckJobs(void);
Eric Andersen35e643b2003-07-28 07:40:39 +0000100
101static void RunJob(const char *user, CronLine *line);
102#ifdef CONFIG_FEATURE_CROND_CALL_SENDMAIL
103static void EndJob(const char *user, CronLine *line);
104#else
105#define EndJob(user, line) line->cl_Pid = 0
106#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000107
108static void DeleteFile(const char *userName);
109
110static CronFile *FileBase;
111
112
Eric Andersen35e643b2003-07-28 07:40:39 +0000113static void
Eric Andersen9b476612003-07-28 09:37:56 +0000114crondlog(const char *ctl, ...)
Eric Andersen35e643b2003-07-28 07:40:39 +0000115{
116 va_list va;
Eric Andersena48b0a32003-10-22 10:56:47 +0000117 const char *fmt;
Eric Andersen35e643b2003-07-28 07:40:39 +0000118 int level = (int)(ctl[0] & 0xf);
119 int type = level == 20 ?
120 LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE);
121
122
123 va_start(va, ctl);
Eric Andersena48b0a32003-10-22 10:56:47 +0000124 fmt = ctl+1;
Eric Andersen35e643b2003-07-28 07:40:39 +0000125 if (level >= LogLevel) {
126
127#ifdef FEATURE_DEBUG_OPT
Eric Andersena48b0a32003-10-22 10:56:47 +0000128 if (DebugOpt) vfprintf(stderr, fmt, va);
Eric Andersen35e643b2003-07-28 07:40:39 +0000129 else
130#endif
Eric Andersena48b0a32003-10-22 10:56:47 +0000131 if (LogFile == 0) vsyslog(type, fmt, va);
Eric Andersen35e643b2003-07-28 07:40:39 +0000132 else {
133 int logfd;
134
135 if ((logfd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 600)) >= 0) {
Eric Andersena48b0a32003-10-22 10:56:47 +0000136 vdprintf(logfd, fmt, va);
Eric Andersen35e643b2003-07-28 07:40:39 +0000137 close(logfd);
138#ifdef FEATURE_DEBUG_OPT
139 } else {
140 bb_perror_msg("Can't open log file");
141#endif
142 }
143 }
144 }
145 va_end(va);
146 if(ctl[0] & 0200)
147 exit(20);
148}
149
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000150int
151crond_main(int ac, char **av)
152{
Eric Andersen8876fb22003-06-20 09:01:58 +0000153 unsigned long opt;
154 char *lopt, *Lopt, *copt;
155#ifdef FEATURE_DEBUG_OPT
156 char *dopt;
157 bb_opt_complementaly = "f-b:b-f:S-L:L-S:d-l";
158#else
159 bb_opt_complementaly = "f-b:b-f:S-L:L-S";
160#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000161
162 opterr = 0; /* disable getopt 'errors' message.*/
Eric Andersen8876fb22003-06-20 09:01:58 +0000163 opt = bb_getopt_ulflags(ac, av, "l:L:fbSc:"
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000164#ifdef FEATURE_DEBUG_OPT
165 "d:"
166#endif
Eric Andersen8876fb22003-06-20 09:01:58 +0000167 , &lopt, &Lopt, &copt
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000168#ifdef FEATURE_DEBUG_OPT
Eric Andersen8876fb22003-06-20 09:01:58 +0000169 , &dopt
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000170#endif
Eric Andersen8876fb22003-06-20 09:01:58 +0000171 );
172 if(opt & 1)
173 LogLevel = atoi(lopt);
Eric Andersen35e643b2003-07-28 07:40:39 +0000174 if(opt & 2)
Eric Andersen8876fb22003-06-20 09:01:58 +0000175 if (*Lopt != 0) LogFile = Lopt;
Eric Andersen8876fb22003-06-20 09:01:58 +0000176 if(opt & 32) {
177 if (*copt != 0) CDir = copt;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000178 }
Eric Andersen8876fb22003-06-20 09:01:58 +0000179#ifdef FEATURE_DEBUG_OPT
180 if(opt & 64) {
181 DebugOpt = atoi(dopt);
182 LogLevel = 0;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000183 }
Eric Andersen8876fb22003-06-20 09:01:58 +0000184#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000185
186 /*
187 * change directory
188 */
189
190 if (chdir(CDir) != 0)
Eric Andersen35e643b2003-07-28 07:40:39 +0000191 bb_perror_msg_and_die("%s", CDir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000192
Eric Andersen8876fb22003-06-20 09:01:58 +0000193 signal(SIGHUP,SIG_IGN); /* hmm.. but, if kill -HUP original
194 * version - his died. ;(
195 */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000196 /*
197 * close stdin and stdout, stderr.
198 * close unused descriptors - don't need.
199 * optional detach from controlling terminal
200 */
201
Eric Andersen35e643b2003-07-28 07:40:39 +0000202 if (!(opt & 4)) {
203 if(daemon(1, 0) < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000204 bb_perror_msg_and_die("daemon");
Eric Andersen68d4a852003-07-28 09:31:28 +0000205 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000206#if defined(__uClinux__)
Eric Andersen68d4a852003-07-28 09:31:28 +0000207 else {
Eric Andersen35e643b2003-07-28 07:40:39 +0000208 /* 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 Andersen9b476612003-07-28 09:37:56 +0000221 crondlog("\011%s " VERSION " dillon, started, log level %d\n", bb_applet_name,
Eric Andersen35e643b2003-07-28 07:40:39 +0000222 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 Andersen9b476612003-07-28 09:37:56 +0000263 crondlog("\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 Andersen9b476612003-07-28 09:37:56 +0000267 crondlog("\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;
Glenn L McGrath99bd5ad2003-09-03 12:18:42 +0000304 const char *err_msg;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000305
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);
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +0000316 setenv("SHELL", DEFAULT_SHELL, 1);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000317
318 /*
319 * Change running state to the user in question
320 */
Glenn L McGrath99bd5ad2003-09-03 12:18:42 +0000321 err_msg = change_identity_e2str(pas);
322 if (err_msg) {
323 crondlog("\011%s for user %s", err_msg, user);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000324 return(-1);
325 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000326 if (chdir(pas->pw_dir) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000327 crondlog("\011chdir failed: %s: %m", pas->pw_dir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000328 if (chdir(TMPDIR) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000329 crondlog("\011chdir failed: %s: %m", TMPDIR);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000330 return(-1);
331 }
332 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000333 return(pas->pw_uid);
334}
335
336static void
337startlogger(void)
338{
Eric Andersen35e643b2003-07-28 07:40:39 +0000339 if (LogFile == 0)
340 openlog(bb_applet_name, LOG_CONS|LOG_PID, LOG_CRON);
341#ifdef FEATURE_DEBUG_OPT
342 else { /* test logfile */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000343 int logfd;
344
Eric Andersen35e643b2003-07-28 07:40:39 +0000345 if ((logfd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 600)) >= 0)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000346 close(logfd);
347 else
Eric Andersen35e643b2003-07-28 07:40:39 +0000348 bb_perror_msg("Failed to open log file '%s' reason", LogFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000349 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000350#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000351}
352
353
354static const char * const DowAry[] = {
355 "sun",
356 "mon",
357 "tue",
358 "wed",
359 "thu",
360 "fri",
361 "sat",
362
363 "Sun",
364 "Mon",
365 "Tue",
366 "Wed",
367 "Thu",
368 "Fri",
369 "Sat",
370 NULL
371};
372
373static const char * const MonAry[] = {
374 "jan",
375 "feb",
376 "mar",
377 "apr",
378 "may",
379 "jun",
380 "jul",
381 "aug",
382 "sep",
383 "oct",
384 "nov",
385 "dec",
386
387 "Jan",
388 "Feb",
389 "Mar",
390 "Apr",
391 "May",
392 "Jun",
393 "Jul",
394 "Aug",
395 "Sep",
396 "Oct",
397 "Nov",
398 "Dec",
399 NULL
400};
401
402static char *
403ParseField(char *user, char *ary, int modvalue, int off,
404 const char * const *names, char *ptr)
405{
406 char *base = ptr;
407 int n1 = -1;
408 int n2 = -1;
409
410 if (base == NULL)
411 return(NULL);
412
413 while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
414 int skip = 0;
415
416 /*
417 * Handle numeric digit or symbol or '*'
418 */
419
420 if (*ptr == '*') {
421 n1 = 0; /* everything will be filled */
422 n2 = modvalue - 1;
423 skip = 1;
424 ++ptr;
425 } else if (*ptr >= '0' && *ptr <= '9') {
426 if (n1 < 0)
427 n1 = strtol(ptr, &ptr, 10) + off;
428 else
429 n2 = strtol(ptr, &ptr, 10) + off;
430 skip = 1;
431 } else if (names) {
432 int i;
433
434 for (i = 0; names[i]; ++i) {
435 if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
436 break;
437 }
438 }
439 if (names[i]) {
440 ptr += strlen(names[i]);
441 if (n1 < 0)
442 n1 = i;
443 else
444 n2 = i;
445 skip = 1;
446 }
447 }
448
449 /*
450 * handle optional range '-'
451 */
452
453 if (skip == 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000454 crondlog("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000455 return(NULL);
456 }
457 if (*ptr == '-' && n2 < 0) {
458 ++ptr;
459 continue;
460 }
461
462 /*
463 * collapse single-value ranges, handle skipmark, and fill
464 * in the character array appropriately.
465 */
466
467 if (n2 < 0)
468 n2 = n1;
469
470 if (*ptr == '/')
471 skip = strtol(ptr + 1, &ptr, 10);
472
473 /*
474 * fill array, using a failsafe is the easiest way to prevent
475 * an endless loop
476 */
477
478 {
479 int s0 = 1;
480 int failsafe = 1024;
481
482 --n1;
483 do {
484 n1 = (n1 + 1) % modvalue;
485
486 if (--s0 == 0) {
487 ary[n1 % modvalue] = 1;
488 s0 = skip;
489 }
490 } while (n1 != n2 && --failsafe);
491
492 if (failsafe == 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000493 crondlog("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000494 return(NULL);
495 }
496 }
497 if (*ptr != ',')
498 break;
499 ++ptr;
500 n1 = -1;
501 n2 = -1;
502 }
503
504 if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
Eric Andersen9b476612003-07-28 09:37:56 +0000505 crondlog("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000506 return(NULL);
507 }
508
509 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
510 ++ptr;
511
512#ifdef FEATURE_DEBUG_OPT
513 if (DebugOpt) {
514 int i;
515
516 for (i = 0; i < modvalue; ++i)
Eric Andersen9b476612003-07-28 09:37:56 +0000517 crondlog("\005%d", ary[i]);
518 crondlog("\005\n");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000519 }
520#endif
521
522 return(ptr);
523}
524
525static void
526FixDayDow(CronLine *line)
527{
528 short i;
529 short weekUsed = 0;
530 short daysUsed = 0;
531
532 for (i = 0; i < arysize(line->cl_Dow); ++i) {
533 if (line->cl_Dow[i] == 0) {
534 weekUsed = 1;
535 break;
536 }
537 }
538 for (i = 0; i < arysize(line->cl_Days); ++i) {
539 if (line->cl_Days[i] == 0) {
540 daysUsed = 1;
541 break;
542 }
543 }
544 if (weekUsed && !daysUsed) {
545 memset(line->cl_Days, 0, sizeof(line->cl_Days));
546 }
547 if (daysUsed && !weekUsed) {
548 memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
549 }
550}
551
552
553
554static void
555SynchronizeFile(const char *fileName)
556{
557 int maxEntries = MAXLINES;
558 int maxLines;
559 char buf[1024];
560
561 if (strcmp(fileName, "root") == 0)
562 maxEntries = 65535;
563 maxLines = maxEntries * 10;
564
565 if (fileName) {
566 FILE *fi;
567
568 DeleteFile(fileName);
569
570 if ((fi = fopen(fileName, "r")) != NULL) {
571 struct stat sbuf;
572
573 if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
574 CronFile *file = calloc(1, sizeof(CronFile));
575 CronLine **pline;
576
577 file->cf_User = strdup(fileName);
578 pline = &file->cf_LineBase;
579
580 while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
581 CronLine line;
582 char *ptr;
583
584 if (buf[0])
585 buf[strlen(buf)-1] = 0;
586
587 if (buf[0] == 0 || buf[0] == '#' || buf[0] == ' ' || buf[0] == '\t')
588 continue;
589
590 if (--maxEntries == 0)
591 break;
592
593 memset(&line, 0, sizeof(line));
594
595#ifdef FEATURE_DEBUG_OPT
596 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000597 crondlog("\111User %s Entry %s\n", fileName, buf);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000598#endif
599
600 /*
601 * parse date ranges
602 */
603
604 ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
605 ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
606 ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
607 ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
608 ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
609
610 /*
611 * check failure
612 */
613
614 if (ptr == NULL)
615 continue;
616
617 /*
618 * fix days and dow - if one is not * and the other
619 * is *, the other is set to 0, and vise-versa
620 */
621
622 FixDayDow(&line);
623
624 *pline = calloc(1, sizeof(CronLine));
625 **pline = line;
626
627 /*
628 * copy command
629 */
630
631 (*pline)->cl_Shell = strdup(ptr);
632
633#ifdef FEATURE_DEBUG_OPT
634 if (DebugOpt) {
Eric Andersen9b476612003-07-28 09:37:56 +0000635 crondlog("\111 Command %s\n", ptr);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000636 }
637#endif
638
639 pline = &((*pline)->cl_Next);
640 }
641 *pline = NULL;
642
643 file->cf_Next = FileBase;
644 FileBase = file;
645
646 if (maxLines == 0 || maxEntries == 0)
Eric Andersen9b476612003-07-28 09:37:56 +0000647 crondlog("\111Maximum number of lines reached for user %s\n", fileName);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000648 }
649 fclose(fi);
650 }
651 }
652}
653
654static void
655CheckUpdates(void)
656{
657 FILE *fi;
658 char buf[256];
659
660 if ((fi = fopen(CRONUPDATE, "r")) != NULL) {
661 remove(CRONUPDATE);
662 while (fgets(buf, sizeof(buf), fi) != NULL) {
663 SynchronizeFile(strtok(buf, " \t\r\n"));
664 }
665 fclose(fi);
666 }
667}
668
669static void
670SynchronizeDir(void)
671{
672 /*
Eric Andersen35e643b2003-07-28 07:40:39 +0000673 * Attempt to delete the database.
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000674 */
675
676 for (;;) {
677 CronFile *file;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000678
679 for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next)
680 ;
681 if (file == NULL)
682 break;
Eric Andersen35e643b2003-07-28 07:40:39 +0000683 DeleteFile(file->cf_User);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000684 }
685
686 /*
687 * Remove cron update file
688 *
689 * Re-chdir, in case directory was renamed & deleted, or otherwise
690 * screwed up.
691 *
692 * scan directory and add associated users
693 */
694
695 remove(CRONUPDATE);
696 if (chdir(CDir) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000697 crondlog("\311unable to find %s\n", CDir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000698 }
699 {
700 DIR *dir;
701 struct dirent *den;
702
703 if ((dir = opendir("."))) {
704 while ((den = readdir(dir))) {
705 if (strchr(den->d_name, '.') != NULL)
706 continue;
707 if (getpwnam(den->d_name))
708 SynchronizeFile(den->d_name);
709 else
Eric Andersen9b476612003-07-28 09:37:56 +0000710 crondlog("\007ignoring %s\n", den->d_name);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000711 }
712 closedir(dir);
713 } else {
Eric Andersen9b476612003-07-28 09:37:56 +0000714 crondlog("\311Unable to open current dir!\n");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000715 }
716 }
717}
718
719
720/*
721 * DeleteFile() - delete user database
722 *
723 * Note: multiple entries for same user may exist if we were unable to
724 * completely delete a database due to running processes.
725 */
726
727static void
728DeleteFile(const char *userName)
729{
730 CronFile **pfile = &FileBase;
731 CronFile *file;
732
733 while ((file = *pfile) != NULL) {
734 if (strcmp(userName, file->cf_User) == 0) {
735 CronLine **pline = &file->cf_LineBase;
736 CronLine *line;
737
738 file->cf_Running = 0;
739 file->cf_Deleted = 1;
740
741 while ((line = *pline) != NULL) {
742 if (line->cl_Pid > 0) {
743 file->cf_Running = 1;
744 pline = &line->cl_Next;
745 } else {
746 *pline = line->cl_Next;
747 free(line->cl_Shell);
748 free(line);
749 }
750 }
751 if (file->cf_Running == 0) {
752 *pfile = file->cf_Next;
753 free(file->cf_User);
754 free(file);
755 } else {
756 pfile = &file->cf_Next;
757 }
758 } else {
759 pfile = &file->cf_Next;
760 }
761 }
762}
763
764/*
765 * TestJobs()
766 *
767 * determine which jobs need to be run. Under normal conditions, the
768 * period is about a minute (one scan). Worst case it will be one
769 * hour (60 scans).
770 */
771
772static int
773TestJobs(time_t t1, time_t t2)
774{
775 short nJobs = 0;
776 time_t t;
777
778 /*
779 * Find jobs > t1 and <= t2
780 */
781
782 for (t = t1 - t1 % 60; t <= t2; t += 60) {
783 if (t > t1) {
784 struct tm *tp = localtime(&t);
785 CronFile *file;
786 CronLine *line;
787
788 for (file = FileBase; file; file = file->cf_Next) {
789#ifdef FEATURE_DEBUG_OPT
790 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000791 crondlog("\005FILE %s:\n", file->cf_User);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000792#endif
793 if (file->cf_Deleted)
794 continue;
795 for (line = file->cf_LineBase; line; line = line->cl_Next) {
796#ifdef FEATURE_DEBUG_OPT
797 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000798 crondlog("\005 LINE %s\n", line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000799#endif
800 if (line->cl_Mins[tp->tm_min] &&
801 line->cl_Hrs[tp->tm_hour] &&
802 (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) &&
803 line->cl_Mons[tp->tm_mon]
804 ) {
805#ifdef FEATURE_DEBUG_OPT
806 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000807 crondlog("\005 JobToDo: %d %s\n", line->cl_Pid, line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000808#endif
809 if (line->cl_Pid > 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000810 crondlog("\010 process already running: %s %s\n",
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000811 file->cf_User,
812 line->cl_Shell
813 );
814 } else if (line->cl_Pid == 0) {
815 line->cl_Pid = -1;
816 file->cf_Ready = 1;
817 ++nJobs;
818 }
819 }
820 }
821 }
822 }
823 }
824 return(nJobs);
825}
826
827static void
828RunJobs(void)
829{
830 CronFile *file;
831 CronLine *line;
832
833 for (file = FileBase; file; file = file->cf_Next) {
834 if (file->cf_Ready) {
835 file->cf_Ready = 0;
836
837 for (line = file->cf_LineBase; line; line = line->cl_Next) {
838 if (line->cl_Pid < 0) {
839
Eric Andersen35e643b2003-07-28 07:40:39 +0000840 RunJob(file->cf_User, line);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000841
Eric Andersen9b476612003-07-28 09:37:56 +0000842 crondlog("\010USER %s pid %3d cmd %s\n",
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000843 file->cf_User,
844 line->cl_Pid,
845 line->cl_Shell
846 );
847 if (line->cl_Pid < 0)
848 file->cf_Ready = 1;
849 else if (line->cl_Pid > 0)
850 file->cf_Running = 1;
851 }
852 }
853 }
854 }
855}
856
857/*
858 * CheckJobs() - check for job completion
859 *
860 * Check for job completion, return number of jobs still running after
861 * all done.
862 */
863
864static int
865CheckJobs(void)
866{
867 CronFile *file;
868 CronLine *line;
869 int nStillRunning = 0;
870
871 for (file = FileBase; file; file = file->cf_Next) {
872 if (file->cf_Running) {
873 file->cf_Running = 0;
874
875 for (line = file->cf_LineBase; line; line = line->cl_Next) {
876 if (line->cl_Pid > 0) {
877 int status;
878 int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
879
880 if (r < 0 || r == line->cl_Pid) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000881 EndJob(file->cf_User, line);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000882 if (line->cl_Pid)
883 file->cf_Running = 1;
884 } else if (r == 0) {
885 file->cf_Running = 1;
886 }
887 }
888 }
889 }
890 nStillRunning += file->cf_Running;
891 }
892 return(nStillRunning);
893}
894
895
Eric Andersen35e643b2003-07-28 07:40:39 +0000896#ifdef CONFIG_FEATURE_CROND_CALL_SENDMAIL
897static void
898ForkJob(const char *user, CronLine *line, int mailFd,
899 const char *prog, const char *cmd, const char *arg, const char *mailf)
900{
901 /*
902 * Fork as the user in question and run program
903 */
904 pid_t pid = fork();
905
906 line->cl_Pid = pid;
907 if (pid == 0) {
908 /*
909 * CHILD
910 */
911
912 /*
913 * Change running state to the user in question
914 */
915
916 if (ChangeUser(user) < 0)
917 exit(0);
918
919#ifdef FEATURE_DEBUG_OPT
920 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000921 crondlog("\005Child Running %s\n", prog);
Eric Andersen35e643b2003-07-28 07:40:39 +0000922#endif
923
924 if (mailFd >= 0) {
925 dup2(mailFd, mailf != NULL);
926 dup2((mailf ? mailFd : 1), 2);
927 close(mailFd);
928 }
929 execl(prog, prog, cmd, arg, NULL);
Eric Andersen9b476612003-07-28 09:37:56 +0000930 crondlog("\024unable to exec, user %s cmd %s %s %s\n", user,
Eric Andersen35e643b2003-07-28 07:40:39 +0000931 prog, cmd, arg);
932 if(mailf)
933 fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
934 exit(0);
935 } else if (pid < 0) {
936 /*
937 * FORK FAILED
938 */
Eric Andersen9b476612003-07-28 09:37:56 +0000939 crondlog("\024couldn't fork, user %s\n", user);
Eric Andersen35e643b2003-07-28 07:40:39 +0000940 line->cl_Pid = 0;
941 if(mailf)
942 remove(mailf);
943 } else if(mailf) {
944 /*
945 * PARENT, FORK SUCCESS
946 *
947 * rename mail-file based on pid of process
948 */
949 char mailFile2[128];
950
951 snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d",
952 user, pid);
953 rename(mailf, mailFile2);
954 }
955 /*
956 * Close the mail file descriptor.. we can't just leave it open in
957 * a structure, closing it later, because we might run out of descriptors
958 */
959
960 if (mailFd >= 0)
961 close(mailFd);
962}
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000963
964static void
Eric Andersen35e643b2003-07-28 07:40:39 +0000965RunJob(const char *user, CronLine *line)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000966{
967 char mailFile[128];
968 int mailFd;
969
970 line->cl_Pid = 0;
971 line->cl_MailFlag = 0;
972
973 /*
974 * open mail file - owner root so nobody can screw with it.
975 */
976
977 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
Eric Andersen35e643b2003-07-28 07:40:39 +0000978 user, getpid());
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000979 mailFd = open(mailFile, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_APPEND, 0600);
980
981 if (mailFd >= 0) {
982 line->cl_MailFlag = 1;
Eric Andersen35e643b2003-07-28 07:40:39 +0000983 fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,
984 line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000985 line->cl_MailPos = lseek(mailFd, 0, 1);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000986 } else {
Eric Andersen9b476612003-07-28 09:37:56 +0000987 crondlog("\024unable to create mail file user %s file %s, output to /dev/null\n",
Eric Andersen35e643b2003-07-28 07:40:39 +0000988 user, mailFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000989 }
990
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +0000991 ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000992}
993
994/*
995 * EndJob - called when job terminates and when mail terminates
996 */
997
998static void
Eric Andersen35e643b2003-07-28 07:40:39 +0000999EndJob(const char *user, CronLine *line)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001000{
1001 int mailFd;
1002 char mailFile[128];
1003 struct stat sbuf;
1004
1005 /*
1006 * No job
1007 */
1008
1009 if (line->cl_Pid <= 0) {
1010 line->cl_Pid = 0;
1011 return;
1012 }
1013
1014 /*
1015 * End of job and no mail file
1016 * End of sendmail job
1017 */
1018
1019 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
Eric Andersen35e643b2003-07-28 07:40:39 +00001020 user, line->cl_Pid);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001021 line->cl_Pid = 0;
1022
1023 if (line->cl_MailFlag != 1)
1024 return;
1025
1026 line->cl_MailFlag = 0;
1027
1028 /*
1029 * End of primary job - check for mail file. If size has increased and
1030 * the file is still valid, we sendmail it.
1031 */
1032
1033 mailFd = open(mailFile, O_RDONLY);
1034 remove(mailFile);
1035 if (mailFd < 0) {
1036 return;
1037 }
1038
1039 if (fstat(mailFd, &sbuf) < 0 ||
1040 sbuf.st_uid != DaemonUid ||
1041 sbuf.st_nlink != 0 ||
1042 sbuf.st_size == line->cl_MailPos ||
1043 !S_ISREG(sbuf.st_mode)
1044 ) {
1045 close(mailFd);
1046 return;
1047 }
Eric Andersen35e643b2003-07-28 07:40:39 +00001048 ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
1049}
1050#else
1051/* crond whithout sendmail */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001052
Eric Andersen35e643b2003-07-28 07:40:39 +00001053static void
1054RunJob(const char *user, CronLine *line)
1055{
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001056 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001057 * Fork as the user in question and run program
1058 */
1059 pid_t pid = fork();
1060
1061 if (pid == 0) {
1062 /*
1063 * CHILD
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001064 */
1065
1066 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001067 * Change running state to the user in question
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001068 */
1069
Eric Andersen35e643b2003-07-28 07:40:39 +00001070 if (ChangeUser(user) < 0)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001071 exit(0);
1072
Eric Andersen35e643b2003-07-28 07:40:39 +00001073#ifdef FEATURE_DEBUG_OPT
1074 if (DebugOpt)
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +00001075 crondlog("\005Child Running %s\n", DEFAULT_SHELL);
Eric Andersen35e643b2003-07-28 07:40:39 +00001076#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001077
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +00001078 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
Eric Andersen9b476612003-07-28 09:37:56 +00001079 crondlog("\024unable to exec, user %s cmd %s -c %s\n", user,
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +00001080 DEFAULT_SHELL, line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001081 exit(0);
Eric Andersen35e643b2003-07-28 07:40:39 +00001082 } else if (pid < 0) {
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001083 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001084 * FORK FAILED
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001085 */
Eric Andersen9b476612003-07-28 09:37:56 +00001086 crondlog("\024couldn't fork, user %s\n", user);
Eric Andersen35e643b2003-07-28 07:40:39 +00001087 pid = 0;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001088 }
Eric Andersen35e643b2003-07-28 07:40:39 +00001089 line->cl_Pid = pid;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001090}
Eric Andersen35e643b2003-07-28 07:40:39 +00001091#endif /* CONFIG_FEATURE_CROND_CALL_SENDMAIL */