blob: 8ae54a536b7bba7256b141b0cb2fef0016d04b67 [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
44#ifndef LOG_FILE
45#define LOG_FILE "/var/log/cron"
46#endif
47#ifndef SENDMAIL
48#define SENDMAIL "/usr/sbin/sendmail"
49#endif
50#ifndef SENDMAIL_ARGS
51#define SENDMAIL_ARGS "-t", "-oem", "-i"
52#endif
53#ifndef CRONUPDATE
54#define CRONUPDATE "cron.update"
55#endif
56#ifndef MAXLINES
57#define MAXLINES 256 /* max lines in non-root crontabs */
58#endif
59
60
61
62typedef struct CronFile {
63 struct CronFile *cf_Next;
64 struct CronLine *cf_LineBase;
65 char *cf_User; /* username */
66 int cf_Ready; /* bool: one or more jobs ready */
67 int cf_Running; /* bool: one or more jobs running */
68 int cf_Deleted; /* marked for deletion, ignore */
69} CronFile;
70
71typedef struct CronLine {
72 struct CronLine *cl_Next;
73 char *cl_Shell; /* shell command */
74 int cl_Pid; /* running pid, 0, or armed (-1) */
75 int cl_MailFlag; /* running pid is for mail */
76 int cl_MailPos; /* 'empty file' size */
77 char cl_Mins[60]; /* 0-59 */
78 char cl_Hrs[24]; /* 0-23 */
79 char cl_Days[32]; /* 1-31 */
80 char cl_Mons[12]; /* 0-11 */
81 char cl_Dow[7]; /* 0-6, beginning sunday */
82} CronLine;
83
84#define RUN_RANOUT 1
85#define RUN_RUNNING 2
86#define RUN_FAILED 3
87
88#define DaemonUid 0
89
90#ifdef FEATURE_DEBUG_OPT
91static short DebugOpt;
92#endif
93
94static short LogLevel = 8;
95static short ForegroundOpt;
96static short LoggerOpt;
97static const char *LogFile = LOG_FILE;
98static const char *CDir = CRONTABS;
99
100static void log(int level, const char *ctl, ...);
101static void log9(const char *ctl, ...);
102static void startlogger(void);
103
104static void CheckUpdates(void);
105static void SynchronizeDir(void);
106static int TestJobs(time_t t1, time_t t2);
107static void RunJobs(void);
108static int CheckJobs(void);
109static void RunJob(CronFile *file, CronLine *line);
110static void EndJob(const CronFile *file, CronLine *line);
111
112static void DeleteFile(const char *userName);
113
114static CronFile *FileBase;
115
116
117int
118crond_main(int ac, char **av)
119{
120 int i;
121
122 opterr = 0; /* disable getopt 'errors' message.*/
123
124 while ((i = getopt(ac,av,
125#ifdef FEATURE_DEBUG_OPT
126 "d:"
127#endif
128 "l:L:fbSc:")) != EOF){
129
130 switch (i){
131 case 'l':
132 LogLevel = atoi(optarg);
133 break;
134#ifdef FEATURE_DEBUG_OPT
135 case 'd':
136 DebugOpt = atoi(optarg);
137 LogLevel = 0;
138 break;
139#endif
140 case 'f':
141 ForegroundOpt = 1;
142 break;
143 case 'b':
144 ForegroundOpt = 0;
145 break;
146 case 'S': /* select logging to syslog */
147 LoggerOpt = 0;
148 break;
149 case 'L': /* select internal file logger */
150 LoggerOpt = 1;
151 if (*optarg != 0) LogFile = optarg;
152 break;
153 case 'c':
154 if (*optarg != 0) CDir = optarg;
155 break;
156 default: /* parse error */
157 show_usage();
158 }
159 }
160
161 /*
162 * change directory
163 */
164
165 if (chdir(CDir) != 0)
166 perror_msg_and_die("chdir");
167
168 /*
169 * close stdin and stdout, stderr.
170 * close unused descriptors - don't need.
171 * optional detach from controlling terminal
172 */
173
174 if (ForegroundOpt == 0) {
175 if(daemon(1, 0) < 0)
176 perror_msg_and_die("daemon");
177 }
178
179 (void)startlogger(); /* need if syslog mode selected */
180 signal(SIGHUP,SIG_IGN); /* hmm.. but, if kill -HUP original
181 * version - his died. ;(
182 */
183
184 /*
185 * main loop - synchronize to 1 second after the minute, minimum sleep
186 * of 1 second.
187 */
188
189 log(9,"%s " VERSION " dillon, started, log level %d\n", av[0], LogLevel);
190
191 SynchronizeDir();
192
193 {
194 time_t t1 = time(NULL);
195 time_t t2;
196 long dt;
197 short rescan = 60;
198 short sleep_time = 60;
199
200 for (;;) {
201 sleep((sleep_time + 1) - (short)(time(NULL) % sleep_time));
202
203 t2 = time(NULL);
204 dt = t2 - t1;
205
206 /*
207 * The file 'cron.update' is checked to determine new cron
208 * jobs. The directory is rescanned once an hour to deal
209 * with any screwups.
210 *
211 * check for disparity. Disparities over an hour either way
212 * result in resynchronization. A reverse-indexed disparity
213 * less then an hour causes us to effectively sleep until we
214 * match the original time (i.e. no re-execution of jobs that
215 * have just been run). A forward-indexed disparity less then
216 * an hour causes intermediate jobs to be run, but only once
217 * in the worst case.
218 *
219 * when running jobs, the inequality used is greater but not
220 * equal to t1, and less then or equal to t2.
221 */
222
223 if (--rescan == 0) {
224 rescan = 60;
225 SynchronizeDir();
226 }
227 CheckUpdates();
228#ifdef FEATURE_DEBUG_OPT
229 if (DebugOpt)
230 log(5, "Wakeup dt=%d\n", dt);
231#endif
232 if (dt < -60*60 || dt > 60*60) {
233 t1 = t2;
234 log9("time disparity of %d minutes detected\n", dt / 60);
235 } else if (dt > 0) {
236 TestJobs(t1, t2);
237 RunJobs();
238 sleep(5);
239 if (CheckJobs() > 0)
240 sleep_time = 10;
241 else
242 sleep_time = 60;
243 t1 = t2;
244 }
245 }
246 }
247 /* not reached */
248}
249
250
251static void
252vlog(int level, int MLOG_LEVEL, const char *ctl, va_list va)
253{
254 char buf[1024];
255 int logfd;
256
257 if (level >= LogLevel) {
258
259 vsnprintf(buf,sizeof(buf), ctl, va);
260#ifdef FEATURE_DEBUG_OPT
261 if (DebugOpt) fprintf(stderr,"%s",buf);
262 else
263#endif
264 if (LoggerOpt == 0) syslog(MLOG_LEVEL, "%s", buf);
265 else {
266 if ((logfd = open(LogFile,O_WRONLY|O_CREAT|O_APPEND,600)) >= 0){
267 write(logfd, buf, strlen(buf));
268 close(logfd);
269 } else
270#ifdef FEATURE_DEBUG_OPT
271 perror_msg("Can't open log file")
272#endif
273 ;
274 }
275 }
276}
277
278/*
279 set log_level=9 and log messages
280*/
281
282static void
283log9(const char *ctl, ...)
284{
285 va_list va;
286
287 va_start(va, ctl);
288 vlog(9, LOG_WARNING, ctl, va);
289 va_end(va);
290}
291
292/*
293 normal logger call point.
294*/
295
296static void
297log(int level, const char *ctl, ...)
298{
299 va_list va;
300
301 va_start(va, ctl);
302 vlog(level, LOG_NOTICE, ctl, va);
303 va_end(va);
304}
305
306/*
307 Original: void
308 logfd(int fd, const char *ctl, ...)
309 Updated to: log_error (used by jobs.c)
310*/
311
312static void
313log_err(const char *ctl, ...)
314{
315 va_list va;
316
317 va_start(va, ctl);
318 vlog(20, LOG_ERR, ctl, va);
319 va_end(va);
320}
321
322/*
323 used by jobs.c (write to temp file..)
324*/
325
326static void
327fdprintf(int fd, const char *ctl, ...)
328{
329 va_list va;
330
331 va_start(va, ctl);
332 vdprintf(fd, ctl, va);
333 va_end(va);
334}
335
336
337static int
338ChangeUser(const char *user, short dochdir)
339{
340 struct passwd *pas;
341
342 /*
343 * Obtain password entry and change privilages
344 */
345
346 if ((pas = getpwnam(user)) == 0) {
347 log(9, "failed to get uid for %s", user);
348 return(-1);
349 }
350 setenv("USER", pas->pw_name, 1);
351 setenv("HOME", pas->pw_dir, 1);
352 setenv("SHELL", "/bin/sh", 1);
353
354 /*
355 * Change running state to the user in question
356 */
357
358 if (initgroups(user, pas->pw_gid) < 0) {
359 log(9, "initgroups failed: %s %m", user);
360 return(-1);
361 }
362 if (setregid(pas->pw_gid, pas->pw_gid) < 0) {
363 log(9, "setregid failed: %s %d", user, pas->pw_gid);
364 return(-1);
365 }
366 if (setreuid(pas->pw_uid, pas->pw_uid) < 0) {
367 log(9, "setreuid failed: %s %d", user, pas->pw_uid);
368 return(-1);
369 }
370 if (dochdir) {
371 if (chdir(pas->pw_dir) < 0) {
372 log(8, "chdir failed: %s %s", user, pas->pw_dir);
373 if (chdir(TMPDIR) < 0) {
374 log(9, "chdir failed: %s %s", TMPDIR, user);
375 return(-1);
376 }
377 }
378 }
379 return(pas->pw_uid);
380}
381
382static void
383startlogger(void)
384{
385 int logfd;
386
387 if (LoggerOpt == 0)
388 openlog(applet_name, LOG_CONS|LOG_PID,LOG_CRON);
389
390 else { /* test logfile */
391 if ((logfd = open(LogFile,O_WRONLY|O_CREAT|O_APPEND,600)) >= 0)
392 close(logfd);
393 else
394#ifdef FEATURE_DEBUG_OPT
395 printf("Failed to open log file '%s' reason: %m", LogFile)
396#endif
397 ;
398 }
399}
400
401
402static const char * const DowAry[] = {
403 "sun",
404 "mon",
405 "tue",
406 "wed",
407 "thu",
408 "fri",
409 "sat",
410
411 "Sun",
412 "Mon",
413 "Tue",
414 "Wed",
415 "Thu",
416 "Fri",
417 "Sat",
418 NULL
419};
420
421static const char * const MonAry[] = {
422 "jan",
423 "feb",
424 "mar",
425 "apr",
426 "may",
427 "jun",
428 "jul",
429 "aug",
430 "sep",
431 "oct",
432 "nov",
433 "dec",
434
435 "Jan",
436 "Feb",
437 "Mar",
438 "Apr",
439 "May",
440 "Jun",
441 "Jul",
442 "Aug",
443 "Sep",
444 "Oct",
445 "Nov",
446 "Dec",
447 NULL
448};
449
450static char *
451ParseField(char *user, char *ary, int modvalue, int off,
452 const char * const *names, char *ptr)
453{
454 char *base = ptr;
455 int n1 = -1;
456 int n2 = -1;
457
458 if (base == NULL)
459 return(NULL);
460
461 while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
462 int skip = 0;
463
464 /*
465 * Handle numeric digit or symbol or '*'
466 */
467
468 if (*ptr == '*') {
469 n1 = 0; /* everything will be filled */
470 n2 = modvalue - 1;
471 skip = 1;
472 ++ptr;
473 } else if (*ptr >= '0' && *ptr <= '9') {
474 if (n1 < 0)
475 n1 = strtol(ptr, &ptr, 10) + off;
476 else
477 n2 = strtol(ptr, &ptr, 10) + off;
478 skip = 1;
479 } else if (names) {
480 int i;
481
482 for (i = 0; names[i]; ++i) {
483 if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
484 break;
485 }
486 }
487 if (names[i]) {
488 ptr += strlen(names[i]);
489 if (n1 < 0)
490 n1 = i;
491 else
492 n2 = i;
493 skip = 1;
494 }
495 }
496
497 /*
498 * handle optional range '-'
499 */
500
501 if (skip == 0) {
502 log9("failed user %s parsing %s\n", user, base);
503 return(NULL);
504 }
505 if (*ptr == '-' && n2 < 0) {
506 ++ptr;
507 continue;
508 }
509
510 /*
511 * collapse single-value ranges, handle skipmark, and fill
512 * in the character array appropriately.
513 */
514
515 if (n2 < 0)
516 n2 = n1;
517
518 if (*ptr == '/')
519 skip = strtol(ptr + 1, &ptr, 10);
520
521 /*
522 * fill array, using a failsafe is the easiest way to prevent
523 * an endless loop
524 */
525
526 {
527 int s0 = 1;
528 int failsafe = 1024;
529
530 --n1;
531 do {
532 n1 = (n1 + 1) % modvalue;
533
534 if (--s0 == 0) {
535 ary[n1 % modvalue] = 1;
536 s0 = skip;
537 }
538 } while (n1 != n2 && --failsafe);
539
540 if (failsafe == 0) {
541 log9("failed user %s parsing %s\n", user, base);
542 return(NULL);
543 }
544 }
545 if (*ptr != ',')
546 break;
547 ++ptr;
548 n1 = -1;
549 n2 = -1;
550 }
551
552 if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
553 log9("failed user %s parsing %s\n", user, base);
554 return(NULL);
555 }
556
557 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
558 ++ptr;
559
560#ifdef FEATURE_DEBUG_OPT
561 if (DebugOpt) {
562 int i;
563
564 for (i = 0; i < modvalue; ++i)
565 log(5, "%d", ary[i]);
566 log(5, "\n");
567 }
568#endif
569
570 return(ptr);
571}
572
573static void
574FixDayDow(CronLine *line)
575{
576 short i;
577 short weekUsed = 0;
578 short daysUsed = 0;
579
580 for (i = 0; i < arysize(line->cl_Dow); ++i) {
581 if (line->cl_Dow[i] == 0) {
582 weekUsed = 1;
583 break;
584 }
585 }
586 for (i = 0; i < arysize(line->cl_Days); ++i) {
587 if (line->cl_Days[i] == 0) {
588 daysUsed = 1;
589 break;
590 }
591 }
592 if (weekUsed && !daysUsed) {
593 memset(line->cl_Days, 0, sizeof(line->cl_Days));
594 }
595 if (daysUsed && !weekUsed) {
596 memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
597 }
598}
599
600
601
602static void
603SynchronizeFile(const char *fileName)
604{
605 int maxEntries = MAXLINES;
606 int maxLines;
607 char buf[1024];
608
609 if (strcmp(fileName, "root") == 0)
610 maxEntries = 65535;
611 maxLines = maxEntries * 10;
612
613 if (fileName) {
614 FILE *fi;
615
616 DeleteFile(fileName);
617
618 if ((fi = fopen(fileName, "r")) != NULL) {
619 struct stat sbuf;
620
621 if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
622 CronFile *file = calloc(1, sizeof(CronFile));
623 CronLine **pline;
624
625 file->cf_User = strdup(fileName);
626 pline = &file->cf_LineBase;
627
628 while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
629 CronLine line;
630 char *ptr;
631
632 if (buf[0])
633 buf[strlen(buf)-1] = 0;
634
635 if (buf[0] == 0 || buf[0] == '#' || buf[0] == ' ' || buf[0] == '\t')
636 continue;
637
638 if (--maxEntries == 0)
639 break;
640
641 memset(&line, 0, sizeof(line));
642
643#ifdef FEATURE_DEBUG_OPT
644 if (DebugOpt)
645 log9("User %s Entry %s\n", fileName, buf);
646#endif
647
648 /*
649 * parse date ranges
650 */
651
652 ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
653 ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
654 ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
655 ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
656 ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
657
658 /*
659 * check failure
660 */
661
662 if (ptr == NULL)
663 continue;
664
665 /*
666 * fix days and dow - if one is not * and the other
667 * is *, the other is set to 0, and vise-versa
668 */
669
670 FixDayDow(&line);
671
672 *pline = calloc(1, sizeof(CronLine));
673 **pline = line;
674
675 /*
676 * copy command
677 */
678
679 (*pline)->cl_Shell = strdup(ptr);
680
681#ifdef FEATURE_DEBUG_OPT
682 if (DebugOpt) {
683 log9(" Command %s\n", ptr);
684 }
685#endif
686
687 pline = &((*pline)->cl_Next);
688 }
689 *pline = NULL;
690
691 file->cf_Next = FileBase;
692 FileBase = file;
693
694 if (maxLines == 0 || maxEntries == 0)
695 log9("Maximum number of lines reached for user %s\n", fileName);
696 }
697 fclose(fi);
698 }
699 }
700}
701
702static void
703CheckUpdates(void)
704{
705 FILE *fi;
706 char buf[256];
707
708 if ((fi = fopen(CRONUPDATE, "r")) != NULL) {
709 remove(CRONUPDATE);
710 while (fgets(buf, sizeof(buf), fi) != NULL) {
711 SynchronizeFile(strtok(buf, " \t\r\n"));
712 }
713 fclose(fi);
714 }
715}
716
717static void
718SynchronizeDir(void)
719{
720 /*
721 * Attempt to delete the database. Note that we have to make a copy
722 * of the string
723 */
724
725 for (;;) {
726 CronFile *file;
727 char *user;
728
729 for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next)
730 ;
731 if (file == NULL)
732 break;
733 user = strdup(file->cf_User);
734 DeleteFile(user);
735 free(user);
736 }
737
738 /*
739 * Remove cron update file
740 *
741 * Re-chdir, in case directory was renamed & deleted, or otherwise
742 * screwed up.
743 *
744 * scan directory and add associated users
745 */
746
747 remove(CRONUPDATE);
748 if (chdir(CDir) < 0) {
749 log9("unable to find %s\n", CDir);
750 exit(20);
751 }
752 {
753 DIR *dir;
754 struct dirent *den;
755
756 if ((dir = opendir("."))) {
757 while ((den = readdir(dir))) {
758 if (strchr(den->d_name, '.') != NULL)
759 continue;
760 if (getpwnam(den->d_name))
761 SynchronizeFile(den->d_name);
762 else
763 log(7, "ignoring %s\n", den->d_name);
764 }
765 closedir(dir);
766 } else {
767 log9("Unable to open current dir!\n");
768 exit(20);
769 }
770 }
771}
772
773
774/*
775 * DeleteFile() - delete user database
776 *
777 * Note: multiple entries for same user may exist if we were unable to
778 * completely delete a database due to running processes.
779 */
780
781static void
782DeleteFile(const char *userName)
783{
784 CronFile **pfile = &FileBase;
785 CronFile *file;
786
787 while ((file = *pfile) != NULL) {
788 if (strcmp(userName, file->cf_User) == 0) {
789 CronLine **pline = &file->cf_LineBase;
790 CronLine *line;
791
792 file->cf_Running = 0;
793 file->cf_Deleted = 1;
794
795 while ((line = *pline) != NULL) {
796 if (line->cl_Pid > 0) {
797 file->cf_Running = 1;
798 pline = &line->cl_Next;
799 } else {
800 *pline = line->cl_Next;
801 free(line->cl_Shell);
802 free(line);
803 }
804 }
805 if (file->cf_Running == 0) {
806 *pfile = file->cf_Next;
807 free(file->cf_User);
808 free(file);
809 } else {
810 pfile = &file->cf_Next;
811 }
812 } else {
813 pfile = &file->cf_Next;
814 }
815 }
816}
817
818/*
819 * TestJobs()
820 *
821 * determine which jobs need to be run. Under normal conditions, the
822 * period is about a minute (one scan). Worst case it will be one
823 * hour (60 scans).
824 */
825
826static int
827TestJobs(time_t t1, time_t t2)
828{
829 short nJobs = 0;
830 time_t t;
831
832 /*
833 * Find jobs > t1 and <= t2
834 */
835
836 for (t = t1 - t1 % 60; t <= t2; t += 60) {
837 if (t > t1) {
838 struct tm *tp = localtime(&t);
839 CronFile *file;
840 CronLine *line;
841
842 for (file = FileBase; file; file = file->cf_Next) {
843#ifdef FEATURE_DEBUG_OPT
844 if (DebugOpt)
845 log(5, "FILE %s:\n", file->cf_User);
846#endif
847 if (file->cf_Deleted)
848 continue;
849 for (line = file->cf_LineBase; line; line = line->cl_Next) {
850#ifdef FEATURE_DEBUG_OPT
851 if (DebugOpt)
852 log(5, " LINE %s\n", line->cl_Shell);
853#endif
854 if (line->cl_Mins[tp->tm_min] &&
855 line->cl_Hrs[tp->tm_hour] &&
856 (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) &&
857 line->cl_Mons[tp->tm_mon]
858 ) {
859#ifdef FEATURE_DEBUG_OPT
860 if (DebugOpt)
861 log(5, " JobToDo: %d %s\n", line->cl_Pid, line->cl_Shell);
862#endif
863 if (line->cl_Pid > 0) {
864 log(8, " process already running: %s %s\n",
865 file->cf_User,
866 line->cl_Shell
867 );
868 } else if (line->cl_Pid == 0) {
869 line->cl_Pid = -1;
870 file->cf_Ready = 1;
871 ++nJobs;
872 }
873 }
874 }
875 }
876 }
877 }
878 return(nJobs);
879}
880
881static void
882RunJobs(void)
883{
884 CronFile *file;
885 CronLine *line;
886
887 for (file = FileBase; file; file = file->cf_Next) {
888 if (file->cf_Ready) {
889 file->cf_Ready = 0;
890
891 for (line = file->cf_LineBase; line; line = line->cl_Next) {
892 if (line->cl_Pid < 0) {
893
894 RunJob(file, line);
895
896 log(8, "USER %s pid %3d cmd %s\n",
897 file->cf_User,
898 line->cl_Pid,
899 line->cl_Shell
900 );
901 if (line->cl_Pid < 0)
902 file->cf_Ready = 1;
903 else if (line->cl_Pid > 0)
904 file->cf_Running = 1;
905 }
906 }
907 }
908 }
909}
910
911/*
912 * CheckJobs() - check for job completion
913 *
914 * Check for job completion, return number of jobs still running after
915 * all done.
916 */
917
918static int
919CheckJobs(void)
920{
921 CronFile *file;
922 CronLine *line;
923 int nStillRunning = 0;
924
925 for (file = FileBase; file; file = file->cf_Next) {
926 if (file->cf_Running) {
927 file->cf_Running = 0;
928
929 for (line = file->cf_LineBase; line; line = line->cl_Next) {
930 if (line->cl_Pid > 0) {
931 int status;
932 int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
933
934 if (r < 0 || r == line->cl_Pid) {
935 EndJob(file, line);
936 if (line->cl_Pid)
937 file->cf_Running = 1;
938 } else if (r == 0) {
939 file->cf_Running = 1;
940 }
941 }
942 }
943 }
944 nStillRunning += file->cf_Running;
945 }
946 return(nStillRunning);
947}
948
949
950
951static void
952RunJob(CronFile *file, CronLine *line)
953{
954 char mailFile[128];
955 int mailFd;
956
957 line->cl_Pid = 0;
958 line->cl_MailFlag = 0;
959
960 /*
961 * open mail file - owner root so nobody can screw with it.
962 */
963
964 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
965 file->cf_User, getpid());
966 mailFd = open(mailFile, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_APPEND, 0600);
967
968 if (mailFd >= 0) {
969 line->cl_MailFlag = 1;
970 fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n",
971 file->cf_User,
972 line->cl_Shell
973 );
974 line->cl_MailPos = lseek(mailFd, 0, 1);
975 }
976
977 /*
978 * Fork as the user in question and run program
979 */
980
981 if ((line->cl_Pid = fork()) == 0) {
982 /*
983 * CHILD, FORK OK
984 */
985
986 /*
987 * Change running state to the user in question
988 */
989
990 if (ChangeUser(file->cf_User, 1) < 0)
991 return;
992
993#ifdef FEATURE_DEBUG_OPT
994 if (DebugOpt)
995 log(5, "Child Running %s\n", line->cl_Shell);
996#endif
997
998 /*
999 * stdin is already /dev/null, setup stdout and stderr
1000 */
1001
1002 if (mailFd >= 0) {
1003 dup2(mailFd, 1);
1004 dup2(mailFd, 2);
1005 close(mailFd);
1006 } else {
1007 log_err("unable to create mail file user %s file %s, output to /dev/null\n",
1008 file->cf_User,
1009 mailFile
1010 );
1011 }
1012 execl("/bin/sh", "/bin/sh", "-c", line->cl_Shell, NULL, NULL);
1013 log_err("unable to exec, user %s cmd /bin/sh -c %s\n",
1014 file->cf_User,
1015 line->cl_Shell
1016 );
1017 fdprintf(1, "Exec failed: /bin/sh -c %s\n", line->cl_Shell);
1018 exit(0);
1019 } else if (line->cl_Pid < 0) {
1020 /*
1021 * PARENT, FORK FAILED
1022 */
1023 log_err("couldn't fork, user %s\n", file->cf_User);
1024 line->cl_Pid = 0;
1025 remove(mailFile);
1026 } else {
1027 /*
1028 * PARENT, FORK SUCCESS
1029 *
1030 * rename mail-file based on pid of process
1031 */
1032 char mailFile2[128];
1033
1034 snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d",
1035 file->cf_User, line->cl_Pid);
1036 rename(mailFile, mailFile2);
1037 }
1038
1039 /*
1040 * Close the mail file descriptor.. we can't just leave it open in
1041 * a structure, closing it later, because we might run out of descriptors
1042 */
1043
1044 if (mailFd >= 0)
1045 close(mailFd);
1046}
1047
1048/*
1049 * EndJob - called when job terminates and when mail terminates
1050 */
1051
1052static void
1053EndJob(const CronFile *file, CronLine *line)
1054{
1055 int mailFd;
1056 char mailFile[128];
1057 struct stat sbuf;
1058
1059 /*
1060 * No job
1061 */
1062
1063 if (line->cl_Pid <= 0) {
1064 line->cl_Pid = 0;
1065 return;
1066 }
1067
1068 /*
1069 * End of job and no mail file
1070 * End of sendmail job
1071 */
1072
1073 snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
1074 file->cf_User, line->cl_Pid);
1075 line->cl_Pid = 0;
1076
1077 if (line->cl_MailFlag != 1)
1078 return;
1079
1080 line->cl_MailFlag = 0;
1081
1082 /*
1083 * End of primary job - check for mail file. If size has increased and
1084 * the file is still valid, we sendmail it.
1085 */
1086
1087 mailFd = open(mailFile, O_RDONLY);
1088 remove(mailFile);
1089 if (mailFd < 0) {
1090 return;
1091 }
1092
1093 if (fstat(mailFd, &sbuf) < 0 ||
1094 sbuf.st_uid != DaemonUid ||
1095 sbuf.st_nlink != 0 ||
1096 sbuf.st_size == line->cl_MailPos ||
1097 !S_ISREG(sbuf.st_mode)
1098 ) {
1099 close(mailFd);
1100 return;
1101 }
1102
1103 if ((line->cl_Pid = fork()) == 0) {
1104 /*
1105 * CHILD, FORK OK
1106 */
1107
1108 /*
1109 * change user id - no way in hell security can be compromised
1110 * by the mailing and we already verified the mail file.
1111 */
1112
1113 if (ChangeUser(file->cf_User, 1) < 0)
1114 exit(0);
1115
1116 /*
1117 * run sendmail with mail file as standard input, only if
1118 * mail file exists!
1119 */
1120
1121 dup2(mailFd, 0);
1122 dup2(1, 2);
1123 close(mailFd);
1124
1125 execl(SENDMAIL, SENDMAIL, SENDMAIL_ARGS, NULL, NULL);
1126 log_err("unable to exec %s %s, user %s, output to sink null",
1127 SENDMAIL,
1128 SENDMAIL_ARGS,
1129 file->cf_User
1130 );
1131 exit(0);
1132 } else if (line->cl_Pid < 0) {
1133 /*
1134 * PARENT, FORK FAILED
1135 */
1136 log_err("unable to fork, user %s", file->cf_User);
1137 line->cl_Pid = 0;
1138 } else {
1139 /*
1140 * PARENT, FORK OK
1141 */
1142 }
1143 close(mailFd);
1144}