1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-28 11:57:28 +00:00

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
This commit is contained in:
Yaroslav Tykhiy 2007-06-17 17:25:53 +00:00
parent 5e521fcd2e
commit 997c6eefd8
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=170890
9 changed files with 90 additions and 8 deletions

View File

@ -4,6 +4,7 @@ NO_OBJ=
FILES= README \
atrun \
cron \
ftpd \
gdm \
imap \

9
etc/pam.d/cron Normal file
View File

@ -0,0 +1,9 @@
#
# $FreeBSD$
#
# PAM configuration for the "cron" service
#
# account
account required pam_nologin.so
account required pam_unix.so

View File

@ -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 <bsd.prog.mk>

View File

@ -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

View File

@ -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.

View File

@ -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");

View File

@ -32,6 +32,10 @@ static const char rcsid[] =
#if defined(LOGIN_CAP)
# include <login_cap.h>
#endif
#ifdef PAM
# include <security/pam_appl.h>
# include <security/openpam.h>
#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

View File

@ -5,6 +5,6 @@ INTERNALLIB=
SRCS= entry.c env.c misc.c
CFLAGS+= -I${.CURDIR}/../cron
CFLAGS+= -DLOGIN_CAP
CFLAGS+= -DLOGIN_CAP -DPAM
.include <bsd.lib.mk>

View File

@ -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;