diff --git a/usr.sbin/cron/cron/cron.c b/usr.sbin/cron/cron/cron.c index 460dd58dbd0d..90bae65fd1ee 100644 --- a/usr.sbin/cron/cron/cron.c +++ b/usr.sbin/cron/cron/cron.c @@ -1,594 +1,589 @@ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie uunet!decwrl!vixie!paul */ #if !defined(lint) && !defined(LINT) static const char rcsid[] = "$FreeBSD$"; #endif #define MAIN_PROGRAM #include "cron.h" #include #include #if SYS_TIME_H # include #else # include #endif static void usage(void), run_reboot_jobs(cron_db *), cron_tick(cron_db *, int), cron_sync(int), cron_sleep(cron_db *, int), cron_clean(cron_db *), #ifdef USE_SIGCHLD sigchld_handler(int), #endif sighup_handler(int), parse_args(int c, char *v[]); static int run_at_secres(cron_db *); static void find_interval_entry(pid_t); static cron_db database; static time_t last_time = 0; static int dst_enabled = 0; static int dont_daemonize = 0; struct pidfh *pfh; static void -usage() { +usage(void) +{ #if DEBUGGING char **dflags; #endif fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] " "[-m mailto] [-n] [-s] [-o] [-x debugflag[,...]]\n"); #if DEBUGGING fprintf(stderr, "\ndebugflags: "); for(dflags = DebugFlagNames; *dflags; dflags++) { fprintf(stderr, "%s ", *dflags); } fprintf(stderr, "\n"); #endif exit(ERROR_EXIT); } static void open_pidfile(void) { char pidfile[MAX_FNAME]; char buf[MAX_TEMPSTR]; int otherpid; (void) snprintf(pidfile, sizeof(pidfile), PIDFILE, PIDDIR); pfh = pidfile_open(pidfile, 0600, &otherpid); if (pfh == NULL) { if (errno == EEXIST) { snprintf(buf, sizeof(buf), "cron already running, pid: %d", otherpid); } else { snprintf(buf, sizeof(buf), "can't open or create %s: %s", pidfile, strerror(errno)); } log_it("CRON", getpid(), "DEATH", buf); errx(ERROR_EXIT, "%s", buf); } } int -main(argc, argv) - int argc; - char *argv[]; +main(int argc, char *argv[]) { int runnum; int secres1, secres2; struct tm *tm; ProgramName = argv[0]; #if defined(BSD) setlinebuf(stdout); setlinebuf(stderr); #endif parse_args(argc, argv); #ifdef USE_SIGCHLD (void) signal(SIGCHLD, sigchld_handler); #else (void) signal(SIGCLD, SIG_IGN); #endif (void) signal(SIGHUP, sighup_handler); open_pidfile(); set_cron_uid(); set_cron_cwd(); #if defined(POSIX) setenv("PATH", _PATH_DEFPATH, 1); #endif /* if there are no debug flags turned on, fork as a daemon should. */ # if DEBUGGING if (DebugFlags) { # else if (0) { # endif (void) fprintf(stderr, "[%d] cron started\n", getpid()); } else if (dont_daemonize == 0) { if (daemon(1, 0) == -1) { pidfile_remove(pfh); log_it("CRON",getpid(),"DEATH","can't become daemon"); exit(0); } } if (madvise(NULL, 0, MADV_PROTECT) != 0) log_it("CRON", getpid(), "WARNING", "madvise() failed"); pidfile_write(pfh); database.head = NULL; database.tail = NULL; database.mtime = (time_t) 0; load_database(&database); secres1 = secres2 = run_at_secres(&database); cron_sync(secres1); run_reboot_jobs(&database); runnum = 0; while (TRUE) { # if DEBUGGING /* if (!(DebugFlags & DTEST)) */ # endif /*DEBUGGING*/ cron_sleep(&database, secres1); if (secres1 == 0 || runnum % 60 == 0) { load_database(&database); secres2 = run_at_secres(&database); if (secres2 != secres1) { secres1 = secres2; if (secres1 != 0) { runnum = 0; } else { /* * Going from 1 sec to 60 sec res. If we * are already at minute's boundary, so * let it run, otherwise schedule for the * next minute. */ tm = localtime(&TargetTime); if (tm->tm_sec > 0) { cron_sync(secres2); continue; } } } } /* do this iteration */ cron_tick(&database, secres1); /* sleep 1 or 60 seconds */ TargetTime += (secres1 != 0) ? 1 : 60; runnum += 1; } } static void -run_reboot_jobs(db) - cron_db *db; +run_reboot_jobs(cron_db *db) { register user *u; register entry *e; for (u = db->head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { if (e->flags & WHEN_REBOOT) { job_add(e, u); } if (e->flags & INTERVAL) { e->lastexit = TargetTime; } } } (void) job_runqueue(); } static void cron_tick(cron_db *db, int secres) { static struct tm lasttm; static time_t diff = 0, /* time difference in seconds from the last offset change */ difflimit = 0; /* end point for the time zone correction */ struct tm otztm; /* time in the old time zone */ int otzsecond, otzminute, otzhour, otzdom, otzmonth, otzdow; register struct tm *tm = localtime(&TargetTime); register int second, minute, hour, dom, month, dow; register user *u; register entry *e; /* make 0-based values out of these so we can use them as indices */ second = (secres == 0) ? 0 : tm->tm_sec -FIRST_SECOND; minute = tm->tm_min -FIRST_MINUTE; hour = tm->tm_hour -FIRST_HOUR; dom = tm->tm_mday -FIRST_DOM; month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; dow = tm->tm_wday -FIRST_DOW; Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d,%d)\n", getpid(), second, minute, hour, dom, month, dow)) if (dst_enabled && last_time != 0 && TargetTime > last_time /* exclude stepping back */ && tm->tm_gmtoff != lasttm.tm_gmtoff ) { diff = tm->tm_gmtoff - lasttm.tm_gmtoff; if ( diff > 0 ) { /* ST->DST */ /* mark jobs for an earlier run */ difflimit = TargetTime + diff; for (u = db->head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { e->flags &= ~NOT_UNTIL; if ( e->lastrun >= TargetTime ) e->lastrun = 0; /* not include the ends of hourly ranges */ if ( e->lastrun < TargetTime - 3600 ) e->flags |= RUN_AT; else e->flags &= ~RUN_AT; } } } else { /* diff < 0 : DST->ST */ /* mark jobs for skipping */ difflimit = TargetTime - diff; for (u = db->head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { e->flags |= NOT_UNTIL; e->flags &= ~RUN_AT; } } } } if (diff != 0) { /* if the time was reset of the end of special zone is reached */ if (last_time == 0 || TargetTime >= difflimit) { /* disable the TZ switch checks */ diff = 0; difflimit = 0; for (u = db->head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { e->flags &= ~(RUN_AT|NOT_UNTIL); } } } else { /* get the time in the old time zone */ time_t difftime = TargetTime + tm->tm_gmtoff - diff; gmtime_r(&difftime, &otztm); /* make 0-based values out of these so we can use them as indices */ otzsecond = (secres == 0) ? 0 : otztm.tm_sec -FIRST_SECOND; otzminute = otztm.tm_min -FIRST_MINUTE; otzhour = otztm.tm_hour -FIRST_HOUR; otzdom = otztm.tm_mday -FIRST_DOM; otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; otzdow = otztm.tm_wday -FIRST_DOW; } } /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. * like many bizarre things, it's the standard. */ for (u = db->head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n", env_get("LOGNAME", e->envp), e->uid, e->gid, e->cmd)) if (e->flags & INTERVAL) { if (e->lastexit > 0 && TargetTime >= e->lastexit + e->interval) job_add(e, u); continue; } if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) { if (bit_test(e->second, otzsecond) && bit_test(e->minute, otzminute) && bit_test(e->hour, otzhour) && bit_test(e->month, otzmonth) && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom)) : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom)) ) ) { if ( e->flags & RUN_AT ) { e->flags &= ~RUN_AT; e->lastrun = TargetTime; job_add(e, u); continue; } else e->flags &= ~NOT_UNTIL; } else if ( e->flags & NOT_UNTIL ) continue; } if (bit_test(e->second, second) && bit_test(e->minute, minute) && bit_test(e->hour, hour) && bit_test(e->month, month) && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) ) ) { e->flags &= ~RUN_AT; e->lastrun = TargetTime; job_add(e, u); } } } last_time = TargetTime; lasttm = *tm; } /* the task here is to figure out how long it's going to be until :00 of the * following minute and initialize TargetTime to this value. TargetTime * will subsequently slide 60 seconds at a time, with correction applied * implicitly in cron_sleep(). it would be nice to let cron execute in * the "current minute" before going to sleep, but by restarting cron you * could then get it to execute a given minute's jobs more than once. * instead we have the chance of missing a minute's jobs completely, but * that's something sysadmin's know to expect what with crashing computers.. */ static void cron_sync(int secres) { struct tm *tm; TargetTime = time((time_t*)0); if (secres != 0) { TargetTime += 1; } else { tm = localtime(&TargetTime); TargetTime += (60 - tm->tm_sec); } } static void timespec_subtract(struct timespec *result, struct timespec *x, struct timespec *y) { *result = *x; result->tv_sec -= y->tv_sec; result->tv_nsec -= y->tv_nsec; if (result->tv_nsec < 0) { result->tv_sec--; result->tv_nsec += 1000000000; } } static void cron_sleep(cron_db *db, int secres) { int seconds_to_wait; int rval; struct timespec ctime, ttime, stime, remtime; /* * Loop until we reach the top of the next minute, sleep when possible. */ for (;;) { clock_gettime(CLOCK_REALTIME, &ctime); ttime.tv_sec = TargetTime; ttime.tv_nsec = 0; timespec_subtract(&stime, &ttime, &ctime); /* * If the seconds_to_wait value is insane, jump the cron */ if (stime.tv_sec < -600 || stime.tv_sec > 600) { cron_clean(db); cron_sync(secres); continue; } seconds_to_wait = (stime.tv_nsec > 0) ? stime.tv_sec + 1 : stime.tv_sec; Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n", getpid(), (long)TargetTime, seconds_to_wait)) /* * If we've run out of wait time or there are no jobs left * to run, break */ if (stime.tv_sec < 0) break; if (job_runqueue() == 0) { Debug(DSCH, ("[%d] sleeping for %d seconds\n", getpid(), seconds_to_wait)) for (;;) { rval = nanosleep(&stime, &remtime); if (rval == 0 || errno != EINTR) break; stime.tv_sec = remtime.tv_sec; stime.tv_nsec = remtime.tv_nsec; } } } } /* if the time was changed abruptly, clear the flags related * to the daylight time switch handling to avoid strange effects */ static void -cron_clean(db) - cron_db *db; +cron_clean(cron_db *db) { user *u; entry *e; last_time = 0; for (u = db->head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { e->flags &= ~(RUN_AT|NOT_UNTIL); } } } #ifdef USE_SIGCHLD static void sigchld_handler(int x) { WAIT_T waiter; PID_T pid; for (;;) { #ifdef POSIX pid = waitpid(-1, &waiter, WNOHANG); #else pid = wait3(&waiter, WNOHANG, (struct rusage *)0); #endif switch (pid) { case -1: Debug(DPROC, ("[%d] sigchld...no children\n", getpid())) return; case 0: Debug(DPROC, ("[%d] sigchld...no dead kids\n", getpid())) return; default: find_interval_entry(pid); Debug(DPROC, ("[%d] sigchld...pid #%d died, stat=%d\n", getpid(), pid, WEXITSTATUS(waiter))) } } } #endif /*USE_SIGCHLD*/ static void sighup_handler(int x) { log_close(); } static void -parse_args(argc, argv) - int argc; - char *argv[]; +parse_args(int argc, char *argv[]) { int argch; char *endp; while ((argch = getopt(argc, argv, "j:J:m:nosx:")) != -1) { switch (argch) { case 'j': Jitter = strtoul(optarg, &endp, 10); if (*optarg == '\0' || *endp != '\0' || Jitter > 60) errx(ERROR_EXIT, "bad value for jitter: %s", optarg); break; case 'J': RootJitter = strtoul(optarg, &endp, 10); if (*optarg == '\0' || *endp != '\0' || RootJitter > 60) errx(ERROR_EXIT, "bad value for root jitter: %s", optarg); break; case 'm': defmailto = optarg; break; case 'n': dont_daemonize = 1; break; case 'o': dst_enabled = 0; break; case 's': dst_enabled = 1; break; case 'x': if (!set_debug_flags(optarg)) usage(); break; default: usage(); } } } static int run_at_secres(cron_db *db) { user *u; entry *e; for (u = db->head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { if ((e->flags & (SEC_RES | INTERVAL)) != 0) return 1; } } return 0; } static void find_interval_entry(pid_t pid) { user *u; entry *e; for (u = database.head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { if ((e->flags & INTERVAL) && e->child == pid) { e->lastexit = time(NULL); e->child = 0; break; } } } } diff --git a/usr.sbin/cron/cron/database.c b/usr.sbin/cron/cron/database.c index ce1f916c2042..80b749f8d955 100644 --- a/usr.sbin/cron/cron/database.c +++ b/usr.sbin/cron/cron/database.c @@ -1,335 +1,322 @@ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie uunet!decwrl!vixie!paul */ #if !defined(lint) && !defined(LINT) static const char rcsid[] = "$FreeBSD$"; #endif /* vix 26jan87 [RCS has the log] */ #include "cron.h" #include #include #include #define TMAX(a,b) ((a)>(b)?(a):(b)) static void process_crontab(char *, char *, char *, struct stat *, cron_db *, cron_db *); void -load_database(old_db) - cron_db *old_db; +load_database(cron_db *old_db) { DIR *dir; struct stat statbuf; struct stat syscron_stat, st; time_t maxmtime; DIR_T *dp; cron_db new_db; user *u, *nu; struct { const char *name; struct stat st; } syscrontabs [] = { { SYSCRONTABS }, { LOCALSYSCRONTABS } }; int i, ret; Debug(DLOAD, ("[%d] load_database()\n", getpid())) /* before we start loading any data, do a stat on SPOOL_DIR * so that if anything changes as of this moment (i.e., before we've * cached any of the database), we'll see the changes next time. */ if (stat(SPOOL_DIR, &statbuf) < OK) { log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR); (void) exit(ERROR_EXIT); } /* track system crontab file */ if (stat(SYSCRONTAB, &syscron_stat) < OK) syscron_stat.st_mtime = 0; maxmtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime); for (i = 0; i < nitems(syscrontabs); i++) { if (stat(syscrontabs[i].name, &syscrontabs[i].st) != -1) { maxmtime = TMAX(syscrontabs[i].st.st_mtime, maxmtime); /* Traverse into directory */ if (!(dir = opendir(syscrontabs[i].name))) continue; while (NULL != (dp = readdir(dir))) { if (dp->d_name[0] == '.') continue; ret = fstatat(dirfd(dir), dp->d_name, &st, 0); if (ret != 0 || !S_ISREG(st.st_mode)) continue; maxmtime = TMAX(st.st_mtime, maxmtime); } closedir(dir); } else { syscrontabs[i].st.st_mtime = 0; } } /* if spooldir's mtime has not changed, we don't need to fiddle with * the database. * * Note that old_db->mtime is initialized to 0 in main(), and * so is guaranteed to be different than the stat() mtime the first * time this function is called. */ if (old_db->mtime == maxmtime) { Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n", getpid())) return; } /* something's different. make a new database, moving unchanged * elements from the old database, reloading elements that have * actually changed. Whatever is left in the old database when * we're done is chaff -- crontabs that disappeared. */ new_db.mtime = maxmtime; new_db.head = new_db.tail = NULL; if (syscron_stat.st_mtime) { process_crontab("root", SYS_NAME, SYSCRONTAB, &syscron_stat, &new_db, old_db); } for (i = 0; i < nitems(syscrontabs); i++) { char tabname[MAXPATHLEN]; if (syscrontabs[i].st.st_mtime == 0) continue; if (!(dir = opendir(syscrontabs[i].name))) { log_it("CRON", getpid(), "OPENDIR FAILED", syscrontabs[i].name); (void) exit(ERROR_EXIT); } while (NULL != (dp = readdir(dir))) { if (dp->d_name[0] == '.') continue; if (fstatat(dirfd(dir), dp->d_name, &st, 0) == 0 && !S_ISREG(st.st_mode)) continue; snprintf(tabname, sizeof(tabname), "%s/%s", syscrontabs[i].name, dp->d_name); process_crontab("root", SYS_NAME, tabname, &syscrontabs[i].st, &new_db, old_db); } closedir(dir); } /* we used to keep this dir open all the time, for the sake of * efficiency. however, we need to close it in every fork, and * we fork a lot more often than the mtime of the dir changes. */ if (!(dir = opendir(SPOOL_DIR))) { log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR); (void) exit(ERROR_EXIT); } while (NULL != (dp = readdir(dir))) { char fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1]; /* avoid file names beginning with ".". this is good * because we would otherwise waste two guaranteed calls * to getpwnam() for . and .., and also because user names * starting with a period are just too nasty to consider. */ if (dp->d_name[0] == '.') continue; (void) strncpy(fname, dp->d_name, sizeof(fname)); fname[sizeof(fname)-1] = '\0'; (void) snprintf(tabname, sizeof tabname, CRON_TAB(fname)); process_crontab(fname, fname, tabname, &statbuf, &new_db, old_db); } closedir(dir); /* if we don't do this, then when our children eventually call * getpwnam() in do_command.c's child_process to verify MAILTO=, * they will screw us up (and v-v). */ endpwent(); /* whatever's left in the old database is now junk. */ Debug(DLOAD, ("unlinking old database:\n")) for (u = old_db->head; u != NULL; u = nu) { Debug(DLOAD, ("\t%s\n", u->name)) nu = u->next; unlink_user(old_db, u); free_user(u); } /* overwrite the database control block with the new one. */ *old_db = new_db; Debug(DLOAD, ("load_database is done\n")) } void -link_user(db, u) - cron_db *db; - user *u; +link_user(cron_db *db, user *u) { if (db->head == NULL) db->head = u; if (db->tail) db->tail->next = u; u->prev = db->tail; u->next = NULL; db->tail = u; } void -unlink_user(db, u) - cron_db *db; - user *u; +unlink_user(cron_db *db, user *u) { if (u->prev == NULL) db->head = u->next; else u->prev->next = u->next; if (u->next == NULL) db->tail = u->prev; else u->next->prev = u->prev; } user * -find_user(db, name) - cron_db *db; - char *name; +find_user(cron_db *db, char *name) { - char *env_get(); user *u; for (u = db->head; u != NULL; u = u->next) if (!strcmp(u->name, name)) break; return u; } static void -process_crontab(uname, fname, tabname, statbuf, new_db, old_db) - char *uname; - char *fname; - char *tabname; - struct stat *statbuf; - cron_db *new_db; - cron_db *old_db; +process_crontab(char *uname, char *fname, char *tabname, struct stat *statbuf, + cron_db *new_db, cron_db *old_db) { struct passwd *pw = NULL; int crontab_fd = OK - 1; user *u; entry *e; time_t now; if (strcmp(fname, SYS_NAME) && !(pw = getpwnam(uname))) { /* file doesn't have a user in passwd file. */ log_it(fname, getpid(), "ORPHAN", "no passwd entry"); goto next_crontab; } if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) { /* crontab not accessible? */ log_it(fname, getpid(), "CAN'T OPEN", tabname); goto next_crontab; } if (fstat(crontab_fd, statbuf) < OK) { log_it(fname, getpid(), "FSTAT FAILED", tabname); goto next_crontab; } Debug(DLOAD, ("\t%s:", fname)) u = find_user(old_db, fname); if (u != NULL) { /* if crontab has not changed since we last read it * in, then we can just use our existing entry. */ if (u->mtime == statbuf->st_mtime) { Debug(DLOAD, (" [no change, using old data]")) unlink_user(old_db, u); link_user(new_db, u); goto next_crontab; } /* before we fall through to the code that will reload * the user, let's deallocate and unlink the user in * the old database. This is more a point of memory * efficiency than anything else, since all leftover * users will be deleted from the old database when * we finish with the crontab... */ Debug(DLOAD, (" [delete old data]")) unlink_user(old_db, u); free_user(u); log_it(fname, getpid(), "RELOAD", tabname); } u = load_user(crontab_fd, pw, fname); if (u != NULL) { u->mtime = statbuf->st_mtime; /* * TargetTime == 0 when we're initially populating the database, * and TargetTime > 0 any time after that (i.e. we're reloading * cron.d/ files because they've been created/modified). In the * latter case, we should check for any interval jobs and run * them 'n' seconds from the time the job was loaded/reloaded. * Otherwise, they will not be run until cron is restarted. */ if (TargetTime != 0) { now = time(NULL); for (e = u->crontab; e != NULL; e = e->next) { if ((e->flags & INTERVAL) != 0) e->lastexit = now; } } link_user(new_db, u); } next_crontab: if (crontab_fd >= OK) { Debug(DLOAD, (" [done]\n")) close(crontab_fd); } } diff --git a/usr.sbin/cron/cron/do_command.c b/usr.sbin/cron/cron/do_command.c index fcc5a5e3eb2a..13e147e5f3df 100644 --- a/usr.sbin/cron/cron/do_command.c +++ b/usr.sbin/cron/cron/do_command.c @@ -1,683 +1,679 @@ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie uunet!decwrl!vixie!paul */ #if !defined(lint) && !defined(LINT) static const char rcsid[] = "$FreeBSD$"; #endif #include "cron.h" #include #if defined(sequent) # include #endif #if defined(SYSLOG) # include #endif #if defined(LOGIN_CAP) # include #endif #ifdef PAM # include # include #endif static void child_process(entry *, user *); static WAIT_T wait_on_child(PID_T, const char *); extern char *environ; void -do_command(e, u) - entry *e; - user *u; +do_command(entry *e, user *u) { pid_t pid; Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", getpid(), e->cmd, u->name, e->uid, e->gid)) /* fork to become asynchronous -- parent process is done immediately, * and continues to run the normal cron code, which means return to * tick(). the child and grandchild don't leave this function, alive. */ switch ((pid = fork())) { case -1: log_it("CRON",getpid(),"error","can't fork"); if (e->flags & INTERVAL) e->lastexit = time(NULL); break; case 0: /* child process */ pidfile_close(pfh); child_process(e, u); Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) _exit(OK_EXIT); break; default: /* parent process */ Debug(DPROC, ("[%d] main process forked child #%d, " "returning to work\n", getpid(), pid)) if (e->flags & INTERVAL) { e->lastexit = 0; e->child = pid; } break; } Debug(DPROC, ("[%d] main process returning to work\n", getpid())) } static void -child_process(e, u) - entry *e; - user *u; +child_process(entry *e, user *u) { int stdin_pipe[2], stdout_pipe[2]; register char *input_data; char *usernm, *mailto, *mailfrom; PID_T jobpid, stdinjob, mailpid; register FILE *mail; register int bytes = 1; int status = 0; const char *homedir = NULL; # if defined(LOGIN_CAP) struct passwd *pwd; login_cap_t *lc; # endif Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) /* mark ourselves as different to PS command watchers by upshifting * our program name. This has no effect on some kernels. */ setproctitle("running job"); /* discover some useful and important environment settings */ usernm = env_get("LOGNAME", e->envp); mailto = env_get("MAILTO", e->envp); mailfrom = env_get("MAILFROM", e->envp); #ifdef PAM /* use PAM to see if the user's account is available, * i.e., not locked or expired or whatever. skip this * for system tasks from /etc/crontab -- they can run * as any user. */ if (strcmp(u->name, SYS_NAME)) { /* not equal */ pam_handle_t *pamh = NULL; int pam_err; struct pam_conv pamc = { .conv = openpam_nullconv, .appdata_ptr = NULL }; Debug(DPROC, ("[%d] checking account with PAM\n", getpid())) /* u->name keeps crontab owner name while LOGNAME is the name * of user to run command on behalf of. they should be the * same for a task from a per-user crontab. */ if (strcmp(u->name, usernm)) { log_it(usernm, getpid(), "username ambiguity", u->name); exit(ERROR_EXIT); } pam_err = pam_start("cron", usernm, &pamc, &pamh); if (pam_err != PAM_SUCCESS) { log_it("CRON", getpid(), "error", "can't start PAM"); exit(ERROR_EXIT); } pam_err = pam_acct_mgmt(pamh, PAM_SILENT); /* Expired password shouldn't prevent the job from running. */ if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) { log_it(usernm, getpid(), "USER", "account unavailable"); exit(ERROR_EXIT); } pam_end(pamh, pam_err); } #endif #ifdef USE_SIGCHLD /* our parent is watching for our death by catching SIGCHLD. we * do not care to watch for our children's deaths this way -- we * use wait() explicitly. so we have to disable the signal (which * was inherited from the parent). */ (void) signal(SIGCHLD, SIG_DFL); #else /* on system-V systems, we are ignoring SIGCLD. we have to stop * ignoring it now or the wait() in cron_pclose() won't work. * because of this, we have to wait() for our children here, as well. */ (void) signal(SIGCLD, SIG_DFL); #endif /*BSD*/ /* create some pipes to talk to our future child */ if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) { log_it("CRON", getpid(), "error", "can't pipe"); exit(ERROR_EXIT); } /* since we are a forked process, we can diddle the command string * we were passed -- nobody else is going to use it again, right? * * if a % is present in the command, previous characters are the * command, and subsequent characters are the additional input to * the command. Subsequent %'s will be transformed into newlines, * but that happens later. * * If there are escaped %'s, remove the escape character. */ /*local*/{ register int escaped = FALSE; register int ch; register char *p; for (input_data = p = e->cmd; (ch = *input_data); input_data++, p++) { if (p != input_data) *p = ch; if (escaped) { if (ch == '%' || ch == '\\') *--p = ch; escaped = FALSE; continue; } if (ch == '\\') { escaped = TRUE; continue; } if (ch == '%') { *input_data++ = '\0'; break; } } *p = '\0'; } /* fork again, this time so we can exec the user's command. */ switch (jobpid = fork()) { case -1: log_it("CRON",getpid(),"error","can't fork"); exit(ERROR_EXIT); /*NOTREACHED*/ case 0: Debug(DPROC, ("[%d] grandchild process fork()'ed\n", getpid())) if (e->uid == ROOT_UID) Jitter = RootJitter; if (Jitter != 0) { srandom(getpid()); sleep(random() % Jitter); } /* write a log message. we've waited this long to do it * because it was not until now that we knew the PID that * the actual user command shell was going to get and the * PID is part of the log message. */ if ((e->flags & DONT_LOG) == 0) { char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); log_it(usernm, getpid(), "CMD", x); free(x); } /* that's the last thing we'll log. close the log files. */ #ifdef SYSLOG closelog(); #endif /* get new pgrp, void tty, etc. */ (void) setsid(); /* close the pipe ends that we won't use. this doesn't affect * the parent, who has to read and write them; it keeps the * kernel from recording us as a potential client TWICE -- * which would keep it from sending SIGPIPE in otherwise * appropriate circumstances. */ close(stdin_pipe[WRITE_PIPE]); close(stdout_pipe[READ_PIPE]); /* grandchild process. make std{in,out} be the ends of * pipes opened by our daddy; make stderr go to stdout. */ close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); close(STDERR); dup2(STDOUT, STDERR); /* close the pipes we just dup'ed. The resources will remain. */ close(stdin_pipe[READ_PIPE]); close(stdout_pipe[WRITE_PIPE]); environ = NULL; # if defined(LOGIN_CAP) /* Set user's entire context, but note that PATH will * be overridden later */ if ((pwd = getpwnam(usernm)) == NULL) pwd = getpwuid(e->uid); lc = NULL; if (pwd != NULL) { if (pwd->pw_dir != NULL && pwd->pw_dir[0] != '\0') { homedir = strdup(pwd->pw_dir); if (homedir == NULL) { warn("strdup"); _exit(ERROR_EXIT); } } pwd->pw_gid = e->gid; if (e->class != NULL) lc = login_getclass(e->class); } if (pwd && setusercontext(lc, pwd, e->uid, LOGIN_SETALL) == 0) (void) endpwent(); else { /* fall back to the old method */ (void) endpwent(); # endif /* set our directory, uid and gid. Set gid first, * since once we set uid, we've lost root privileges. */ if (setgid(e->gid) != 0) { log_it(usernm, getpid(), "error", "setgid failed"); _exit(ERROR_EXIT); } # if defined(BSD) if (initgroups(usernm, e->gid) != 0) { log_it(usernm, getpid(), "error", "initgroups failed"); _exit(ERROR_EXIT); } # endif if (setlogin(usernm) != 0) { log_it(usernm, getpid(), "error", "setlogin failed"); _exit(ERROR_EXIT); } if (setuid(e->uid) != 0) { log_it(usernm, getpid(), "error", "setuid failed"); _exit(ERROR_EXIT); } /* we aren't root after this..*/ #if defined(LOGIN_CAP) } if (lc != NULL) login_close(lc); #endif /* For compatibility, we chdir to the value of HOME if it was * specified explicitly in the crontab file, but not if it was * set in the environment by some other mechanism. We chdir to * the homedir given by the pw entry otherwise. * * If !LOGIN_CAP, then HOME is always set in e->envp. * * XXX: probably should also consult PAM. */ { char *new_home = env_get("HOME", e->envp); if (new_home != NULL && new_home[0] != '\0') chdir(new_home); else if (homedir != NULL) chdir(homedir); else chdir("/"); } /* exec the command. Note that SHELL is not respected from * either login.conf or pw_shell, only an explicit setting * in the crontab. (default of _PATH_BSHELL is supplied when * setting up the entry) */ { char *shell = env_get("SHELL", e->envp); char **p; /* Apply the environment from the entry, overriding * existing values (this will always set LOGNAME and * SHELL). putenv should not fail unless malloc does. */ for (p = e->envp; *p; ++p) { if (putenv(*p) != 0) { warn("putenv"); _exit(ERROR_EXIT); } } /* HOME in login.conf overrides pw, and HOME in the * crontab overrides both. So set pw's value only if * nothing was already set (overwrite==0). */ if (homedir != NULL && setenv("HOME", homedir, 0) < 0) { warn("setenv(HOME)"); _exit(ERROR_EXIT); } /* PATH in login.conf is respected, but the crontab * overrides; set a default value only if nothing * already set. */ if (setenv("PATH", _PATH_DEFPATH, 0) < 0) { warn("setenv(PATH)"); _exit(ERROR_EXIT); } # if DEBUGGING if (DebugFlags & DTEST) { fprintf(stderr, "debug DTEST is on, not exec'ing command.\n"); fprintf(stderr, "\tcmd='%s' shell='%s'\n", e->cmd, shell); _exit(OK_EXIT); } # endif /*DEBUGGING*/ execl(shell, shell, "-c", e->cmd, (char *)NULL); warn("execl: couldn't exec `%s'", shell); _exit(ERROR_EXIT); } break; default: /* parent process */ break; } /* middle process, child of original cron, parent of process running * the user's command. */ Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) /* close the ends of the pipe that will only be referenced in the * grandchild process... */ close(stdin_pipe[READ_PIPE]); close(stdout_pipe[WRITE_PIPE]); /* * write, to the pipe connected to child's stdin, any input specified * after a % in the crontab entry. while we copy, convert any * additional %'s to newlines. when done, if some characters were * written and the last one wasn't a newline, write a newline. * * Note that if the input data won't fit into one pipe buffer (2K * or 4K on most BSD systems), and the child doesn't read its stdin, * we would block here. thus we must fork again. */ if (*input_data && (stdinjob = fork()) == 0) { register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); register int need_newline = FALSE; register int escaped = FALSE; register int ch; if (out == NULL) { warn("fdopen failed in child2"); _exit(ERROR_EXIT); } Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) /* close the pipe we don't use, since we inherited it and * are part of its reference count now. */ close(stdout_pipe[READ_PIPE]); /* translation: * \% -> % * % -> \n * \x -> \x for all x != % */ while ((ch = *input_data++)) { if (escaped) { if (ch != '%') putc('\\', out); } else { if (ch == '%') ch = '\n'; } if (!(escaped = (ch == '\\'))) { putc(ch, out); need_newline = (ch != '\n'); } } if (escaped) putc('\\', out); if (need_newline) putc('\n', out); /* close the pipe, causing an EOF condition. fclose causes * stdin_pipe[WRITE_PIPE] to be closed, too. */ fclose(out); Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) exit(0); } /* close the pipe to the grandkiddie's stdin, since its wicked uncle * ernie back there has it open and will close it when he's done. */ close(stdin_pipe[WRITE_PIPE]); /* * read output from the grandchild. it's stderr has been redirected to * it's stdout, which has been redirected to our pipe. if there is any * output, we'll be mailing it to the user whose crontab this is... * when the grandchild exits, we'll get EOF. */ Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) /*local*/{ register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); register int ch; if (in == NULL) { warn("fdopen failed in child"); _exit(ERROR_EXIT); } mail = NULL; ch = getc(in); if (ch != EOF) { Debug(DPROC|DEXT, ("[%d] got data (%x:%c) from grandchild\n", getpid(), ch, ch)) /* get name of recipient. this is MAILTO if set to a * valid local username; USER otherwise. */ if (mailto == NULL) { /* MAILTO not present, set to USER, * unless globally overridden. */ if (defmailto) mailto = defmailto; else mailto = usernm; } if (mailto && *mailto == '\0') mailto = NULL; /* if we are supposed to be mailing, MAILTO will * be non-NULL. only in this case should we set * up the mail command and subjects and stuff... */ if (mailto) { register char **env; auto char mailcmd[MAX_COMMAND]; auto char hostname[MAXHOSTNAMELEN]; if (gethostname(hostname, MAXHOSTNAMELEN) == -1) hostname[0] = '\0'; hostname[sizeof(hostname) - 1] = '\0'; (void) snprintf(mailcmd, sizeof(mailcmd), MAILARGS, MAILCMD); if (!(mail = cron_popen(mailcmd, "w", e, &mailpid))) { warn("%s", MAILCMD); (void) _exit(ERROR_EXIT); } if (mailfrom == NULL || *mailfrom == '\0') fprintf(mail, "From: Cron Daemon <%s@%s>\n", usernm, hostname); else fprintf(mail, "From: Cron Daemon <%s>\n", mailfrom); fprintf(mail, "To: %s\n", mailto); fprintf(mail, "Subject: Cron <%s@%s> %s\n", usernm, first_word(hostname, "."), e->cmd); # if defined(MAIL_DATE) fprintf(mail, "Date: %s\n", arpadate(&TargetTime)); # endif /* MAIL_DATE */ for (env = e->envp; *env; env++) fprintf(mail, "X-Cron-Env: <%s>\n", *env); fprintf(mail, "\n"); /* this was the first char from the pipe */ putc(ch, mail); } /* we have to read the input pipe no matter whether * we mail or not, but obviously we only write to * mail pipe if we ARE mailing. */ while (EOF != (ch = getc(in))) { bytes++; if (mail) putc(ch, mail); } } /*if data from grandchild*/ Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) /* also closes stdout_pipe[READ_PIPE] */ fclose(in); } /* wait for children to die. */ if (jobpid > 0) { WAIT_T waiter; waiter = wait_on_child(jobpid, "grandchild command job"); /* If everything went well, and -n was set, _and_ we have mail, * we won't be mailing... so shoot the messenger! */ if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0 && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR && mail) { Debug(DPROC, ("[%d] %s executed successfully, mail suppressed\n", getpid(), "grandchild command job")) kill(mailpid, SIGKILL); (void)fclose(mail); mail = NULL; } /* only close pipe if we opened it -- i.e., we're * mailing... */ if (mail) { Debug(DPROC, ("[%d] closing pipe to mail\n", getpid())) /* Note: the pclose will probably see * the termination of the grandchild * in addition to the mail process, since * it (the grandchild) is likely to exit * after closing its stdout. */ status = cron_pclose(mail); /* if there was output and we could not mail it, * log the facts so the poor user can figure out * what's going on. */ if (status) { char buf[MAX_TEMPSTR]; snprintf(buf, sizeof(buf), "mailed %d byte%s of output but got status 0x%04x\n", bytes, (bytes==1)?"":"s", status); log_it(usernm, getpid(), "MAIL", buf); } } } if (*input_data && stdinjob > 0) wait_on_child(stdinjob, "grandchild stdinjob"); } static WAIT_T wait_on_child(PID_T childpid, const char *name) { WAIT_T waiter; PID_T pid; Debug(DPROC, ("[%d] waiting for %s (%d) to finish\n", getpid(), name, childpid)) #ifdef POSIX while ((pid = waitpid(childpid, &waiter, 0)) < 0 && errno == EINTR) #else while ((pid = wait4(childpid, &waiter, 0, NULL)) < 0 && errno == EINTR) #endif ; if (pid < OK) return waiter; Debug(DPROC, ("[%d] %s (%d) finished, status=%04x", getpid(), name, pid, WEXITSTATUS(waiter))) if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) Debug(DPROC, (", dumped core")) Debug(DPROC, ("\n")) return waiter; } diff --git a/usr.sbin/cron/cron/job.c b/usr.sbin/cron/cron/job.c index 6d1b1af6e474..7ba7643c2c65 100644 --- a/usr.sbin/cron/cron/job.c +++ b/usr.sbin/cron/cron/job.c @@ -1,76 +1,74 @@ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie uunet!decwrl!vixie!paul */ #if !defined(lint) && !defined(LINT) static const char rcsid[] = "$FreeBSD$"; #endif #include "cron.h" typedef struct _job { struct _job *next; entry *e; user *u; } job; static job *jhead = NULL, *jtail = NULL; void -job_add(e, u) - register entry *e; - register user *u; +job_add(entry *e, user *u) { register job *j; /* if already on queue, keep going */ for (j=jhead; j; j=j->next) if (j->e == e && j->u == u) { return; } /* build a job queue element */ if ((j = (job*)malloc(sizeof(job))) == NULL) return; j->next = (job*) NULL; j->e = e; j->u = u; /* add it to the tail */ if (!jhead) { jhead=j; } else { jtail->next=j; } jtail = j; } int -job_runqueue() +job_runqueue(void) { register job *j, *jn; register int run = 0; for (j=jhead; j; j=jn) { do_command(j->e, j->u); jn = j->next; free(j); run++; } jhead = jtail = NULL; return run; } diff --git a/usr.sbin/cron/cron/popen.c b/usr.sbin/cron/cron/popen.c index 44aaa0c990f6..ee8323c49873 100644 --- a/usr.sbin/cron/cron/popen.c +++ b/usr.sbin/cron/cron/popen.c @@ -1,250 +1,246 @@ /* * Copyright (c) 1988 The Regents of the University of California. * All rights reserved. * * This code is derived from software written by Ken Arnold and * published in UNIX Review, Vol. 6, No. 8. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of California, Berkeley. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. * */ /* this came out of the ftpd sources; it's been modified to avoid the * globbing stuff since we don't need it. also execvp instead of execv. */ #ifndef lint #if 0 static char sccsid[] = "@(#)popen.c 5.7 (Berkeley) 2/14/89"; #endif static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include "cron.h" #include #include #include #if defined(SYSLOG) # include #endif #if defined(LOGIN_CAP) # include #endif #define MAX_ARGS 100 #define WANT_GLOBBING 0 /* * Special version of popen which avoids call to shell. This insures no one * may create a pipe to a hidden program as a side effect of a list or dir * command. */ static PID_T *pids; static int fds; FILE * -cron_popen(program, type, e, pidptr) - char *program, *type; - entry *e; - PID_T *pidptr; +cron_popen(char *program, char *type, entry *e, PID_T *pidptr) { register char *cp; FILE *iop; int argc, pdes[2]; PID_T pid; char *usernm; char *argv[MAX_ARGS + 1]; # if defined(LOGIN_CAP) struct passwd *pwd; login_cap_t *lc; # endif #if WANT_GLOBBING char **pop, *vv[2]; int gargc; char *gargv[1000]; extern char **glob(), **copyblk(); #endif if ((*type != 'r' && *type != 'w') || type[1]) return(NULL); if (!pids) { if ((fds = getdtablesize()) <= 0) return(NULL); if (!(pids = calloc(fds, sizeof(PID_T)))) return(NULL); } if (pipe(pdes) < 0) return(NULL); /* break up string into pieces */ for (argc = 0, cp = program; argc < MAX_ARGS; cp = NULL) if (!(argv[argc++] = strtok(cp, " \t\n"))) break; argv[MAX_ARGS] = NULL; #if WANT_GLOBBING /* glob each piece */ gargv[0] = argv[0]; for (gargc = argc = 1; argv[argc]; argc++) { if (!(pop = glob(argv[argc]))) { /* globbing failed */ vv[0] = argv[argc]; vv[1] = NULL; pop = copyblk(vv); } argv[argc] = (char *)pop; /* save to free later */ while (*pop && gargc < 1000) gargv[gargc++] = *pop++; } gargv[gargc] = NULL; #endif iop = NULL; switch(pid = fork()) { case -1: /* error */ (void)close(pdes[0]); (void)close(pdes[1]); goto pfree; /* NOTREACHED */ case 0: /* child */ if (e != NULL) { #ifdef SYSLOG closelog(); #endif /* get new pgrp, void tty, etc. */ (void) setsid(); } if (*type == 'r') { /* Do not share our parent's stdin */ (void)close(0); (void)open(_PATH_DEVNULL, O_RDWR); if (pdes[1] != 1) { dup2(pdes[1], 1); dup2(pdes[1], 2); /* stderr, too! */ (void)close(pdes[1]); } (void)close(pdes[0]); } else { if (pdes[0] != 0) { dup2(pdes[0], 0); (void)close(pdes[0]); } /* Hack: stdout gets revoked */ (void)close(1); (void)open(_PATH_DEVNULL, O_RDWR); (void)close(2); (void)open(_PATH_DEVNULL, O_RDWR); (void)close(pdes[1]); } if (e != NULL) { /* Set user's entire context, but skip the environment * as cron provides a separate interface for this */ usernm = env_get("LOGNAME", e->envp); # if defined(LOGIN_CAP) if ((pwd = getpwnam(usernm)) == NULL) pwd = getpwuid(e->uid); lc = NULL; if (pwd != NULL) { pwd->pw_gid = e->gid; if (e->class != NULL) lc = login_getclass(e->class); } if (pwd && setusercontext(lc, pwd, e->uid, LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0) (void) endpwent(); else { /* fall back to the old method */ (void) endpwent(); # endif /* * Set our directory, uid and gid. Set gid * first since once we set uid, we've lost * root privileges. */ if (setgid(e->gid) != 0) _exit(ERROR_EXIT); # if defined(BSD) if (initgroups(usernm, e->gid) != 0) _exit(ERROR_EXIT); # endif if (setlogin(usernm) != 0) _exit(ERROR_EXIT); if (setuid(e->uid) != 0) _exit(ERROR_EXIT); /* we aren't root after this..*/ #if defined(LOGIN_CAP) } if (lc != NULL) login_close(lc); #endif chdir(env_get("HOME", e->envp)); } #if WANT_GLOBBING execvp(gargv[0], gargv); #else execvp(argv[0], argv); #endif _exit(1); } /* parent; assume fdopen can't fail... */ if (*type == 'r') { iop = fdopen(pdes[0], type); (void)close(pdes[1]); } else { iop = fdopen(pdes[1], type); (void)close(pdes[0]); } pids[fileno(iop)] = pid; pfree: #if WANT_GLOBBING for (argc = 1; argv[argc] != NULL; argc++) { /* blkfree((char **)argv[argc]); */ free((char *)argv[argc]); } #endif *pidptr = pid; return(iop); } int -cron_pclose(iop) - FILE *iop; +cron_pclose(FILE *iop) { register int fdes; int omask; WAIT_T stat_loc; PID_T pid; /* * pclose returns -1 if stream is not associated with a * `popened' command, or, if already `pclosed'. */ if (pids == 0 || pids[fdes = fileno(iop)] == 0) return(-1); (void)fclose(iop); omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1) ; (void)sigsetmask(omask); pids[fdes] = 0; return (pid == -1 ? -1 : WEXITSTATUS(stat_loc)); } diff --git a/usr.sbin/cron/cron/user.c b/usr.sbin/cron/cron/user.c index 16fd61723a86..ce8e08fccf20 100644 --- a/usr.sbin/cron/cron/user.c +++ b/usr.sbin/cron/cron/user.c @@ -1,128 +1,124 @@ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie uunet!decwrl!vixie!paul */ #if !defined(lint) && !defined(LINT) static const char rcsid[] = "$FreeBSD$"; #endif /* vix 26jan87 [log is in RCS file] */ #include "cron.h" static char *User_name; void -free_user(u) - user *u; +free_user(user *u) { entry *e, *ne; free(u->name); for (e = u->crontab; e != NULL; e = ne) { ne = e->next; free_entry(e); } free(u); } static void -log_error(msg) - char *msg; +log_error(char *msg) { log_it(User_name, getpid(), "PARSE", msg); } +/* NULL pw implies syscrontab */ user * -load_user(crontab_fd, pw, name) - int crontab_fd; - struct passwd *pw; /* NULL implies syscrontab */ - char *name; +load_user(int crontab_fd, struct passwd *pw, char *name) { char envstr[MAX_ENVSTR]; FILE *file; user *u; entry *e; int status; char **envp, **tenvp; if (!(file = fdopen(crontab_fd, "r"))) { warn("fdopen on crontab_fd in load_user"); return NULL; } Debug(DPARS, ("load_user()\n")) /* file is open. build user entry, then read the crontab file. */ if ((u = (user *) malloc(sizeof(user))) == NULL) { errno = ENOMEM; return NULL; } if ((u->name = strdup(name)) == NULL) { free(u); errno = ENOMEM; return NULL; } u->crontab = NULL; /* * init environment. this will be copied/augmented for each entry. */ if ((envp = env_init()) == NULL) { free(u->name); free(u); return NULL; } /* * load the crontab */ while ((status = load_env(envstr, file)) >= OK) { switch (status) { case ERR: free_user(u); u = NULL; goto done; case FALSE: User_name = u->name; /* for log_error */ e = load_entry(file, log_error, pw, envp); if (e) { e->next = u->crontab; u->crontab = e; } break; case TRUE: if ((tenvp = env_set(envp, envstr))) { envp = tenvp; } else { free_user(u); u = NULL; goto done; } break; } } done: env_free(envp); fclose(file); Debug(DPARS, ("...load_user() done\n")) return u; } diff --git a/usr.sbin/cron/crontab/crontab.c b/usr.sbin/cron/crontab/crontab.c index 53c58403406a..13e8250a1b63 100644 --- a/usr.sbin/cron/crontab/crontab.c +++ b/usr.sbin/cron/crontab/crontab.c @@ -1,648 +1,650 @@ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie uunet!decwrl!vixie!paul * From Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp */ #if !defined(lint) && !defined(LINT) static const char rcsid[] = "$FreeBSD$"; #endif /* crontab - install and manage per-user crontab files * vix 02may87 [RCS has the rest of the log] * vix 26jan87 [original] */ #define MAIN_PROGRAM #include #include "cron.h" #include #include #include #include #include #include #ifdef USE_UTIMES # include #else # include # include #endif #if defined(POSIX) # include #endif #define MD5_SIZE 33 #define NHEADER_LINES 3 enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace }; #if DEBUGGING static char *Options[] = { "???", "list", "delete", "edit", "replace" }; #endif static PID_T Pid; static char User[MAXLOGNAME], RealUser[MAXLOGNAME]; static char Filename[MAX_FNAME]; static FILE *NewCrontab; static int CheckErrorCount; static enum opt_t Option; static int fflag; static struct passwd *pw; static void list_cmd(void), delete_cmd(void), edit_cmd(void), poke_daemon(void), check_error(char *), parse_args(int c, char *v[]); static int replace_cmd(void); static void usage(char *msg) { fprintf(stderr, "crontab: usage error: %s\n", msg); fprintf(stderr, "%s\n%s\n", "usage: crontab [-u user] file", " crontab [-u user] { -l | -r [-f] | -e }"); exit(ERROR_EXIT); } int main(int argc, char *argv[]) { int exitstatus; Pid = getpid(); ProgramName = argv[0]; #if defined(POSIX) setlocale(LC_ALL, ""); #endif #if defined(BSD) setlinebuf(stderr); #endif parse_args(argc, argv); /* sets many globals, opens a file */ set_cron_uid(); set_cron_cwd(); if (!allowed(User)) { warnx("you (%s) are not allowed to use this program", User); log_it(RealUser, Pid, "AUTH", "crontab command not allowed"); exit(ERROR_EXIT); } exitstatus = OK_EXIT; switch (Option) { case opt_list: list_cmd(); break; case opt_delete: delete_cmd(); break; case opt_edit: edit_cmd(); break; case opt_replace: if (replace_cmd() < 0) exitstatus = ERROR_EXIT; break; case opt_unknown: break; } exit(exitstatus); /*NOTREACHED*/ } static void -parse_args(argc, argv) - int argc; - char *argv[]; +parse_args(int argc, char *argv[]) { int argch; char resolved_path[PATH_MAX]; if (!(pw = getpwuid(getuid()))) errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out"); bzero(pw->pw_passwd, strlen(pw->pw_passwd)); (void) strncpy(User, pw->pw_name, (sizeof User)-1); User[(sizeof User)-1] = '\0'; strcpy(RealUser, User); Filename[0] = '\0'; Option = opt_unknown; while ((argch = getopt(argc, argv, "u:lerx:f")) != -1) { switch (argch) { case 'x': if (!set_debug_flags(optarg)) usage("bad debug option"); break; case 'u': if (getuid() != ROOT_UID) errx(ERROR_EXIT, "must be privileged to use -u"); if (!(pw = getpwnam(optarg))) errx(ERROR_EXIT, "user `%s' unknown", optarg); bzero(pw->pw_passwd, strlen(pw->pw_passwd)); (void) strncpy(User, pw->pw_name, (sizeof User)-1); User[(sizeof User)-1] = '\0'; break; case 'l': if (Option != opt_unknown) usage("only one operation permitted"); Option = opt_list; break; case 'r': if (Option != opt_unknown) usage("only one operation permitted"); Option = opt_delete; break; case 'e': if (Option != opt_unknown) usage("only one operation permitted"); Option = opt_edit; break; case 'f': fflag = 1; break; default: usage("unrecognized option"); } } endpwent(); if (Option != opt_unknown) { if (argv[optind] != NULL) { usage("no arguments permitted after this option"); } } else { if (argv[optind] != NULL) { Option = opt_replace; (void) strncpy (Filename, argv[optind], (sizeof Filename)-1); Filename[(sizeof Filename)-1] = '\0'; } else { usage("file name must be specified for replace"); } } if (Option == opt_replace) { /* relinquish the setuid status of the binary during * the open, lest nonroot users read files they should * not be able to read. we can't use access() here * since there's a race condition. thanks go out to * Arnt Gulbrandsen for spotting * the race. */ if (swap_uids() < OK) err(ERROR_EXIT, "swapping uids"); /* we have to open the file here because we're going to * chdir(2) into /var/cron before we get around to * reading the file. */ if (!strcmp(Filename, "-")) { NewCrontab = stdin; } else if (realpath(Filename, resolved_path) != NULL && !strcmp(resolved_path, SYSCRONTAB)) { err(ERROR_EXIT, SYSCRONTAB " must be edited manually"); } else { if (!(NewCrontab = fopen(Filename, "r"))) err(ERROR_EXIT, "%s", Filename); } if (swap_uids_back() < OK) err(ERROR_EXIT, "swapping uids back"); } Debug(DMISC, ("user=%s, file=%s, option=%s\n", User, Filename, Options[(int)Option])) } static void copy_file(FILE *in, FILE *out) { int x, ch; Set_LineNum(1) /* ignore the top few comments since we probably put them there. */ for (x = 0; x < NHEADER_LINES; x++) { ch = get_char(in); if (EOF == ch) break; if ('#' != ch) { putc(ch, out); break; } while (EOF != (ch = get_char(in))) if (ch == '\n') break; if (EOF == ch) break; } /* copy the rest of the crontab (if any) to the output file. */ if (EOF != ch) while (EOF != (ch = get_char(in))) putc(ch, out); } static void -list_cmd() { +list_cmd(void) +{ char n[MAX_FNAME]; FILE *f; log_it(RealUser, Pid, "LIST", User); (void) snprintf(n, sizeof(n), CRON_TAB(User)); if (!(f = fopen(n, "r"))) { if (errno == ENOENT) errx(ERROR_EXIT, "no crontab for %s", User); else err(ERROR_EXIT, "%s", n); } /* file is open. copy to stdout, close. */ copy_file(f, stdout); fclose(f); } static void -delete_cmd() { +delete_cmd(void) +{ char n[MAX_FNAME]; int ch, first; if (!fflag && isatty(STDIN_FILENO)) { (void)fprintf(stderr, "remove crontab for %s? ", User); first = ch = getchar(); while (ch != '\n' && ch != EOF) ch = getchar(); if (first != 'y' && first != 'Y') return; } log_it(RealUser, Pid, "DELETE", User); (void) snprintf(n, sizeof(n), CRON_TAB(User)); if (unlink(n)) { if (errno == ENOENT) errx(ERROR_EXIT, "no crontab for %s", User); else err(ERROR_EXIT, "%s", n); } poke_daemon(); } static void -check_error(msg) - char *msg; +check_error(char *msg) { CheckErrorCount++; fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg); } static void -edit_cmd() { +edit_cmd(void) +{ char n[MAX_FNAME], q[MAX_TEMPSTR], *editor; FILE *f; int t; struct stat statbuf, fsbuf; WAIT_T waiter; PID_T pid, xpid; mode_t um; int syntax_error = 0; char orig_md5[MD5_SIZE]; char new_md5[MD5_SIZE]; log_it(RealUser, Pid, "BEGIN EDIT", User); (void) snprintf(n, sizeof(n), CRON_TAB(User)); if (!(f = fopen(n, "r"))) { if (errno != ENOENT) err(ERROR_EXIT, "%s", n); warnx("no crontab for %s - using an empty one", User); if (!(f = fopen(_PATH_DEVNULL, "r"))) err(ERROR_EXIT, _PATH_DEVNULL); } um = umask(077); (void) snprintf(Filename, sizeof(Filename), "/tmp/crontab.XXXXXXXXXX"); if ((t = mkstemp(Filename)) == -1) { warn("%s", Filename); (void) umask(um); goto fatal; } (void) umask(um); #ifdef HAS_FCHOWN if (fchown(t, getuid(), getgid()) < 0) { #else if (chown(Filename, getuid(), getgid()) < 0) { #endif warn("fchown"); goto fatal; } if (!(NewCrontab = fdopen(t, "r+"))) { warn("fdopen"); goto fatal; } copy_file(f, NewCrontab); fclose(f); if (fflush(NewCrontab)) err(ERROR_EXIT, "%s", Filename); if (fstat(t, &fsbuf) < 0) { warn("unable to fstat temp file"); goto fatal; } again: if (swap_uids() < OK) err(ERROR_EXIT, "swapping uids"); if (stat(Filename, &statbuf) < 0) { warn("stat"); fatal: unlink(Filename); exit(ERROR_EXIT); } if (swap_uids_back() < OK) err(ERROR_EXIT, "swapping uids back"); if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino) errx(ERROR_EXIT, "temp file must be edited in place"); if (MD5File(Filename, orig_md5) == NULL) { warn("MD5"); goto fatal; } if ((!(editor = getenv("VISUAL"))) && (!(editor = getenv("EDITOR"))) ) { editor = EDITOR; } /* we still have the file open. editors will generally rewrite the * original file rather than renaming/unlinking it and starting a * new one; even backup files are supposed to be made by copying * rather than by renaming. if some editor does not support this, * then don't use it. the security problems are more severe if we * close and reopen the file around the edit. */ switch (pid = fork()) { case -1: warn("fork"); goto fatal; case 0: /* child */ if (setuid(getuid()) < 0) err(ERROR_EXIT, "setuid(getuid())"); if (chdir("/tmp") < 0) err(ERROR_EXIT, "chdir(/tmp)"); if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR) errx(ERROR_EXIT, "editor or filename too long"); execlp(editor, editor, Filename, (char *)NULL); err(ERROR_EXIT, "%s", editor); /*NOTREACHED*/ default: /* parent */ break; } /* parent */ { void (*sig[3])(int signal); sig[0] = signal(SIGHUP, SIG_IGN); sig[1] = signal(SIGINT, SIG_IGN); sig[2] = signal(SIGTERM, SIG_IGN); xpid = wait(&waiter); signal(SIGHUP, sig[0]); signal(SIGINT, sig[1]); signal(SIGTERM, sig[2]); } if (xpid != pid) { warnx("wrong PID (%d != %d) from \"%s\"", xpid, pid, editor); goto fatal; } if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { warnx("\"%s\" exited with status %d", editor, WEXITSTATUS(waiter)); goto fatal; } if (WIFSIGNALED(waiter)) { warnx("\"%s\" killed; signal %d (%score dumped)", editor, WTERMSIG(waiter), WCOREDUMP(waiter) ?"" :"no "); goto fatal; } if (swap_uids() < OK) err(ERROR_EXIT, "swapping uids"); if (stat(Filename, &statbuf) < 0) { warn("stat"); goto fatal; } if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino) errx(ERROR_EXIT, "temp file must be edited in place"); if (MD5File(Filename, new_md5) == NULL) { warn("MD5"); goto fatal; } if (swap_uids_back() < OK) err(ERROR_EXIT, "swapping uids back"); if (strcmp(orig_md5, new_md5) == 0 && !syntax_error) { warnx("no changes made to crontab"); goto remove; } warnx("installing new crontab"); switch (replace_cmd()) { case 0: /* Success */ break; case -1: /* Syntax error */ for (;;) { printf("Do you want to retry the same edit? "); fflush(stdout); q[0] = '\0'; (void) fgets(q, sizeof q, stdin); switch (islower(q[0]) ? q[0] : tolower(q[0])) { case 'y': syntax_error = 1; goto again; case 'n': goto abandon; default: fprintf(stderr, "Enter Y or N\n"); } } /*NOTREACHED*/ case -2: /* Install error */ abandon: warnx("edits left in %s", Filename); goto done; default: warnx("panic: bad switch() in replace_cmd()"); goto fatal; } remove: unlink(Filename); done: log_it(RealUser, Pid, "END EDIT", User); } /* returns 0 on success * -1 on syntax error * -2 on install error */ static int -replace_cmd() { +replace_cmd(void) +{ char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME]; FILE *tmp; int ch, eof; entry *e; time_t now = time(NULL); char **envp = env_init(); if (envp == NULL) { warnx("cannot allocate memory"); return (-2); } (void) snprintf(n, sizeof(n), "tmp.%d", Pid); (void) snprintf(tn, sizeof(tn), CRON_TAB(n)); if (!(tmp = fopen(tn, "w+"))) { warn("%s", tn); return (-2); } /* write a signature at the top of the file. * * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. */ fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); fprintf(tmp, "# (Cron version -- %s)\n", rcsid); /* copy the crontab to the tmp */ rewind(NewCrontab); Set_LineNum(1) while (EOF != (ch = get_char(NewCrontab))) putc(ch, tmp); ftruncate(fileno(tmp), ftello(tmp)); fflush(tmp); rewind(tmp); if (ferror(tmp)) { warnx("error while writing new crontab to %s", tn); fclose(tmp); unlink(tn); return (-2); } /* check the syntax of the file being installed. */ /* BUG: was reporting errors after the EOF if there were any errors * in the file proper -- kludged it by stopping after first error. * vix 31mar87 */ Set_LineNum(1 - NHEADER_LINES) CheckErrorCount = 0; eof = FALSE; while (!CheckErrorCount && !eof) { switch (load_env(envstr, tmp)) { case ERR: eof = TRUE; break; case FALSE: e = load_entry(tmp, check_error, pw, envp); if (e) free_entry(e); break; case TRUE: break; } } if (CheckErrorCount != 0) { warnx("errors in crontab file, can't install"); fclose(tmp); unlink(tn); return (-1); } #ifdef HAS_FCHOWN if (fchown(fileno(tmp), ROOT_UID, -1) < OK) #else if (chown(tn, ROOT_UID, -1) < OK) #endif { warn("chown"); fclose(tmp); unlink(tn); return (-2); } #ifdef HAS_FCHMOD if (fchmod(fileno(tmp), 0600) < OK) #else if (chmod(tn, 0600) < OK) #endif { warn("chown"); fclose(tmp); unlink(tn); return (-2); } if (fclose(tmp) == EOF) { warn("fclose"); unlink(tn); return (-2); } (void) snprintf(n, sizeof(n), CRON_TAB(User)); if (rename(tn, n)) { warn("error renaming %s to %s", tn, n); unlink(tn); return (-2); } log_it(RealUser, Pid, "REPLACE", User); /* * Creating the 'tn' temp file has already updated the * modification time of the spool directory. Sleep for a * second to ensure that poke_daemon() sets a later * modification time. Otherwise, this can race with the cron * daemon scanning for updated crontabs. */ sleep(1); poke_daemon(); return (0); } static void -poke_daemon() { +poke_daemon(void) +{ #ifdef USE_UTIMES struct timeval tvs[2]; (void)gettimeofday(&tvs[0], NULL); tvs[1] = tvs[0]; if (utimes(SPOOL_DIR, tvs) < OK) { warn("can't update mtime on spooldir %s", SPOOL_DIR); return; } #else if (utime(SPOOL_DIR, NULL) < OK) { warn("can't update mtime on spooldir %s", SPOOL_DIR); return; } #endif /*USE_UTIMES*/ } diff --git a/usr.sbin/cron/lib/entry.c b/usr.sbin/cron/lib/entry.c index 2693c9c8d07a..6d8cff847604 100644 --- a/usr.sbin/cron/lib/entry.c +++ b/usr.sbin/cron/lib/entry.c @@ -1,724 +1,701 @@ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie uunet!decwrl!vixie!paul */ #if !defined(lint) && !defined(LINT) static const char rcsid[] = "$FreeBSD$"; #endif /* vix 26jan87 [RCS'd; rest of log is in RCS file] * vix 01jan87 [added line-level error recovery] * vix 31dec86 [added /step to the from-to range, per bob@acornrc] * vix 30dec86 [written] */ #include "cron.h" #include #ifdef LOGIN_CAP #include #endif typedef enum ecode { e_none, e_minute, e_hour, e_dom, e_month, e_dow, e_cmd, e_timespec, e_username, e_group, e_option, e_mem #ifdef LOGIN_CAP , e_class #endif } ecode_e; static char get_list(bitstr_t *, int, int, char *[], int, FILE *), get_range(bitstr_t *, int, int, char *[], int, FILE *), get_number(int *, int, char *[], int, FILE *); static int set_element(bitstr_t *, int, int, int); static char *ecodes[] = { "no error", "bad minute", "bad hour", "bad day-of-month", "bad month", "bad day-of-week", "bad command", "bad time specifier", "bad username", "bad group name", "bad option", "out of memory", #ifdef LOGIN_CAP "bad class name", #endif }; void -free_entry(e) - entry *e; +free_entry(entry *e) { #ifdef LOGIN_CAP if (e->class != NULL) free(e->class); #endif if (e->cmd != NULL) free(e->cmd); if (e->envp != NULL) env_free(e->envp); free(e); } /* return NULL if eof or syntax error occurs; * otherwise return a pointer to a new entry. */ entry * -load_entry(file, error_func, pw, envp) - FILE *file; - void (*error_func)(char *); - struct passwd *pw; - char **envp; +load_entry(FILE *file, void (*error_func)(char *), struct passwd *pw, + char **envp) { /* this function reads one crontab entry -- the next -- from a file. * it skips any leading blank lines, ignores comments, and returns * EOF if for any reason the entry can't be read and parsed. * * the entry is also parsed here. * * syntax: * user crontab: * minutes hours doms months dows cmd\n * system crontab (/etc/crontab): * minutes hours doms months dows USERNAME cmd\n */ ecode_e ecode = e_none; entry *e; int ch; char cmd[MAX_COMMAND]; char envstr[MAX_ENVSTR]; char **prev_env; Debug(DPARS, ("load_entry()...about to eat comments\n")) skip_comments(file); ch = get_char(file); if (ch == EOF) return NULL; /* ch is now the first useful character of a useful line. * it may be an @special or it may be the first character * of a list of minutes. */ e = (entry *) calloc(sizeof(entry), sizeof(char)); if (e == NULL) { warn("load_entry: calloc failed"); return NULL; } if (ch == '@') { long interval; char *endptr; /* all of these should be flagged and load-limited; i.e., * instead of @hourly meaning "0 * * * *" it should mean * "close to the front of every hour but not 'til the * system load is low". Problems are: how do you know * what "low" means? (save me from /etc/cron.conf!) and: * how to guarantee low variance (how low is low?), which * means how to we run roughly every hour -- seems like * we need to keep a history or let the first hour set * the schedule, which means we aren't load-limited * anymore. too much for my overloaded brain. (vix, jan90) * HINT */ Debug(DPARS, ("load_entry()...about to test shortcuts\n")) ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); if (!strcmp("reboot", cmd)) { Debug(DPARS, ("load_entry()...reboot shortcut\n")) e->flags |= WHEN_REBOOT; } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ Debug(DPARS, ("load_entry()...yearly shortcut\n")) bit_set(e->second, 0); bit_set(e->minute, 0); bit_set(e->hour, 0); bit_set(e->dom, 0); bit_set(e->month, 0); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); e->flags |= DOW_STAR; } else if (!strcmp("monthly", cmd)) { Debug(DPARS, ("load_entry()...monthly shortcut\n")) bit_set(e->second, 0); bit_set(e->minute, 0); bit_set(e->hour, 0); bit_set(e->dom, 0); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); e->flags |= DOW_STAR; } else if (!strcmp("weekly", cmd)) { Debug(DPARS, ("load_entry()...weekly shortcut\n")) bit_set(e->second, 0); bit_set(e->minute, 0); bit_set(e->hour, 0); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); e->flags |= DOM_STAR; bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_set(e->dow, 0); } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { Debug(DPARS, ("load_entry()...daily shortcut\n")) bit_set(e->second, 0); bit_set(e->minute, 0); bit_set(e->hour, 0); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); } else if (!strcmp("hourly", cmd)) { Debug(DPARS, ("load_entry()...hourly shortcut\n")) bit_set(e->second, 0); bit_set(e->minute, 0); bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); } else if (!strcmp("every_minute", cmd)) { Debug(DPARS, ("load_entry()...every_minute shortcut\n")) bit_set(e->second, 0); bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1)); bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); } else if (!strcmp("every_second", cmd)) { Debug(DPARS, ("load_entry()...every_second shortcut\n")) e->flags |= SEC_RES; bit_nset(e->second, 0, (LAST_SECOND-FIRST_SECOND+1)); bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1)); bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); } else if (*cmd != '\0' && (interval = strtol(cmd, &endptr, 10)) > 0 && *endptr == '\0') { Debug(DPARS, ("load_entry()... %ld seconds " "since last run\n", interval)) e->interval = interval; e->flags = INTERVAL; } else { ecode = e_timespec; goto eof; } /* Advance past whitespace between shortcut and * username/command. */ Skip_Blanks(ch, file); if (ch == EOF) { ecode = e_cmd; goto eof; } } else { Debug(DPARS, ("load_entry()...about to parse numerics\n")) bit_set(e->second, 0); ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, PPC_NULL, ch, file); if (ch == EOF) { ecode = e_minute; goto eof; } /* hours */ ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, PPC_NULL, ch, file); if (ch == EOF) { ecode = e_hour; goto eof; } /* DOM (days of month) */ if (ch == '*') e->flags |= DOM_STAR; ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file); if (ch == EOF) { ecode = e_dom; goto eof; } /* month */ ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, MonthNames, ch, file); if (ch == EOF) { ecode = e_month; goto eof; } /* DOW (days of week) */ if (ch == '*') e->flags |= DOW_STAR; ch = get_list(e->dow, FIRST_DOW, LAST_DOW, DowNames, ch, file); if (ch == EOF) { ecode = e_dow; goto eof; } } /* make sundays equivalent */ if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { bit_set(e->dow, 0); bit_set(e->dow, 7); } /* ch is the first character of a command, or a username */ unget_char(ch, file); if (!pw) { char *username = cmd; /* temp buffer */ char *s; struct group *grp; #ifdef LOGIN_CAP login_cap_t *lc; #endif Debug(DPARS, ("load_entry()...about to parse username\n")) ch = get_string(username, MAX_COMMAND, file, " \t"); Debug(DPARS, ("load_entry()...got %s\n",username)) if (ch == EOF) { ecode = e_cmd; goto eof; } /* need to have consumed blanks when checking options below */ Skip_Blanks(ch, file) unget_char(ch, file); #ifdef LOGIN_CAP if ((s = strrchr(username, '/')) != NULL) { *s = '\0'; e->class = strdup(s + 1); if (e->class == NULL) warn("strdup(\"%s\")", s + 1); } else { e->class = strdup(RESOURCE_RC); if (e->class == NULL) warn("strdup(\"%s\")", RESOURCE_RC); } if (e->class == NULL) { ecode = e_mem; goto eof; } if ((lc = login_getclass(e->class)) == NULL) { ecode = e_class; goto eof; } login_close(lc); #endif grp = NULL; if ((s = strrchr(username, ':')) != NULL) { *s = '\0'; if ((grp = getgrnam(s + 1)) == NULL) { ecode = e_group; goto eof; } } pw = getpwnam(username); if (pw == NULL) { ecode = e_username; goto eof; } if (grp != NULL) pw->pw_gid = grp->gr_gid; Debug(DPARS, ("load_entry()...uid %d, gid %d\n",pw->pw_uid,pw->pw_gid)) #ifdef LOGIN_CAP Debug(DPARS, ("load_entry()...class %s\n",e->class)) #endif } #ifndef PAM /* PAM takes care of account expiration by itself */ if (pw->pw_expire && time(NULL) >= pw->pw_expire) { ecode = e_username; goto eof; } #endif /* !PAM */ e->uid = pw->pw_uid; e->gid = pw->pw_gid; /* copy and fix up environment. some variables are just defaults and * others are overrides; we process only the overrides here, defaults * are handled in do_command after login.conf is processed. */ e->envp = env_copy(envp); if (e->envp == NULL) { warn("env_copy"); ecode = e_mem; goto eof; } if (!env_get("SHELL", e->envp)) { prev_env = e->envp; sprintf(envstr, "SHELL=%s", _PATH_BSHELL); e->envp = env_set(e->envp, envstr); if (e->envp == NULL) { warn("env_set(%s)", envstr); env_free(prev_env); ecode = e_mem; goto eof; } } /* If LOGIN_CAP, this is deferred to do_command where the login class * is processed. If !LOGIN_CAP, do it here. */ #ifndef LOGIN_CAP if (!env_get("HOME", e->envp)) { prev_env = e->envp; sprintf(envstr, "HOME=%s", pw->pw_dir); e->envp = env_set(e->envp, envstr); if (e->envp == NULL) { warn("env_set(%s)", envstr); env_free(prev_env); ecode = e_mem; goto eof; } } #endif prev_env = e->envp; sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name); e->envp = env_set(e->envp, envstr); if (e->envp == NULL) { warn("env_set(%s)", envstr); env_free(prev_env); ecode = e_mem; goto eof; } #if defined(BSD) prev_env = e->envp; sprintf(envstr, "%s=%s", "USER", pw->pw_name); e->envp = env_set(e->envp, envstr); if (e->envp == NULL) { warn("env_set(%s)", envstr); env_free(prev_env); ecode = e_mem; goto eof; } #endif Debug(DPARS, ("load_entry()...checking for command options\n")) ch = get_char(file); while (ch == '-') { Debug(DPARS|DEXT, ("load_entry()...expecting option\n")) switch (ch = get_char(file)) { case 'n': Debug(DPARS|DEXT, ("load_entry()...got MAIL_WHEN_ERR ('n') option\n")) /* only allow the user to set the option once */ if ((e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR) { Debug(DPARS|DEXT, ("load_entry()...duplicate MAIL_WHEN_ERR ('n') option\n")) ecode = e_option; goto eof; } e->flags |= MAIL_WHEN_ERR; break; case 'q': Debug(DPARS|DEXT, ("load_entry()...got DONT_LOG ('q') option\n")) /* only allow the user to set the option once */ if ((e->flags & DONT_LOG) == DONT_LOG) { Debug(DPARS|DEXT, ("load_entry()...duplicate DONT_LOG ('q') option\n")) ecode = e_option; goto eof; } e->flags |= DONT_LOG; break; default: Debug(DPARS|DEXT, ("load_entry()...invalid option '%c'\n", ch)) ecode = e_option; goto eof; } ch = get_char(file); if (ch!='\t' && ch!=' ') { ecode = e_option; goto eof; } Skip_Blanks(ch, file) if (ch == EOF || ch == '\n') { ecode = e_cmd; goto eof; } } unget_char(ch, file); Debug(DPARS, ("load_entry()...about to parse command\n")) /* Everything up to the next \n or EOF is part of the command... * too bad we don't know in advance how long it will be, since we * need to malloc a string for it... so, we limit it to MAX_COMMAND. * XXX - should use realloc(). */ ch = get_string(cmd, MAX_COMMAND, file, "\n"); /* a file without a \n before the EOF is rude, so we'll complain... */ if (ch == EOF) { ecode = e_cmd; goto eof; } /* got the command in the 'cmd' string; save it in *e. */ e->cmd = strdup(cmd); if (e->cmd == NULL) { warn("strdup(\"%s\")", cmd); ecode = e_mem; goto eof; } Debug(DPARS, ("load_entry()...returning successfully\n")) /* success, fini, return pointer to the entry we just created... */ return e; eof: free_entry(e); if (ecode != e_none && error_func) (*error_func)(ecodes[(int)ecode]); while (ch != EOF && ch != '\n') ch = get_char(file); return NULL; } static char -get_list(bits, low, high, names, ch, file) - bitstr_t *bits; /* one bit per flag, default=FALSE */ - int low, high; /* bounds, impl. offset for bitstr */ - char *names[]; /* NULL or *[] of names for these elements */ - int ch; /* current character being processed */ - FILE *file; /* file being read */ +get_list(bitstr_t *bits, int low, int high, char *names[], int ch, FILE *file) { register int done; /* we know that we point to a non-blank character here; * must do a Skip_Blanks before we exit, so that the * next call (or the code that picks up the cmd) can * assume the same thing. */ Debug(DPARS|DEXT, ("get_list()...entered\n")) /* list = range {"," range} */ /* clear the bit string, since the default is 'off'. */ bit_nclear(bits, 0, (high-low+1)); /* process all ranges */ done = FALSE; while (!done) { ch = get_range(bits, low, high, names, ch, file); if (ch == ',') ch = get_char(file); else done = TRUE; } /* exiting. skip to some blanks, then skip over the blanks. */ Skip_Nonblanks(ch, file) Skip_Blanks(ch, file) Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)) return ch; } static char -get_range(bits, low, high, names, ch, file) - bitstr_t *bits; /* one bit per flag, default=FALSE */ - int low, high; /* bounds, impl. offset for bitstr */ - char *names[]; /* NULL or names of elements */ - int ch; /* current character being processed */ - FILE *file; /* file being read */ +get_range(bitstr_t *bits, int low, int high, char *names[], int ch, FILE *file) { /* range = number | number "-" number [ "/" number ] */ register int i; auto int num1, num2, num3; Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")) if (ch == '*') { /* '*' means "first-last" but can still be modified by /step */ num1 = low; num2 = high; ch = get_char(file); if (ch == EOF) return EOF; } else { if (EOF == (ch = get_number(&num1, low, names, ch, file))) return EOF; if (ch == '/') num2 = high; else if (ch != '-') { /* not a range, it's a single number. */ if (EOF == set_element(bits, low, high, num1)) return EOF; return ch; } else { /* eat the dash */ ch = get_char(file); if (ch == EOF) return EOF; /* get the number following the dash */ ch = get_number(&num2, low, names, ch, file); if (ch == EOF) return EOF; } } /* check for step size */ if (ch == '/') { /* eat the slash */ ch = get_char(file); if (ch == EOF) return EOF; /* get the step size -- note: we don't pass the * names here, because the number is not an * element id, it's a step size. 'low' is * sent as a 0 since there is no offset either. */ ch = get_number(&num3, 0, PPC_NULL, ch, file); if (ch == EOF || num3 == 0) return EOF; } else { /* no step. default==1. */ num3 = 1; } /* range. set all elements from num1 to num2, stepping * by num3. (the step is a downward-compatible extension * proposed conceptually by bob@acornrc, syntactically * designed then implmented by paul vixie). */ for (i = num1; i <= num2; i += num3) if (EOF == set_element(bits, low, high, i)) return EOF; return ch; } static char -get_number(numptr, low, names, ch, file) - int *numptr; /* where does the result go? */ - int low; /* offset applied to result if symbolic enum used */ - char *names[]; /* symbolic names, if any, for enums */ - int ch; /* current character */ - FILE *file; /* source */ +get_number(int *numptr, int low, char *names[], int ch, FILE *file) { char temp[MAX_TEMPSTR], *pc; int len, i, all_digits; /* collect alphanumerics into our fixed-size temp array */ pc = temp; len = 0; all_digits = TRUE; while (isalnum(ch)) { if (++len >= MAX_TEMPSTR) return EOF; *pc++ = ch; if (!isdigit(ch)) all_digits = FALSE; ch = get_char(file); } *pc = '\0'; if (len == 0) return (EOF); /* try to find the name in the name list */ if (names) { for (i = 0; names[i] != NULL; i++) { Debug(DPARS|DEXT, ("get_num, compare(%s,%s)\n", names[i], temp)) if (!strcasecmp(names[i], temp)) { *numptr = i+low; return ch; } } } /* no name list specified, or there is one and our string isn't * in it. either way: if it's all digits, use its magnitude. * otherwise, it's an error. */ if (all_digits) { *numptr = atoi(temp); return ch; } return EOF; } static int -set_element(bits, low, high, number) - bitstr_t *bits; /* one bit per flag, default=FALSE */ - int low; - int high; - int number; +set_element(bitstr_t *bits, int low, int high, int number) { Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) if (number < low || number > high) return EOF; bit_set(bits, (number-low)); return OK; } diff --git a/usr.sbin/cron/lib/env.c b/usr.sbin/cron/lib/env.c index fd358176b124..77fbdf0c2627 100644 --- a/usr.sbin/cron/lib/env.c +++ b/usr.sbin/cron/lib/env.c @@ -1,269 +1,261 @@ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie uunet!decwrl!vixie!paul */ #if !defined(lint) && !defined(LINT) static const char rcsid[] = "$FreeBSD$"; #endif #include "cron.h" char ** -env_init() +env_init(void) { register char **p = (char **) malloc(sizeof(char *)); if (p) p[0] = NULL; return (p); } void -env_free(envp) - char **envp; +env_free(char **envp) { char **p; if ((p = envp)) for (; *p; p++) free(*p); free(envp); } char ** -env_copy(envp) - register char **envp; +env_copy(char **envp) { register int count, i; register char **p; for (count = 0; envp[count] != NULL; count++) ; p = (char **) malloc((count+1) * sizeof(char *)); /* 1 for the NULL */ if (p == NULL) { errno = ENOMEM; return NULL; } for (i = 0; i < count; i++) if ((p[i] = strdup(envp[i])) == NULL) { while (--i >= 0) (void) free(p[i]); free(p); errno = ENOMEM; return NULL; } p[count] = NULL; return (p); } char ** -env_set(envp, envstr) - char **envp; - char *envstr; +env_set(char **envp, char *envstr) { register int count, found; register char **p; char *q; /* * count the number of elements, including the null pointer; * also set 'found' to -1 or index of entry if already in here. */ found = -1; for (count = 0; envp[count] != NULL; count++) { if (!strcmp_until(envp[count], envstr, '=')) found = count; } count++; /* for the NULL */ if (found != -1) { /* * it exists already, so just free the existing setting, * save our new one there, and return the existing array. */ q = envp[found]; if ((envp[found] = strdup(envstr)) == NULL) { envp[found] = q; /* XXX env_free(envp); */ errno = ENOMEM; return NULL; } free(q); return (envp); } /* * it doesn't exist yet, so resize the array, move null pointer over * one, save our string over the old null pointer, and return resized * array. */ p = (char **) realloc((void *) envp, (unsigned) ((count+1) * sizeof(char *))); if (p == NULL) { /* XXX env_free(envp); */ errno = ENOMEM; return NULL; } p[count] = p[count-1]; if ((p[count-1] = strdup(envstr)) == NULL) { env_free(p); errno = ENOMEM; return NULL; } return (p); } /* return ERR = end of file * FALSE = not an env setting (file was repositioned) * TRUE = was an env setting */ int -load_env(envstr, f) - char *envstr; - FILE *f; +load_env(char *envstr, FILE *f) { long filepos; int fileline; char name[MAX_ENVSTR], val[MAX_ENVSTR]; char quotechar, *c, *str; int state; /* The following states are traversed in order: */ #define NAMEI 0 /* First char of NAME, may be quote */ #define NAME 1 /* Subsequent chars of NAME */ #define EQ1 2 /* After end of name, looking for '=' sign */ #define EQ2 3 /* After '=', skipping whitespace */ #define VALUEI 4 /* First char of VALUE, may be quote */ #define VALUE 5 /* Subsequent chars of VALUE */ #define FINI 6 /* All done, skipping trailing whitespace */ #define ERROR 7 /* Error */ filepos = ftell(f); fileline = LineNumber; skip_comments(f); if (EOF == get_string(envstr, MAX_ENVSTR, f, "\n")) return (ERR); Debug(DPARS, ("load_env, read <%s>\n", envstr)); bzero (name, sizeof name); bzero (val, sizeof val); str = name; state = NAMEI; quotechar = '\0'; c = envstr; while (state != ERROR && *c) { switch (state) { case NAMEI: case VALUEI: if (*c == '\'' || *c == '"') quotechar = *c++; ++state; /* FALLTHROUGH */ case NAME: case VALUE: if (quotechar) { if (*c == quotechar) { state++; c++; break; } if (state == NAME && *c == '=') { state = ERROR; break; } } else { if (state == NAME) { if (isspace (*c)) { c++; state++; break; } if (*c == '=') { state++; break; } } } *str++ = *c++; break; case EQ1: if (*c == '=') { state++; str = val; quotechar = '\0'; } else { if (!isspace (*c)) state = ERROR; } c++; break; case EQ2: case FINI: if (isspace (*c)) c++; else state++; break; } } if (state != FINI && !(state == VALUE && !quotechar)) { Debug(DPARS, ("load_env, parse error, state = %d\n", state)) fseek(f, filepos, 0); Set_LineNum(fileline); return (FALSE); } if (state == VALUE) { /* End of unquoted value: trim trailing whitespace */ c = val + strlen (val); while (c > val && isspace (*(c - 1))) *(--c) = '\0'; } /* 2 fields from parser; looks like an env setting */ if (strlen(name) + 1 + strlen(val) >= MAX_ENVSTR-1) return (FALSE); (void) sprintf(envstr, "%s=%s", name, val); Debug(DPARS, ("load_env, <%s> <%s> -> <%s>\n", name, val, envstr)) return (TRUE); } char * -env_get(name, envp) - register char *name; - register char **envp; +env_get(char *name, char **envp) { register int len = strlen(name); register char *p, *q; while ((p = *envp++)) { if (!(q = strchr(p, '='))) continue; if ((q - p) == len && !strncmp(p, name, len)) return (q+1); } return (NULL); } diff --git a/usr.sbin/cron/lib/misc.c b/usr.sbin/cron/lib/misc.c index 6a0b8acbd742..5746bc33ca51 100644 --- a/usr.sbin/cron/lib/misc.c +++ b/usr.sbin/cron/lib/misc.c @@ -1,596 +1,578 @@ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Paul Vixie uunet!decwrl!vixie!paul */ #if !defined(lint) && !defined(LINT) static const char rcsid[] = "$FreeBSD$"; #endif /* vix 26jan87 [RCS has the rest of the log] * vix 30dec86 [written] */ #include "cron.h" #if SYS_TIME_H # include #else # include #endif #include #include #include #include #include #include #if defined(SYSLOG) # include #endif #if defined(LOG_DAEMON) && !defined(LOG_CRON) #define LOG_CRON LOG_DAEMON #endif static int LogFD = ERR; int -strcmp_until(left, right, until) - char *left; - char *right; - int until; +strcmp_until(char *left, char *right, int until) { register int diff; while (*left && *left != until && *left == *right) { left++; right++; } if ((*left=='\0' || *left == until) && (*right=='\0' || *right == until)) { diff = 0; } else { diff = *left - *right; } return diff; } /* strdtb(s) - delete trailing blanks in string 's' and return new length */ int -strdtb(s) - char *s; +strdtb(char *s) { char *x = s; /* scan forward to the null */ while (*x) x++; /* scan backward to either the first character before the string, * or the last non-blank in the string, whichever comes first. */ do {x--;} while (x >= s && isspace(*x)); /* one character beyond where we stopped above is where the null * goes. */ *++x = '\0'; /* the difference between the position of the null character and * the position of the first character of the string is the length. */ return x - s; } int -set_debug_flags(flags) - char *flags; +set_debug_flags(char *flags) { /* debug flags are of the form flag[,flag ...] * * if an error occurs, print a message to stdout and return FALSE. * otherwise return TRUE after setting ERROR_FLAGS. */ #if !DEBUGGING printf("this program was compiled without debugging enabled\n"); return FALSE; #else /* DEBUGGING */ char *pc = flags; DebugFlags = 0; while (*pc) { char **test; int mask; /* try to find debug flag name in our list. */ for ( test = DebugFlagNames, mask = 1; *test && strcmp_until(*test, pc, ','); test++, mask <<= 1 ) ; if (!*test) { fprintf(stderr, "unrecognized debug flag <%s> <%s>\n", flags, pc); return FALSE; } DebugFlags |= mask; /* skip to the next flag */ while (*pc && *pc != ',') pc++; if (*pc == ',') pc++; } if (DebugFlags) { int flag; fprintf(stderr, "debug flags enabled:"); for (flag = 0; DebugFlagNames[flag]; flag++) if (DebugFlags & (1 << flag)) fprintf(stderr, " %s", DebugFlagNames[flag]); fprintf(stderr, "\n"); } return TRUE; #endif /* DEBUGGING */ } void -set_cron_uid() +set_cron_uid(void) { #if defined(BSD) || defined(POSIX) if (seteuid(ROOT_UID) < OK) err(ERROR_EXIT, "seteuid"); #else if (setuid(ROOT_UID) < OK) err(ERROR_EXIT, "setuid"); #endif } void -set_cron_cwd() +set_cron_cwd(void) { struct stat sb; /* first check for CRONDIR ("/var/cron" or some such) */ if (stat(CRONDIR, &sb) < OK && errno == ENOENT) { warn("%s", CRONDIR); if (OK == mkdir(CRONDIR, 0700)) { warnx("%s: created", CRONDIR); stat(CRONDIR, &sb); } else { err(ERROR_EXIT, "%s: mkdir", CRONDIR); } } if (!(sb.st_mode & S_IFDIR)) err(ERROR_EXIT, "'%s' is not a directory, bailing out", CRONDIR); if (chdir(CRONDIR) < OK) err(ERROR_EXIT, "cannot chdir(%s), bailing out", CRONDIR); /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such) */ if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) { warn("%s", SPOOL_DIR); if (OK == mkdir(SPOOL_DIR, 0700)) { warnx("%s: created", SPOOL_DIR); stat(SPOOL_DIR, &sb); } else { err(ERROR_EXIT, "%s: mkdir", SPOOL_DIR); } } if (!(sb.st_mode & S_IFDIR)) err(ERROR_EXIT, "'%s' is not a directory, bailing out", SPOOL_DIR); } /* get_char(file) : like getc() but increment LineNumber on newlines */ int -get_char(file) - FILE *file; +get_char(FILE *file) { int ch; ch = getc(file); if (ch == '\n') Set_LineNum(LineNumber + 1) return ch; } /* unget_char(ch, file) : like ungetc but do LineNumber processing */ void -unget_char(ch, file) - int ch; - FILE *file; +unget_char(int ch, FILE *file) { ungetc(ch, file); if (ch == '\n') Set_LineNum(LineNumber - 1) } /* get_string(str, max, file, termstr) : like fgets() but * (1) has terminator string which should include \n * (2) will always leave room for the null * (3) uses get_char() so LineNumber will be accurate * (4) returns EOF or terminating character, whichever */ int -get_string(string, size, file, terms) - char *string; - int size; - FILE *file; - char *terms; +get_string(char *string, int size, FILE *file, char *terms) { int ch; while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) { if (size > 1) { *string++ = (char) ch; size--; } } if (size > 0) *string = '\0'; return ch; } /* skip_comments(file) : read past comment (if any) */ void -skip_comments(file) - FILE *file; +skip_comments(FILE *file) { int ch; while (EOF != (ch = get_char(file))) { /* ch is now the first character of a line. */ while (ch == ' ' || ch == '\t') ch = get_char(file); if (ch == EOF) break; /* ch is now the first non-blank character of a line. */ if (ch != '\n' && ch != '#') break; /* ch must be a newline or comment as first non-blank * character on a line. */ while (ch != '\n' && ch != EOF) ch = get_char(file); /* ch is now the newline of a line which we're going to * ignore. */ } if (ch != EOF) unget_char(ch, file); } /* int in_file(char *string, FILE *file) * return TRUE if one of the lines in file matches string exactly, * FALSE otherwise. */ static int in_file(char *string, FILE *file) { char line[MAX_TEMPSTR]; rewind(file); while (fgets(line, MAX_TEMPSTR, file)) { if (line[0] != '\0') if (line[strlen(line)-1] == '\n') line[strlen(line)-1] = '\0'; if (0 == strcmp(line, string)) return TRUE; } return FALSE; } /* int allowed(char *username) * returns TRUE if (ALLOW_FILE exists and user is listed) * or (DENY_FILE exists and user is NOT listed) * or (neither file exists but user=="root" so it's okay) */ int -allowed(username) - char *username; +allowed(char *username) { FILE *allow, *deny; int isallowed; isallowed = FALSE; deny = NULL; #if defined(ALLOW_FILE) && defined(DENY_FILE) if ((allow = fopen(ALLOW_FILE, "r")) == NULL && errno != ENOENT) goto out; if ((deny = fopen(DENY_FILE, "r")) == NULL && errno != ENOENT) goto out; Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny)) #else allow = NULL; #endif if (allow) isallowed = in_file(username, allow); else if (deny) isallowed = !in_file(username, deny); else { #if defined(ALLOW_ONLY_ROOT) isallowed = (strcmp(username, ROOT_USER) == 0); #else isallowed = TRUE; #endif } out: if (allow) fclose(allow); if (deny) fclose(deny); return (isallowed); } void log_it(char *username, int xpid, char *event, const char *detail) { #if defined(LOG_FILE) || DEBUGGING PID_T pid = xpid; #endif #if defined(LOG_FILE) char *msg; TIME_T now = time((TIME_T) 0); register struct tm *t = localtime(&now); #endif /*LOG_FILE*/ #if defined(SYSLOG) static int syslog_open = 0; #endif #if defined(LOG_FILE) /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation. */ msg = malloc(strlen(username) + strlen(event) + strlen(detail) + MAX_TEMPSTR); if (msg == NULL) warnx("failed to allocate memory for log message"); else { if (LogFD < OK) { LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600); if (LogFD < OK) { warn("can't open log file %s", LOG_FILE); } else { (void) fcntl(LogFD, F_SETFD, 1); } } /* we have to sprintf() it because fprintf() doesn't always * write everything out in one chunk and this has to be * atomically appended to the log file. */ sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n", username, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid, event, detail); /* we have to run strlen() because sprintf() returns (char*) * on old BSD. */ if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) { if (LogFD >= OK) warn("%s", LOG_FILE); warnx("can't write to log file"); write(STDERR, msg, strlen(msg)); } free(msg); } #endif /*LOG_FILE*/ #if defined(SYSLOG) if (!syslog_open) { /* we don't use LOG_PID since the pid passed to us by * our client may not be our own. therefore we want to * print the pid ourselves. */ # ifdef LOG_DAEMON openlog(ProgramName, LOG_PID, LOG_CRON); # else openlog(ProgramName, LOG_PID); # endif syslog_open = TRUE; /* assume openlog success */ } syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail); #endif /*SYSLOG*/ #if DEBUGGING if (DebugFlags) { fprintf(stderr, "log_it: (%s %d) %s (%s)\n", username, pid, event, detail); } #endif } void -log_close() { +log_close(void) +{ if (LogFD != ERR) { close(LogFD); LogFD = ERR; } } /* two warnings: * (1) this routine is fairly slow * (2) it returns a pointer to static storage */ char * -first_word(s, t) - register char *s; /* string we want the first word of */ - register char *t; /* terminators, implicitly including \0 */ +first_word(char *s, char *t) { static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */ static int retsel = 0; register char *rb, *rp; /* select a return buffer */ retsel = 1-retsel; rb = &retbuf[retsel][0]; rp = rb; /* skip any leading terminators */ while (*s && (NULL != strchr(t, *s))) { s++; } /* copy until next terminator or full buffer */ while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) { *rp++ = *s++; } /* finish the return-string and return it */ *rp = '\0'; return rb; } /* warning: * heavily ascii-dependent. */ static void mkprint(register char *dst, register unsigned char *src, register int len) { while (len-- > 0) { register unsigned char ch = *src++; if (ch < ' ') { /* control character */ *dst++ = '^'; *dst++ = ch + '@'; } else if (ch < 0177) { /* printable */ *dst++ = ch; } else if (ch == 0177) { /* delete/rubout */ *dst++ = '^'; *dst++ = '?'; } else { /* parity character */ sprintf(dst, "\\%03o", ch); dst += 4; } } *dst = '\0'; } /* warning: * returns a pointer to malloc'd storage, you must call free yourself. */ char * -mkprints(src, len) - register unsigned char *src; - register unsigned int len; +mkprints(unsigned char *src, unsigned int len) { register char *dst = malloc(len*4 + 1); if (dst != NULL) mkprint(dst, src, len); return dst; } #ifdef MAIL_DATE /* Sat, 27 Feb 93 11:44:51 CST * 123456789012345678901234567 */ char * -arpadate(clock) - time_t *clock; +arpadate(time_t *clock) { time_t t = clock ?*clock :time(0L); struct tm *tm = localtime(&t); static char ret[32]; /* zone name might be >3 chars */ if (tm->tm_year >= 100) tm->tm_year += 1900; (void) snprintf(ret, sizeof(ret), "%s, %2d %s %d %02d:%02d:%02d %s", DowNames[tm->tm_wday], tm->tm_mday, MonthNames[tm->tm_mon], tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec, TZONE(*tm)); return ret; } #endif /*MAIL_DATE*/ #ifdef HAVE_SAVED_UIDS static int save_euid; -int swap_uids() { save_euid = geteuid(); return seteuid(getuid()); } -int swap_uids_back() { return seteuid(save_euid); } +int swap_uids(void) { save_euid = geteuid(); return seteuid(getuid()); } +int swap_uids_back(void) { return seteuid(save_euid); } #else /*HAVE_SAVED_UIDS*/ -int swap_uids() { return setreuid(geteuid(), getuid()); } -int swap_uids_back() { return swap_uids(); } +int swap_uids(void) { return setreuid(geteuid(), getuid()); } +int swap_uids_back(void) { return swap_uids(); } #endif /*HAVE_SAVED_UIDS*/