From 997c6eefd8ed7f0f81862dc3727d17fd23046629 Mon Sep 17 00:00:00 2001 From: Yaroslav Tykhiy Date: Sun, 17 Jun 2007 17:25:53 +0000 Subject: Add PAM support to cron(8). Now cron(8) will skip commands scheduled by unavailable accounts, e.g., those locked, expired, not allowed in at the moment by nologin(5), or whatever, depending on cron's pam.conf(5). This applies to personal crontabs only, /etc/crontab is unaffected. In other words, now the account management policy will apply to commands scheduled by users via crontab(1) so that a user can no longer use cron(8) to set up a delayed backdoor and run commands during periods when the admin doesn't want him to. The PAM check is done just before running a command, not when loading a crontab, because accounts can get locked, expired, and re-enabled any time with no changes to their crontabs. E.g., imagine that you provide a system with payed access, or better a cluster of such systems with centralized account management via PAM. When a user pays for some days of access, you set his expire field respectively. If the account expires before its owner pays more, its crontab commands won't run until the next payment is made. Then it'll be enough to set the expire field in future for the commands to run again. And so on. Document this change in the cron(8) manpage, which includes adding a FILES section and touching the document date. X-Security: should benefit as users have access to cron(8) by default --- usr.sbin/cron/cron/Makefile | 6 +++--- usr.sbin/cron/cron/cron.8 | 27 ++++++++++++++++++++++-- usr.sbin/cron/cron/cron.h | 1 + usr.sbin/cron/cron/database.c | 4 ++-- usr.sbin/cron/cron/do_command.c | 46 +++++++++++++++++++++++++++++++++++++++++ usr.sbin/cron/lib/Makefile | 2 +- usr.sbin/cron/lib/entry.c | 2 ++ 7 files changed, 80 insertions(+), 8 deletions(-) (limited to 'usr.sbin/cron') diff --git a/usr.sbin/cron/cron/Makefile b/usr.sbin/cron/cron/Makefile index d578da72daa5..92d3ee4ab18d 100644 --- a/usr.sbin/cron/cron/Makefile +++ b/usr.sbin/cron/cron/Makefile @@ -4,9 +4,9 @@ PROG= cron MAN= cron.8 SRCS= cron.c database.c do_command.c job.c user.c popen.c -CFLAGS+= -DLOGIN_CAP +CFLAGS+= -DLOGIN_CAP -DPAM -DPADD= ${LIBCRON} ${LIBUTIL} -LDADD= ${LIBCRON} -lutil +DPADD= ${LIBCRON} ${LIBPAM} ${LIBUTIL} +LDADD= ${LIBCRON} -lpam -lutil .include diff --git a/usr.sbin/cron/cron/cron.8 b/usr.sbin/cron/cron/cron.8 index eba639d81ffa..b0d1a9136ebd 100644 --- a/usr.sbin/cron/cron/cron.8 +++ b/usr.sbin/cron/cron/cron.8 @@ -17,7 +17,7 @@ .\" .\" $FreeBSD$ .\" -.Dd December 20, 1993 +.Dd June 17, 2007 .Dt CRON 8 .Os .Sh NAME @@ -53,11 +53,21 @@ utility also searches for .Pa /etc/crontab which is in a different format (see .Xr crontab 5 ) . +.Pp The .Nm utility then wakes up every minute, examining all stored crontabs, checking each command to see if it should be run in the current minute. +Before running a command from a per-account crontab file, +.Nm +checks the status of the account with +.Xr pam 3 +and skips the command if the account is unavailable, +e.g., locked out or expired. +Commands from +.Pa /etc/crontab +bypass this check. When executing commands, any output is mailed to the owner of the crontab (or to the user named in the @@ -173,8 +183,21 @@ be verbose when iterating through the scheduling algorithms trace through the execution, but do not perform any actions .El .El +.Sh FILES +.Bl -tag -width /etc/pam.d/cron -compact +.It Pa /etc/crontab +System crontab file +.It Pa /etc/pam.d/cron +.Xr pam.conf 5 +configuration file for +.Nm +.It Pa /var/cron/tabs +Directory for personal crontab files +.El .Sh SEE ALSO .Xr crontab 1 , -.Xr crontab 5 +.Xr pam 3 , +.Xr crontab 5 , +.Xr pam.conf 5 .Sh AUTHORS .An Paul Vixie Aq paul@vix.com diff --git a/usr.sbin/cron/cron/cron.h b/usr.sbin/cron/cron/cron.h index 249bec9c8c1e..6277719aa721 100644 --- a/usr.sbin/cron/cron/cron.h +++ b/usr.sbin/cron/cron/cron.h @@ -76,6 +76,7 @@ #define MAX_UNAME 20 /* max length of username, should be overkill */ #define ROOT_UID 0 /* don't change this, it really must be root */ #define ROOT_USER "root" /* ditto */ +#define SYS_NAME "*system*" /* magic owner name for system crontab */ /* NOTE: these correspond to DebugFlagNames, * defined below. diff --git a/usr.sbin/cron/cron/database.c b/usr.sbin/cron/cron/database.c index 9ebf75d83999..6c364c907074 100644 --- a/usr.sbin/cron/cron/database.c +++ b/usr.sbin/cron/cron/database.c @@ -87,7 +87,7 @@ load_database(old_db) new_db.head = new_db.tail = NULL; if (syscron_stat.st_mtime) { - process_crontab("root", "*system*", + process_crontab("root", SYS_NAME, SYSCRONTAB, &syscron_stat, &new_db, old_db); } @@ -205,7 +205,7 @@ process_crontab(uname, fname, tabname, statbuf, new_db, old_db) int crontab_fd = OK - 1; user *u; - if (strcmp(fname, "*system*") && !(pw = getpwnam(uname))) { + 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"); diff --git a/usr.sbin/cron/cron/do_command.c b/usr.sbin/cron/cron/do_command.c index cb0bf02c7269..afc8317cde24 100644 --- a/usr.sbin/cron/cron/do_command.c +++ b/usr.sbin/cron/cron/do_command.c @@ -32,6 +32,10 @@ static const char rcsid[] = #if defined(LOGIN_CAP) # include #endif +#ifdef PAM +# include +# include +#endif static void child_process __P((entry *, user *)), @@ -98,6 +102,48 @@ child_process(e, u) usernm = env_get("LOGNAME", e->envp); mailto = env_get("MAILTO", 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 diff --git a/usr.sbin/cron/lib/Makefile b/usr.sbin/cron/lib/Makefile index d11c511beec6..296ebd05c06b 100644 --- a/usr.sbin/cron/lib/Makefile +++ b/usr.sbin/cron/lib/Makefile @@ -5,6 +5,6 @@ INTERNALLIB= SRCS= entry.c env.c misc.c CFLAGS+= -I${.CURDIR}/../cron -CFLAGS+= -DLOGIN_CAP +CFLAGS+= -DLOGIN_CAP -DPAM .include diff --git a/usr.sbin/cron/lib/entry.c b/usr.sbin/cron/lib/entry.c index 7269152b72e1..33ace49fd0c3 100644 --- a/usr.sbin/cron/lib/entry.c +++ b/usr.sbin/cron/lib/entry.c @@ -323,10 +323,12 @@ load_entry(file, error_func, pw, envp) #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; -- cgit v1.3