blob: 637e09dd8371df4a5455ccbf8a5018ae86e00e62 [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;
117 int level = (int)(ctl[0] & 0xf);
118 int type = level == 20 ?
119 LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE);
120
121
122 va_start(va, ctl);
123 if (level >= LogLevel) {
124
125#ifdef FEATURE_DEBUG_OPT
126 if (DebugOpt) vfprintf(stderr, ctl, va);
127 else
128#endif
129 if (LogFile == 0) vsyslog(type, ctl, va);
130 else {
131 int logfd;
132
133 if ((logfd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 600)) >= 0) {
134 vdprintf(logfd, ctl, va);
135 close(logfd);
136#ifdef FEATURE_DEBUG_OPT
137 } else {
138 bb_perror_msg("Can't open log file");
139#endif
140 }
141 }
142 }
143 va_end(va);
144 if(ctl[0] & 0200)
145 exit(20);
146}
147
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000148int
149crond_main(int ac, char **av)
150{
Eric Andersen8876fb22003-06-20 09:01:58 +0000151 unsigned long opt;
152 char *lopt, *Lopt, *copt;
153#ifdef FEATURE_DEBUG_OPT
154 char *dopt;
155 bb_opt_complementaly = "f-b:b-f:S-L:L-S:d-l";
156#else
157 bb_opt_complementaly = "f-b:b-f:S-L:L-S";
158#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000159
160 opterr = 0; /* disable getopt 'errors' message.*/
Eric Andersen8876fb22003-06-20 09:01:58 +0000161 opt = bb_getopt_ulflags(ac, av, "l:L:fbSc:"
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000162#ifdef FEATURE_DEBUG_OPT
163 "d:"
164#endif
Eric Andersen8876fb22003-06-20 09:01:58 +0000165 , &lopt, &Lopt, &copt
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000166#ifdef FEATURE_DEBUG_OPT
Eric Andersen8876fb22003-06-20 09:01:58 +0000167 , &dopt
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000168#endif
Eric Andersen8876fb22003-06-20 09:01:58 +0000169 );
170 if(opt & 1)
171 LogLevel = atoi(lopt);
Eric Andersen35e643b2003-07-28 07:40:39 +0000172 if(opt & 2)
Eric Andersen8876fb22003-06-20 09:01:58 +0000173 if (*Lopt != 0) LogFile = Lopt;
Eric Andersen8876fb22003-06-20 09:01:58 +0000174 if(opt & 32) {
175 if (*copt != 0) CDir = copt;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000176 }
Eric Andersen8876fb22003-06-20 09:01:58 +0000177#ifdef FEATURE_DEBUG_OPT
178 if(opt & 64) {
179 DebugOpt = atoi(dopt);
180 LogLevel = 0;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000181 }
Eric Andersen8876fb22003-06-20 09:01:58 +0000182#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000183
184 /*
185 * change directory
186 */
187
188 if (chdir(CDir) != 0)
Eric Andersen35e643b2003-07-28 07:40:39 +0000189 bb_perror_msg_and_die("%s", CDir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000190
Eric Andersen8876fb22003-06-20 09:01:58 +0000191 signal(SIGHUP,SIG_IGN); /* hmm.. but, if kill -HUP original
192 * version - his died. ;(
193 */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000194 /*
195 * close stdin and stdout, stderr.
196 * close unused descriptors - don't need.
197 * optional detach from controlling terminal
198 */
199
Eric Andersen35e643b2003-07-28 07:40:39 +0000200 if (!(opt & 4)) {
201 if(daemon(1, 0) < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000202 bb_perror_msg_and_die("daemon");
Eric Andersen68d4a852003-07-28 09:31:28 +0000203 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000204#if defined(__uClinux__)
Eric Andersen68d4a852003-07-28 09:31:28 +0000205 else {
Eric Andersen35e643b2003-07-28 07:40:39 +0000206 /* reexec for vfork() do continue parent */
207 vfork_daemon_rexec(ac, av, "-f");
208 }
209#endif /* uClinux */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000210 }
211
212 (void)startlogger(); /* need if syslog mode selected */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000213
214 /*
215 * main loop - synchronize to 1 second after the minute, minimum sleep
216 * of 1 second.
217 */
218
Eric Andersen9b476612003-07-28 09:37:56 +0000219 crondlog("\011%s " VERSION " dillon, started, log level %d\n", bb_applet_name,
Eric Andersen35e643b2003-07-28 07:40:39 +0000220 LogLevel);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000221
222 SynchronizeDir();
223
224 {
225 time_t t1 = time(NULL);
226 time_t t2;
227 long dt;
228 short rescan = 60;
229 short sleep_time = 60;
230
231 for (;;) {
232 sleep((sleep_time + 1) - (short)(time(NULL) % sleep_time));
233
234 t2 = time(NULL);
235 dt = t2 - t1;
236
237 /*
238 * The file 'cron.update' is checked to determine new cron
239 * jobs. The directory is rescanned once an hour to deal
240 * with any screwups.
241 *
242 * check for disparity. Disparities over an hour either way
243 * result in resynchronization. A reverse-indexed disparity
244 * less then an hour causes us to effectively sleep until we
245 * match the original time (i.e. no re-execution of jobs that
246 * have just been run). A forward-indexed disparity less then
247 * an hour causes intermediate jobs to be run, but only once
248 * in the worst case.
249 *
250 * when running jobs, the inequality used is greater but not
251 * equal to t1, and less then or equal to t2.
252 */
253
254 if (--rescan == 0) {
255 rescan = 60;
256 SynchronizeDir();
257 }
258 CheckUpdates();
259#ifdef FEATURE_DEBUG_OPT
260 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000261 crondlog("\005Wakeup dt=%d\n", dt);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000262#endif
263 if (dt < -60*60 || dt > 60*60) {
264 t1 = t2;
Eric Andersen9b476612003-07-28 09:37:56 +0000265 crondlog("\111time disparity of %d minutes detected\n", dt / 60);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000266 } else if (dt > 0) {
267 TestJobs(t1, t2);
268 RunJobs();
269 sleep(5);
270 if (CheckJobs() > 0)
271 sleep_time = 10;
272 else
273 sleep_time = 60;
274 t1 = t2;
275 }
276 }
277 }
278 /* not reached */
279}
280
281
Eric Andersen35e643b2003-07-28 07:40:39 +0000282#if defined(FEATURE_DEBUG_OPT) || defined(CONFIG_FEATURE_CROND_CALL_SENDMAIL)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000283/*
Eric Andersen35e643b2003-07-28 07:40:39 +0000284 write to temp file..
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000285*/
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000286static void
287fdprintf(int fd, const char *ctl, ...)
288{
289 va_list va;
290
291 va_start(va, ctl);
292 vdprintf(fd, ctl, va);
293 va_end(va);
294}
Eric Andersen35e643b2003-07-28 07:40:39 +0000295#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000296
297
298static int
Eric Andersen35e643b2003-07-28 07:40:39 +0000299ChangeUser(const char *user)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000300{
301 struct passwd *pas;
302
303 /*
304 * Obtain password entry and change privilages
305 */
306
307 if ((pas = getpwnam(user)) == 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000308 crondlog("\011failed to get uid for %s", user);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000309 return(-1);
310 }
311 setenv("USER", pas->pw_name, 1);
312 setenv("HOME", pas->pw_dir, 1);
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +0000313 setenv("SHELL", DEFAULT_SHELL, 1);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000314
315 /*
316 * Change running state to the user in question
317 */
318
319 if (initgroups(user, pas->pw_gid) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000320 crondlog("\011initgroups failed: %s %m", user);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000321 return(-1);
322 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000323 /* drop all priviledges */
324 if (setgid(pas->pw_gid) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000325 crondlog("\011setgid failed: %s %d", user, pas->pw_gid);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000326 return(-1);
327 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000328 if (setuid(pas->pw_uid) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000329 crondlog("\011setuid failed: %s %d", user, pas->pw_uid);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000330 return(-1);
331 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000332 if (chdir(pas->pw_dir) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000333 crondlog("\011chdir failed: %s: %m", pas->pw_dir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000334 if (chdir(TMPDIR) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000335 crondlog("\011chdir failed: %s: %m", TMPDIR);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000336 return(-1);
337 }
338 }
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000339 return(pas->pw_uid);
340}
341
342static void
343startlogger(void)
344{
Eric Andersen35e643b2003-07-28 07:40:39 +0000345 if (LogFile == 0)
346 openlog(bb_applet_name, LOG_CONS|LOG_PID, LOG_CRON);
347#ifdef FEATURE_DEBUG_OPT
348 else { /* test logfile */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000349 int logfd;
350
Eric Andersen35e643b2003-07-28 07:40:39 +0000351 if ((logfd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 600)) >= 0)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000352 close(logfd);
353 else
Eric Andersen35e643b2003-07-28 07:40:39 +0000354 bb_perror_msg("Failed to open log file '%s' reason", LogFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000355 }
Eric Andersen35e643b2003-07-28 07:40:39 +0000356#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000357}
358
359
360static const char * const DowAry[] = {
361 "sun",
362 "mon",
363 "tue",
364 "wed",
365 "thu",
366 "fri",
367 "sat",
368
369 "Sun",
370 "Mon",
371 "Tue",
372 "Wed",
373 "Thu",
374 "Fri",
375 "Sat",
376 NULL
377};
378
379static const char * const MonAry[] = {
380 "jan",
381 "feb",
382 "mar",
383 "apr",
384 "may",
385 "jun",
386 "jul",
387 "aug",
388 "sep",
389 "oct",
390 "nov",
391 "dec",
392
393 "Jan",
394 "Feb",
395 "Mar",
396 "Apr",
397 "May",
398 "Jun",
399 "Jul",
400 "Aug",
401 "Sep",
402 "Oct",
403 "Nov",
404 "Dec",
405 NULL
406};
407
408static char *
409ParseField(char *user, char *ary, int modvalue, int off,
410 const char * const *names, char *ptr)
411{
412 char *base = ptr;
413 int n1 = -1;
414 int n2 = -1;
415
416 if (base == NULL)
417 return(NULL);
418
419 while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
420 int skip = 0;
421
422 /*
423 * Handle numeric digit or symbol or '*'
424 */
425
426 if (*ptr == '*') {
427 n1 = 0; /* everything will be filled */
428 n2 = modvalue - 1;
429 skip = 1;
430 ++ptr;
431 } else if (*ptr >= '0' && *ptr <= '9') {
432 if (n1 < 0)
433 n1 = strtol(ptr, &ptr, 10) + off;
434 else
435 n2 = strtol(ptr, &ptr, 10) + off;
436 skip = 1;
437 } else if (names) {
438 int i;
439
440 for (i = 0; names[i]; ++i) {
441 if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
442 break;
443 }
444 }
445 if (names[i]) {
446 ptr += strlen(names[i]);
447 if (n1 < 0)
448 n1 = i;
449 else
450 n2 = i;
451 skip = 1;
452 }
453 }
454
455 /*
456 * handle optional range '-'
457 */
458
459 if (skip == 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000460 crondlog("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000461 return(NULL);
462 }
463 if (*ptr == '-' && n2 < 0) {
464 ++ptr;
465 continue;
466 }
467
468 /*
469 * collapse single-value ranges, handle skipmark, and fill
470 * in the character array appropriately.
471 */
472
473 if (n2 < 0)
474 n2 = n1;
475
476 if (*ptr == '/')
477 skip = strtol(ptr + 1, &ptr, 10);
478
479 /*
480 * fill array, using a failsafe is the easiest way to prevent
481 * an endless loop
482 */
483
484 {
485 int s0 = 1;
486 int failsafe = 1024;
487
488 --n1;
489 do {
490 n1 = (n1 + 1) % modvalue;
491
492 if (--s0 == 0) {
493 ary[n1 % modvalue] = 1;
494 s0 = skip;
495 }
496 } while (n1 != n2 && --failsafe);
497
498 if (failsafe == 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000499 crondlog("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000500 return(NULL);
501 }
502 }
503 if (*ptr != ',')
504 break;
505 ++ptr;
506 n1 = -1;
507 n2 = -1;
508 }
509
510 if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
Eric Andersen9b476612003-07-28 09:37:56 +0000511 crondlog("\111failed user %s parsing %s\n", user, base);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000512 return(NULL);
513 }
514
515 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
516 ++ptr;
517
518#ifdef FEATURE_DEBUG_OPT
519 if (DebugOpt) {
520 int i;
521
522 for (i = 0; i < modvalue; ++i)
Eric Andersen9b476612003-07-28 09:37:56 +0000523 crondlog("\005%d", ary[i]);
524 crondlog("\005\n");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000525 }
526#endif
527
528 return(ptr);
529}
530
531static void
532FixDayDow(CronLine *line)
533{
534 short i;
535 short weekUsed = 0;
536 short daysUsed = 0;
537
538 for (i = 0; i < arysize(line->cl_Dow); ++i) {
539 if (line->cl_Dow[i] == 0) {
540 weekUsed = 1;
541 break;
542 }
543 }
544 for (i = 0; i < arysize(line->cl_Days); ++i) {
545 if (line->cl_Days[i] == 0) {
546 daysUsed = 1;
547 break;
548 }
549 }
550 if (weekUsed && !daysUsed) {
551 memset(line->cl_Days, 0, sizeof(line->cl_Days));
552 }
553 if (daysUsed && !weekUsed) {
554 memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
555 }
556}
557
558
559
560static void
561SynchronizeFile(const char *fileName)
562{
563 int maxEntries = MAXLINES;
564 int maxLines;
565 char buf[1024];
566
567 if (strcmp(fileName, "root") == 0)
568 maxEntries = 65535;
569 maxLines = maxEntries * 10;
570
571 if (fileName) {
572 FILE *fi;
573
574 DeleteFile(fileName);
575
576 if ((fi = fopen(fileName, "r")) != NULL) {
577 struct stat sbuf;
578
579 if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
580 CronFile *file = calloc(1, sizeof(CronFile));
581 CronLine **pline;
582
583 file->cf_User = strdup(fileName);
584 pline = &file->cf_LineBase;
585
586 while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
587 CronLine line;
588 char *ptr;
589
590 if (buf[0])
591 buf[strlen(buf)-1] = 0;
592
593 if (buf[0] == 0 || buf[0] == '#' || buf[0] == ' ' || buf[0] == '\t')
594 continue;
595
596 if (--maxEntries == 0)
597 break;
598
599 memset(&line, 0, sizeof(line));
600
601#ifdef FEATURE_DEBUG_OPT
602 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000603 crondlog("\111User %s Entry %s\n", fileName, buf);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000604#endif
605
606 /*
607 * parse date ranges
608 */
609
610 ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
611 ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
612 ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
613 ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
614 ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
615
616 /*
617 * check failure
618 */
619
620 if (ptr == NULL)
621 continue;
622
623 /*
624 * fix days and dow - if one is not * and the other
625 * is *, the other is set to 0, and vise-versa
626 */
627
628 FixDayDow(&line);
629
630 *pline = calloc(1, sizeof(CronLine));
631 **pline = line;
632
633 /*
634 * copy command
635 */
636
637 (*pline)->cl_Shell = strdup(ptr);
638
639#ifdef FEATURE_DEBUG_OPT
640 if (DebugOpt) {
Eric Andersen9b476612003-07-28 09:37:56 +0000641 crondlog("\111 Command %s\n", ptr);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000642 }
643#endif
644
645 pline = &((*pline)->cl_Next);
646 }
647 *pline = NULL;
648
649 file->cf_Next = FileBase;
650 FileBase = file;
651
652 if (maxLines == 0 || maxEntries == 0)
Eric Andersen9b476612003-07-28 09:37:56 +0000653 crondlog("\111Maximum number of lines reached for user %s\n", fileName);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000654 }
655 fclose(fi);
656 }
657 }
658}
659
660static void
661CheckUpdates(void)
662{
663 FILE *fi;
664 char buf[256];
665
666 if ((fi = fopen(CRONUPDATE, "r")) != NULL) {
667 remove(CRONUPDATE);
668 while (fgets(buf, sizeof(buf), fi) != NULL) {
669 SynchronizeFile(strtok(buf, " \t\r\n"));
670 }
671 fclose(fi);
672 }
673}
674
675static void
676SynchronizeDir(void)
677{
678 /*
Eric Andersen35e643b2003-07-28 07:40:39 +0000679 * Attempt to delete the database.
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000680 */
681
682 for (;;) {
683 CronFile *file;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000684
685 for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next)
686 ;
687 if (file == NULL)
688 break;
Eric Andersen35e643b2003-07-28 07:40:39 +0000689 DeleteFile(file->cf_User);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000690 }
691
692 /*
693 * Remove cron update file
694 *
695 * Re-chdir, in case directory was renamed & deleted, or otherwise
696 * screwed up.
697 *
698 * scan directory and add associated users
699 */
700
701 remove(CRONUPDATE);
702 if (chdir(CDir) < 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000703 crondlog("\311unable to find %s\n", CDir);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000704 }
705 {
706 DIR *dir;
707 struct dirent *den;
708
709 if ((dir = opendir("."))) {
710 while ((den = readdir(dir))) {
711 if (strchr(den->d_name, '.') != NULL)
712 continue;
713 if (getpwnam(den->d_name))
714 SynchronizeFile(den->d_name);
715 else
Eric Andersen9b476612003-07-28 09:37:56 +0000716 crondlog("\007ignoring %s\n", den->d_name);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000717 }
718 closedir(dir);
719 } else {
Eric Andersen9b476612003-07-28 09:37:56 +0000720 crondlog("\311Unable to open current dir!\n");
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000721 }
722 }
723}
724
725
726/*
727 * DeleteFile() - delete user database
728 *
729 * Note: multiple entries for same user may exist if we were unable to
730 * completely delete a database due to running processes.
731 */
732
733static void
734DeleteFile(const char *userName)
735{
736 CronFile **pfile = &FileBase;
737 CronFile *file;
738
739 while ((file = *pfile) != NULL) {
740 if (strcmp(userName, file->cf_User) == 0) {
741 CronLine **pline = &file->cf_LineBase;
742 CronLine *line;
743
744 file->cf_Running = 0;
745 file->cf_Deleted = 1;
746
747 while ((line = *pline) != NULL) {
748 if (line->cl_Pid > 0) {
749 file->cf_Running = 1;
750 pline = &line->cl_Next;
751 } else {
752 *pline = line->cl_Next;
753 free(line->cl_Shell);
754 free(line);
755 }
756 }
757 if (file->cf_Running == 0) {
758 *pfile = file->cf_Next;
759 free(file->cf_User);
760 free(file);
761 } else {
762 pfile = &file->cf_Next;
763 }
764 } else {
765 pfile = &file->cf_Next;
766 }
767 }
768}
769
770/*
771 * TestJobs()
772 *
773 * determine which jobs need to be run. Under normal conditions, the
774 * period is about a minute (one scan). Worst case it will be one
775 * hour (60 scans).
776 */
777
778static int
779TestJobs(time_t t1, time_t t2)
780{
781 short nJobs = 0;
782 time_t t;
783
784 /*
785 * Find jobs > t1 and <= t2
786 */
787
788 for (t = t1 - t1 % 60; t <= t2; t += 60) {
789 if (t > t1) {
790 struct tm *tp = localtime(&t);
791 CronFile *file;
792 CronLine *line;
793
794 for (file = FileBase; file; file = file->cf_Next) {
795#ifdef FEATURE_DEBUG_OPT
796 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000797 crondlog("\005FILE %s:\n", file->cf_User);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000798#endif
799 if (file->cf_Deleted)
800 continue;
801 for (line = file->cf_LineBase; line; line = line->cl_Next) {
802#ifdef FEATURE_DEBUG_OPT
803 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000804 crondlog("\005 LINE %s\n", line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000805#endif
806 if (line->cl_Mins[tp->tm_min] &&
807 line->cl_Hrs[tp->tm_hour] &&
808 (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) &&
809 line->cl_Mons[tp->tm_mon]
810 ) {
811#ifdef FEATURE_DEBUG_OPT
812 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000813 crondlog("\005 JobToDo: %d %s\n", line->cl_Pid, line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000814#endif
815 if (line->cl_Pid > 0) {
Eric Andersen9b476612003-07-28 09:37:56 +0000816 crondlog("\010 process already running: %s %s\n",
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000817 file->cf_User,
818 line->cl_Shell
819 );
820 } else if (line->cl_Pid == 0) {
821 line->cl_Pid = -1;
822 file->cf_Ready = 1;
823 ++nJobs;
824 }
825 }
826 }
827 }
828 }
829 }
830 return(nJobs);
831}
832
833static void
834RunJobs(void)
835{
836 CronFile *file;
837 CronLine *line;
838
839 for (file = FileBase; file; file = file->cf_Next) {
840 if (file->cf_Ready) {
841 file->cf_Ready = 0;
842
843 for (line = file->cf_LineBase; line; line = line->cl_Next) {
844 if (line->cl_Pid < 0) {
845
Eric Andersen35e643b2003-07-28 07:40:39 +0000846 RunJob(file->cf_User, line);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000847
Eric Andersen9b476612003-07-28 09:37:56 +0000848 crondlog("\010USER %s pid %3d cmd %s\n",
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000849 file->cf_User,
850 line->cl_Pid,
851 line->cl_Shell
852 );
853 if (line->cl_Pid < 0)
854 file->cf_Ready = 1;
855 else if (line->cl_Pid > 0)
856 file->cf_Running = 1;
857 }
858 }
859 }
860 }
861}
862
863/*
864 * CheckJobs() - check for job completion
865 *
866 * Check for job completion, return number of jobs still running after
867 * all done.
868 */
869
870static int
871CheckJobs(void)
872{
873 CronFile *file;
874 CronLine *line;
875 int nStillRunning = 0;
876
877 for (file = FileBase; file; file = file->cf_Next) {
878 if (file->cf_Running) {
879 file->cf_Running = 0;
880
881 for (line = file->cf_LineBase; line; line = line->cl_Next) {
882 if (line->cl_Pid > 0) {
883 int status;
884 int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
885
886 if (r < 0 || r == line->cl_Pid) {
Eric Andersen35e643b2003-07-28 07:40:39 +0000887 EndJob(file->cf_User, line);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000888 if (line->cl_Pid)
889 file->cf_Running = 1;
890 } else if (r == 0) {
891 file->cf_Running = 1;
892 }
893 }
894 }
895 }
896 nStillRunning += file->cf_Running;
897 }
898 return(nStillRunning);
899}
900
901
Eric Andersen35e643b2003-07-28 07:40:39 +0000902#ifdef CONFIG_FEATURE_CROND_CALL_SENDMAIL
903static void
904ForkJob(const char *user, CronLine *line, int mailFd,
905 const char *prog, const char *cmd, const char *arg, const char *mailf)
906{
907 /*
908 * Fork as the user in question and run program
909 */
910 pid_t pid = fork();
911
912 line->cl_Pid = pid;
913 if (pid == 0) {
914 /*
915 * CHILD
916 */
917
918 /*
919 * Change running state to the user in question
920 */
921
922 if (ChangeUser(user) < 0)
923 exit(0);
924
925#ifdef FEATURE_DEBUG_OPT
926 if (DebugOpt)
Eric Andersen9b476612003-07-28 09:37:56 +0000927 crondlog("\005Child Running %s\n", prog);
Eric Andersen35e643b2003-07-28 07:40:39 +0000928#endif
929
930 if (mailFd >= 0) {
931 dup2(mailFd, mailf != NULL);
932 dup2((mailf ? mailFd : 1), 2);
933 close(mailFd);
934 }
935 execl(prog, prog, cmd, arg, NULL);
Eric Andersen9b476612003-07-28 09:37:56 +0000936 crondlog("\024unable to exec, user %s cmd %s %s %s\n", user,
Eric Andersen35e643b2003-07-28 07:40:39 +0000937 prog, cmd, arg);
938 if(mailf)
939 fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
940 exit(0);
941 } else if (pid < 0) {
942 /*
943 * FORK FAILED
944 */
Eric Andersen9b476612003-07-28 09:37:56 +0000945 crondlog("\024couldn't fork, user %s\n", user);
Eric Andersen35e643b2003-07-28 07:40:39 +0000946 line->cl_Pid = 0;
947 if(mailf)
948 remove(mailf);
949 } else if(mailf) {
950 /*
951 * PARENT, FORK SUCCESS
952 *
953 * rename mail-file based on pid of process
954 */
955 char mailFile2[128];
956
957 snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d",
958 user, pid);
959 rename(mailf, mailFile2);
960 }
961 /*
962 * Close the mail file descriptor.. we can't just leave it open in
963 * a structure, closing it later, because we might run out of descriptors
964 */
965
966 if (mailFd >= 0)
967 close(mailFd);
968}
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000969
970static void
Eric Andersen35e643b2003-07-28 07:40:39 +0000971RunJob(const char *user, CronLine *line)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000972{
973 char mailFile[128];
974 int mailFd;
975
976 line->cl_Pid = 0;
977 line->cl_MailFlag = 0;
978
979 /*
980 * open mail file - owner root so nobody can screw with it.
981 */
982
983 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
Eric Andersen35e643b2003-07-28 07:40:39 +0000984 user, getpid());
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000985 mailFd = open(mailFile, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_APPEND, 0600);
986
987 if (mailFd >= 0) {
988 line->cl_MailFlag = 1;
Eric Andersen35e643b2003-07-28 07:40:39 +0000989 fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,
990 line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000991 line->cl_MailPos = lseek(mailFd, 0, 1);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000992 } else {
Eric Andersen9b476612003-07-28 09:37:56 +0000993 crondlog("\024unable to create mail file user %s file %s, output to /dev/null\n",
Eric Andersen35e643b2003-07-28 07:40:39 +0000994 user, mailFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000995 }
996
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +0000997 ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +0000998}
999
1000/*
1001 * EndJob - called when job terminates and when mail terminates
1002 */
1003
1004static void
Eric Andersen35e643b2003-07-28 07:40:39 +00001005EndJob(const char *user, CronLine *line)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001006{
1007 int mailFd;
1008 char mailFile[128];
1009 struct stat sbuf;
1010
1011 /*
1012 * No job
1013 */
1014
1015 if (line->cl_Pid <= 0) {
1016 line->cl_Pid = 0;
1017 return;
1018 }
1019
1020 /*
1021 * End of job and no mail file
1022 * End of sendmail job
1023 */
1024
1025 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
Eric Andersen35e643b2003-07-28 07:40:39 +00001026 user, line->cl_Pid);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001027 line->cl_Pid = 0;
1028
1029 if (line->cl_MailFlag != 1)
1030 return;
1031
1032 line->cl_MailFlag = 0;
1033
1034 /*
1035 * End of primary job - check for mail file. If size has increased and
1036 * the file is still valid, we sendmail it.
1037 */
1038
1039 mailFd = open(mailFile, O_RDONLY);
1040 remove(mailFile);
1041 if (mailFd < 0) {
1042 return;
1043 }
1044
1045 if (fstat(mailFd, &sbuf) < 0 ||
1046 sbuf.st_uid != DaemonUid ||
1047 sbuf.st_nlink != 0 ||
1048 sbuf.st_size == line->cl_MailPos ||
1049 !S_ISREG(sbuf.st_mode)
1050 ) {
1051 close(mailFd);
1052 return;
1053 }
Eric Andersen35e643b2003-07-28 07:40:39 +00001054 ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
1055}
1056#else
1057/* crond whithout sendmail */
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001058
Eric Andersen35e643b2003-07-28 07:40:39 +00001059static void
1060RunJob(const char *user, CronLine *line)
1061{
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001062 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001063 * Fork as the user in question and run program
1064 */
1065 pid_t pid = fork();
1066
1067 if (pid == 0) {
1068 /*
1069 * CHILD
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001070 */
1071
1072 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001073 * Change running state to the user in question
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001074 */
1075
Eric Andersen35e643b2003-07-28 07:40:39 +00001076 if (ChangeUser(user) < 0)
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001077 exit(0);
1078
Eric Andersen35e643b2003-07-28 07:40:39 +00001079#ifdef FEATURE_DEBUG_OPT
1080 if (DebugOpt)
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +00001081 crondlog("\005Child Running %s\n", DEFAULT_SHELL);
Eric Andersen35e643b2003-07-28 07:40:39 +00001082#endif
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001083
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +00001084 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
Eric Andersen9b476612003-07-28 09:37:56 +00001085 crondlog("\024unable to exec, user %s cmd %s -c %s\n", user,
Glenn L McGrathdc4e75e2003-09-02 02:36:18 +00001086 DEFAULT_SHELL, line->cl_Shell);
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001087 exit(0);
Eric Andersen35e643b2003-07-28 07:40:39 +00001088 } else if (pid < 0) {
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001089 /*
Eric Andersen35e643b2003-07-28 07:40:39 +00001090 * FORK FAILED
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001091 */
Eric Andersen9b476612003-07-28 09:37:56 +00001092 crondlog("\024couldn't fork, user %s\n", user);
Eric Andersen35e643b2003-07-28 07:40:39 +00001093 pid = 0;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001094 }
Eric Andersen35e643b2003-07-28 07:40:39 +00001095 line->cl_Pid = pid;
Eric Andersenf6f7bfb2002-10-22 12:24:59 +00001096}
Eric Andersen35e643b2003-07-28 07:40:39 +00001097#endif /* CONFIG_FEATURE_CROND_CALL_SENDMAIL */