diff options
author | Eric Andersen | 2002-10-22 12:24:59 +0000 |
---|---|---|
committer | Eric Andersen | 2002-10-22 12:24:59 +0000 |
commit | f6f7bfb8e0a257137f8c2dad83ae4ed826b4e4bb (patch) | |
tree | 7ea00c66d341d324294df54238acff2c1795d72b /miscutils/crond.c | |
parent | 44608e9693b03661fbab5e27650bb040c6871d11 (diff) | |
download | busybox-f6f7bfb8e0a257137f8c2dad83ae4ed826b4e4bb.zip busybox-f6f7bfb8e0a257137f8c2dad83ae4ed826b4e4bb.tar.gz |
last_patch63 from vodz: add in crond and crontab applets
Diffstat (limited to 'miscutils/crond.c')
-rw-r--r-- | miscutils/crond.c | 1146 |
1 files changed, 1146 insertions, 0 deletions
diff --git a/miscutils/crond.c b/miscutils/crond.c new file mode 100644 index 0000000..225d502 --- /dev/null +++ b/miscutils/crond.c @@ -0,0 +1,1146 @@ +/* + * crond -d[#] -c <crondir> -f -b + * + * run as root, but NOT setuid root + * + * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) + * May be distributed under the GNU General Public License + * + * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 to be used in busybox + */ + +#define VERSION "2.3.2" + +#undef FEATURE_DEBUG_OPT + + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <dirent.h> +#include <fcntl.h> +#include <pwd.h> +#include <unistd.h> +#include <grp.h> +#include <syslog.h> +#include <signal.h> +#include <getopt.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/resource.h> + +#include "busybox.h" + +#define arysize(ary) (sizeof(ary)/sizeof((ary)[0])) + +#ifndef CRONTABS +#define CRONTABS "/var/spool/cron/crontabs" +#endif +#ifndef TMPDIR +#define TMPDIR "/var/spool/cron" +#endif +#ifndef LOG_FILE +#define LOG_FILE "/var/log/cron" +#endif +#ifndef SENDMAIL +#define SENDMAIL "/usr/sbin/sendmail" +#endif +#ifndef SENDMAIL_ARGS +#define SENDMAIL_ARGS "-t", "-oem", "-i" +#endif +#ifndef CRONUPDATE +#define CRONUPDATE "cron.update" +#endif +#ifndef MAXLINES +#define MAXLINES 256 /* max lines in non-root crontabs */ +#endif + + + +typedef struct CronFile { + struct CronFile *cf_Next; + struct CronLine *cf_LineBase; + char *cf_User; /* username */ + int cf_Ready; /* bool: one or more jobs ready */ + int cf_Running; /* bool: one or more jobs running */ + int cf_Deleted; /* marked for deletion, ignore */ +} CronFile; + +typedef struct CronLine { + struct CronLine *cl_Next; + char *cl_Shell; /* shell command */ + int cl_Pid; /* running pid, 0, or armed (-1) */ + int cl_MailFlag; /* running pid is for mail */ + int cl_MailPos; /* 'empty file' size */ + char cl_Mins[60]; /* 0-59 */ + char cl_Hrs[24]; /* 0-23 */ + char cl_Days[32]; /* 1-31 */ + char cl_Mons[12]; /* 0-11 */ + char cl_Dow[7]; /* 0-6, beginning sunday */ +} CronLine; + +#define RUN_RANOUT 1 +#define RUN_RUNNING 2 +#define RUN_FAILED 3 + +#define DaemonUid 0 + +#ifdef FEATURE_DEBUG_OPT +static short DebugOpt; +#endif + +static short LogLevel = 8; +static short ForegroundOpt; +static short LoggerOpt; +static const char *LogFile = LOG_FILE; +static const char *CDir = CRONTABS; + +static void log(int level, const char *ctl, ...); +static void log9(const char *ctl, ...); +static void startlogger(void); + +static void CheckUpdates(void); +static void SynchronizeDir(void); +static int TestJobs(time_t t1, time_t t2); +static void RunJobs(void); +static int CheckJobs(void); +static void RunJob(CronFile *file, CronLine *line); +static void EndJob(const CronFile *file, CronLine *line); + +static void DeleteFile(const char *userName); + +static CronFile *FileBase; + + +int +crond_main(int ac, char **av) +{ + int i; + + opterr = 0; /* disable getopt 'errors' message.*/ + + while ((i = getopt(ac,av, +#ifdef FEATURE_DEBUG_OPT + "d:" +#endif + "l:L:fbSc:")) != EOF){ + + switch (i){ + case 'l': + LogLevel = atoi(optarg); + break; +#ifdef FEATURE_DEBUG_OPT + case 'd': + DebugOpt = atoi(optarg); + LogLevel = 0; + break; +#endif + case 'f': + ForegroundOpt = 1; + break; + case 'b': + ForegroundOpt = 0; + break; + case 'S': /* select logging to syslog */ + LoggerOpt = 0; + break; + case 'L': /* select internal file logger */ + LoggerOpt = 1; + if (*optarg != 0) LogFile = optarg; + break; + case 'c': + if (*optarg != 0) CDir = optarg; + break; + default: /* parse error */ + show_usage(); + } + } + + /* + * change directory + */ + + if (chdir(CDir) != 0) + perror_msg_and_die("chdir"); + + /* + * close stdin and stdout, stderr. + * close unused descriptors - don't need. + * optional detach from controlling terminal + */ + + if (ForegroundOpt == 0) { + if(daemon(1, 0) < 0) + perror_msg_and_die("daemon"); + } + + (void)startlogger(); /* need if syslog mode selected */ + signal(SIGHUP,SIG_IGN); /* hmm.. but, if kill -HUP original + * version - his died. ;( + */ + + /* + * main loop - synchronize to 1 second after the minute, minimum sleep + * of 1 second. + */ + + log(9,"%s " VERSION " dillon, started, log level %d\n", av[0], LogLevel); + + SynchronizeDir(); + + { + time_t t1 = time(NULL); + time_t t2; + long dt; + short rescan = 60; + short sleep_time = 60; + + for (;;) { + sleep((sleep_time + 1) - (short)(time(NULL) % sleep_time)); + + t2 = time(NULL); + dt = t2 - t1; + + /* + * The file 'cron.update' is checked to determine new cron + * jobs. The directory is rescanned once an hour to deal + * with any screwups. + * + * check for disparity. Disparities over an hour either way + * result in resynchronization. A reverse-indexed disparity + * less then an hour causes us to effectively sleep until we + * match the original time (i.e. no re-execution of jobs that + * have just been run). A forward-indexed disparity less then + * an hour causes intermediate jobs to be run, but only once + * in the worst case. + * + * when running jobs, the inequality used is greater but not + * equal to t1, and less then or equal to t2. + */ + + if (--rescan == 0) { + rescan = 60; + SynchronizeDir(); + } + CheckUpdates(); +#ifdef FEATURE_DEBUG_OPT + if (DebugOpt) + log(5, "Wakeup dt=%d\n", dt); +#endif + if (dt < -60*60 || dt > 60*60) { + t1 = t2; + log9("time disparity of %d minutes detected\n", dt / 60); + } else if (dt > 0) { + TestJobs(t1, t2); + RunJobs(); + sleep(5); + if (CheckJobs() > 0) + sleep_time = 10; + else + sleep_time = 60; + t1 = t2; + } + } + } + /* not reached */ +} + + +static void +vlog(int level, int MLOG_LEVEL, const char *ctl, va_list va) +{ + char buf[1024]; + int logfd; + + if (level >= LogLevel) { + + vsnprintf(buf,sizeof(buf), ctl, va); +#ifdef FEATURE_DEBUG_OPT + if (DebugOpt) fprintf(stderr,"%s",buf); + else +#endif + if (LoggerOpt == 0) syslog(MLOG_LEVEL, "%s", buf); + else { + if ((logfd = open(LogFile,O_WRONLY|O_CREAT|O_APPEND,600)) >= 0){ + write(logfd, buf, strlen(buf)); + close(logfd); + } else +#ifdef FEATURE_DEBUG_OPT + perror_msg("Can't open log file") +#endif + ; + } + } +} + +/* + set log_level=9 and log messages +*/ + +static void +log9(const char *ctl, ...) +{ + va_list va; + + va_start(va, ctl); + vlog(9, LOG_WARNING, ctl, va); + va_end(va); +} + +/* + normal logger call point. +*/ + +static void +log(int level, const char *ctl, ...) +{ + va_list va; + + va_start(va, ctl); + vlog(level, LOG_NOTICE, ctl, va); + va_end(va); +} + +/* + Original: void + logfd(int fd, const char *ctl, ...) + Updated to: log_error (used by jobs.c) +*/ + +static void +log_err(const char *ctl, ...) +{ + va_list va; + + va_start(va, ctl); + vlog(20, LOG_ERR, ctl, va); + va_end(va); +} + +/* + used by jobs.c (write to temp file..) +*/ + +static void +fdprintf(int fd, const char *ctl, ...) +{ + va_list va; + + va_start(va, ctl); + vdprintf(fd, ctl, va); + va_end(va); +} + + +static int +ChangeUser(const char *user, short dochdir) +{ + struct passwd *pas; + + /* + * Obtain password entry and change privilages + */ + + if ((pas = getpwnam(user)) == 0) { + log(9, "failed to get uid for %s", user); + return(-1); + } + setenv("USER", pas->pw_name, 1); + setenv("HOME", pas->pw_dir, 1); + setenv("SHELL", "/bin/sh", 1); + + /* + * Change running state to the user in question + */ + + if (initgroups(user, pas->pw_gid) < 0) { + log(9, "initgroups failed: %s %m", user); + return(-1); + } + if (setregid(pas->pw_gid, pas->pw_gid) < 0) { + log(9, "setregid failed: %s %d", user, pas->pw_gid); + return(-1); + } + if (setreuid(pas->pw_uid, pas->pw_uid) < 0) { + log(9, "setreuid failed: %s %d", user, pas->pw_uid); + return(-1); + } + if (dochdir) { + if (chdir(pas->pw_dir) < 0) { + log(8, "chdir failed: %s %s", user, pas->pw_dir); + if (chdir(TMPDIR) < 0) { + log(9, "chdir failed: %s %s", TMPDIR, user); + return(-1); + } + } + } + return(pas->pw_uid); +} + +static void +startlogger(void) +{ + int logfd; + + if (LoggerOpt == 0) + openlog(applet_name, LOG_CONS|LOG_PID,LOG_CRON); + + else { /* test logfile */ + if ((logfd = open(LogFile,O_WRONLY|O_CREAT|O_APPEND,600)) >= 0) + close(logfd); + else +#ifdef FEATURE_DEBUG_OPT + printf("Failed to open log file '%s' reason: %m", LogFile) +#endif + ; + } +} + + +static const char * const DowAry[] = { + "sun", + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", + + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + NULL +}; + +static const char * const MonAry[] = { + "jan", + "feb", + "mar", + "apr", + "may", + "jun", + "jul", + "aug", + "sep", + "oct", + "nov", + "dec", + + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + NULL +}; + +static char * +ParseField(char *user, char *ary, int modvalue, int off, + const char * const *names, char *ptr) +{ + char *base = ptr; + int n1 = -1; + int n2 = -1; + + if (base == NULL) + return(NULL); + + while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { + int skip = 0; + + /* + * Handle numeric digit or symbol or '*' + */ + + if (*ptr == '*') { + n1 = 0; /* everything will be filled */ + n2 = modvalue - 1; + skip = 1; + ++ptr; + } else if (*ptr >= '0' && *ptr <= '9') { + if (n1 < 0) + n1 = strtol(ptr, &ptr, 10) + off; + else + n2 = strtol(ptr, &ptr, 10) + off; + skip = 1; + } else if (names) { + int i; + + for (i = 0; names[i]; ++i) { + if (strncmp(ptr, names[i], strlen(names[i])) == 0) { + break; + } + } + if (names[i]) { + ptr += strlen(names[i]); + if (n1 < 0) + n1 = i; + else + n2 = i; + skip = 1; + } + } + + /* + * handle optional range '-' + */ + + if (skip == 0) { + log9("failed user %s parsing %s\n", user, base); + return(NULL); + } + if (*ptr == '-' && n2 < 0) { + ++ptr; + continue; + } + + /* + * collapse single-value ranges, handle skipmark, and fill + * in the character array appropriately. + */ + + if (n2 < 0) + n2 = n1; + + if (*ptr == '/') + skip = strtol(ptr + 1, &ptr, 10); + + /* + * fill array, using a failsafe is the easiest way to prevent + * an endless loop + */ + + { + int s0 = 1; + int failsafe = 1024; + + --n1; + do { + n1 = (n1 + 1) % modvalue; + + if (--s0 == 0) { + ary[n1 % modvalue] = 1; + s0 = skip; + } + } while (n1 != n2 && --failsafe); + + if (failsafe == 0) { + log9("failed user %s parsing %s\n", user, base); + return(NULL); + } + } + if (*ptr != ',') + break; + ++ptr; + n1 = -1; + n2 = -1; + } + + if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { + log9("failed user %s parsing %s\n", user, base); + return(NULL); + } + + while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') + ++ptr; + +#ifdef FEATURE_DEBUG_OPT + if (DebugOpt) { + int i; + + for (i = 0; i < modvalue; ++i) + log(5, "%d", ary[i]); + log(5, "\n"); + } +#endif + + return(ptr); +} + +static void +FixDayDow(CronLine *line) +{ + short i; + short weekUsed = 0; + short daysUsed = 0; + + for (i = 0; i < arysize(line->cl_Dow); ++i) { + if (line->cl_Dow[i] == 0) { + weekUsed = 1; + break; + } + } + for (i = 0; i < arysize(line->cl_Days); ++i) { + if (line->cl_Days[i] == 0) { + daysUsed = 1; + break; + } + } + if (weekUsed && !daysUsed) { + memset(line->cl_Days, 0, sizeof(line->cl_Days)); + } + if (daysUsed && !weekUsed) { + memset(line->cl_Dow, 0, sizeof(line->cl_Dow)); + } +} + + + +static void +SynchronizeFile(const char *fileName) +{ + int maxEntries = MAXLINES; + int maxLines; + char buf[1024]; + + if (strcmp(fileName, "root") == 0) + maxEntries = 65535; + maxLines = maxEntries * 10; + + if (fileName) { + FILE *fi; + + DeleteFile(fileName); + + if ((fi = fopen(fileName, "r")) != NULL) { + struct stat sbuf; + + if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) { + CronFile *file = calloc(1, sizeof(CronFile)); + CronLine **pline; + + file->cf_User = strdup(fileName); + pline = &file->cf_LineBase; + + while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) { + CronLine line; + char *ptr; + + if (buf[0]) + buf[strlen(buf)-1] = 0; + + if (buf[0] == 0 || buf[0] == '#' || buf[0] == ' ' || buf[0] == '\t') + continue; + + if (--maxEntries == 0) + break; + + memset(&line, 0, sizeof(line)); + +#ifdef FEATURE_DEBUG_OPT + if (DebugOpt) + log9("User %s Entry %s\n", fileName, buf); +#endif + + /* + * parse date ranges + */ + + ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf); + ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr); + ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr); + ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr); + ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr); + + /* + * check failure + */ + + if (ptr == NULL) + continue; + + /* + * fix days and dow - if one is not * and the other + * is *, the other is set to 0, and vise-versa + */ + + FixDayDow(&line); + + *pline = calloc(1, sizeof(CronLine)); + **pline = line; + + /* + * copy command + */ + + (*pline)->cl_Shell = strdup(ptr); + +#ifdef FEATURE_DEBUG_OPT + if (DebugOpt) { + log9(" Command %s\n", ptr); + } +#endif + + pline = &((*pline)->cl_Next); + } + *pline = NULL; + + file->cf_Next = FileBase; + FileBase = file; + + if (maxLines == 0 || maxEntries == 0) + log9("Maximum number of lines reached for user %s\n", fileName); + } + fclose(fi); + } + } +} + +static void +CheckUpdates(void) +{ + FILE *fi; + char buf[256]; + + if ((fi = fopen(CRONUPDATE, "r")) != NULL) { + remove(CRONUPDATE); + while (fgets(buf, sizeof(buf), fi) != NULL) { + SynchronizeFile(strtok(buf, " \t\r\n")); + } + fclose(fi); + } +} + +static void +SynchronizeDir(void) +{ + /* + * Attempt to delete the database. Note that we have to make a copy + * of the string + */ + + for (;;) { + CronFile *file; + char *user; + + for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next) + ; + if (file == NULL) + break; + user = strdup(file->cf_User); + DeleteFile(user); + free(user); + } + + /* + * Remove cron update file + * + * Re-chdir, in case directory was renamed & deleted, or otherwise + * screwed up. + * + * scan directory and add associated users + */ + + remove(CRONUPDATE); + if (chdir(CDir) < 0) { + log9("unable to find %s\n", CDir); + exit(20); + } + { + DIR *dir; + struct dirent *den; + + if ((dir = opendir("."))) { + while ((den = readdir(dir))) { + if (strchr(den->d_name, '.') != NULL) + continue; + if (getpwnam(den->d_name)) + SynchronizeFile(den->d_name); + else + log(7, "ignoring %s\n", den->d_name); + } + closedir(dir); + } else { + log9("Unable to open current dir!\n"); + exit(20); + } + } +} + + +/* + * DeleteFile() - delete user database + * + * Note: multiple entries for same user may exist if we were unable to + * completely delete a database due to running processes. + */ + +static void +DeleteFile(const char *userName) +{ + CronFile **pfile = &FileBase; + CronFile *file; + + while ((file = *pfile) != NULL) { + if (strcmp(userName, file->cf_User) == 0) { + CronLine **pline = &file->cf_LineBase; + CronLine *line; + + file->cf_Running = 0; + file->cf_Deleted = 1; + + while ((line = *pline) != NULL) { + if (line->cl_Pid > 0) { + file->cf_Running = 1; + pline = &line->cl_Next; + } else { + *pline = line->cl_Next; + free(line->cl_Shell); + free(line); + } + } + if (file->cf_Running == 0) { + *pfile = file->cf_Next; + free(file->cf_User); + free(file); + } else { + pfile = &file->cf_Next; + } + } else { + pfile = &file->cf_Next; + } + } +} + +/* + * TestJobs() + * + * determine which jobs need to be run. Under normal conditions, the + * period is about a minute (one scan). Worst case it will be one + * hour (60 scans). + */ + +static int +TestJobs(time_t t1, time_t t2) +{ + short nJobs = 0; + time_t t; + + /* + * Find jobs > t1 and <= t2 + */ + + for (t = t1 - t1 % 60; t <= t2; t += 60) { + if (t > t1) { + struct tm *tp = localtime(&t); + CronFile *file; + CronLine *line; + + for (file = FileBase; file; file = file->cf_Next) { +#ifdef FEATURE_DEBUG_OPT + if (DebugOpt) + log(5, "FILE %s:\n", file->cf_User); +#endif + if (file->cf_Deleted) + continue; + for (line = file->cf_LineBase; line; line = line->cl_Next) { +#ifdef FEATURE_DEBUG_OPT + if (DebugOpt) + log(5, " LINE %s\n", line->cl_Shell); +#endif + if (line->cl_Mins[tp->tm_min] && + line->cl_Hrs[tp->tm_hour] && + (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) && + line->cl_Mons[tp->tm_mon] + ) { +#ifdef FEATURE_DEBUG_OPT + if (DebugOpt) + log(5, " JobToDo: %d %s\n", line->cl_Pid, line->cl_Shell); +#endif + if (line->cl_Pid > 0) { + log(8, " process already running: %s %s\n", + file->cf_User, + line->cl_Shell + ); + } else if (line->cl_Pid == 0) { + line->cl_Pid = -1; + file->cf_Ready = 1; + ++nJobs; + } + } + } + } + } + } + return(nJobs); +} + +static void +RunJobs(void) +{ + CronFile *file; + CronLine *line; + + for (file = FileBase; file; file = file->cf_Next) { + if (file->cf_Ready) { + file->cf_Ready = 0; + + for (line = file->cf_LineBase; line; line = line->cl_Next) { + if (line->cl_Pid < 0) { + + RunJob(file, line); + + log(8, "USER %s pid %3d cmd %s\n", + file->cf_User, + line->cl_Pid, + line->cl_Shell + ); + if (line->cl_Pid < 0) + file->cf_Ready = 1; + else if (line->cl_Pid > 0) + file->cf_Running = 1; + } + } + } + } +} + +/* + * CheckJobs() - check for job completion + * + * Check for job completion, return number of jobs still running after + * all done. + */ + +static int +CheckJobs(void) +{ + CronFile *file; + CronLine *line; + int nStillRunning = 0; + + for (file = FileBase; file; file = file->cf_Next) { + if (file->cf_Running) { + file->cf_Running = 0; + + for (line = file->cf_LineBase; line; line = line->cl_Next) { + if (line->cl_Pid > 0) { + int status; + int r = wait4(line->cl_Pid, &status, WNOHANG, NULL); + + if (r < 0 || r == line->cl_Pid) { + EndJob(file, line); + if (line->cl_Pid) + file->cf_Running = 1; + } else if (r == 0) { + file->cf_Running = 1; + } + } + } + } + nStillRunning += file->cf_Running; + } + return(nStillRunning); +} + + + +static void +RunJob(CronFile *file, CronLine *line) +{ + char mailFile[128]; + int mailFd; + + line->cl_Pid = 0; + line->cl_MailFlag = 0; + + /* + * open mail file - owner root so nobody can screw with it. + */ + + snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", + file->cf_User, getpid()); + mailFd = open(mailFile, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_APPEND, 0600); + + if (mailFd >= 0) { + line->cl_MailFlag = 1; + fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", + file->cf_User, + line->cl_Shell + ); + line->cl_MailPos = lseek(mailFd, 0, 1); + } + + /* + * Fork as the user in question and run program + */ + + if ((line->cl_Pid = fork()) == 0) { + /* + * CHILD, FORK OK + */ + + /* + * Change running state to the user in question + */ + + if (ChangeUser(file->cf_User, 1) < 0) + return; + +#ifdef FEATURE_DEBUG_OPT + if (DebugOpt) + log(5, "Child Running %s\n", line->cl_Shell); +#endif + + /* + * stdin is already /dev/null, setup stdout and stderr + */ + + if (mailFd >= 0) { + dup2(mailFd, 1); + dup2(mailFd, 2); + close(mailFd); + } else { + log_err("unable to create mail file user %s file %s, output to /dev/null\n", + file->cf_User, + mailFile + ); + } + execl("/bin/sh", "/bin/sh", "-c", line->cl_Shell, NULL, NULL); + log_err("unable to exec, user %s cmd /bin/sh -c %s\n", + file->cf_User, + line->cl_Shell + ); + fdprintf(1, "Exec failed: /bin/sh -c %s\n", line->cl_Shell); + exit(0); + } else if (line->cl_Pid < 0) { + /* + * PARENT, FORK FAILED + */ + log_err("couldn't fork, user %s\n", file->cf_User); + line->cl_Pid = 0; + remove(mailFile); + } else { + /* + * PARENT, FORK SUCCESS + * + * rename mail-file based on pid of process + */ + char mailFile2[128]; + + snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", + file->cf_User, line->cl_Pid); + rename(mailFile, mailFile2); + } + + /* + * Close the mail file descriptor.. we can't just leave it open in + * a structure, closing it later, because we might run out of descriptors + */ + + if (mailFd >= 0) + close(mailFd); +} + +/* + * EndJob - called when job terminates and when mail terminates + */ + +static void +EndJob(const CronFile *file, CronLine *line) +{ + int mailFd; + char mailFile[128]; + struct stat sbuf; + + /* + * No job + */ + + if (line->cl_Pid <= 0) { + line->cl_Pid = 0; + return; + } + + /* + * End of job and no mail file + * End of sendmail job + */ + + snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", + file->cf_User, line->cl_Pid); + line->cl_Pid = 0; + + if (line->cl_MailFlag != 1) + return; + + line->cl_MailFlag = 0; + + /* + * End of primary job - check for mail file. If size has increased and + * the file is still valid, we sendmail it. + */ + + mailFd = open(mailFile, O_RDONLY); + remove(mailFile); + if (mailFd < 0) { + return; + } + + if (fstat(mailFd, &sbuf) < 0 || + sbuf.st_uid != DaemonUid || + sbuf.st_nlink != 0 || + sbuf.st_size == line->cl_MailPos || + !S_ISREG(sbuf.st_mode) + ) { + close(mailFd); + return; + } + + if ((line->cl_Pid = fork()) == 0) { + /* + * CHILD, FORK OK + */ + + /* + * change user id - no way in hell security can be compromised + * by the mailing and we already verified the mail file. + */ + + if (ChangeUser(file->cf_User, 1) < 0) + exit(0); + + /* + * run sendmail with mail file as standard input, only if + * mail file exists! + */ + + dup2(mailFd, 0); + dup2(1, 2); + close(mailFd); + + execl(SENDMAIL, SENDMAIL, SENDMAIL_ARGS, NULL, NULL); + log_err("unable to exec %s %s, user %s, output to sink null", + SENDMAIL, + SENDMAIL_ARGS, + file->cf_User + ); + exit(0); + } else if (line->cl_Pid < 0) { + /* + * PARENT, FORK FAILED + */ + log_err("unable to fork, user %s", file->cf_User); + line->cl_Pid = 0; + } else { + /* + * PARENT, FORK OK + */ + } + close(mailFd); +} |