cron.c revision ee169c7e77bc5d28a401dde8533cbd38afd24ae1
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Copyright 2013 Joshua M. Clulow <josh@sysmgr.org>
*
* Copyright (c) 2014 Gary Mills
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/* Copyright (c) 1987, 1988 Microsoft Corporation */
/* All Rights Reserved */
#ifdef lint
/* make lint happy */
#define __EXTENSIONS__
#endif
#include <sys/resource.h>
#include <security/pam_appl.h>
#include <alloca.h>
#include <ctype.h>
#include <deflt.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <libcontract.h>
#include <libcontract_priv.h>
#include <limits.h>
#include <locale.h>
#include <poll.h>
#include <project.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stropts.h>
#include <time.h>
#include <unistd.h>
#include <libzoneinfo.h>
#include "cron.h"
/*
* #define DEBUG
*/
#define TMPDIR "/tmp"
#define PFX "crout"
#define CUSHION 180L
#define JOBF 'j'
#define NICEF 'n'
#define USERF 'u'
#define WAITF 'w'
#define BCHAR '>'
#define ECHAR '<'
#define DEFAULT 0
#define LOAD 1
#define QBUFSIZ 80
/* Defined actions for crabort() routine */
#define NO_ACTION 000
#define REMOVE_FIFO 001
#define CONSOLE_MSG 002
#define BADCD "can't change directory to the crontab directory."
#define NOREADDIR "can't read the crontab directory."
#define BADJOBOPEN "unable to read your at job."
#define BADSHELL "because your login shell \
#define BADSTAT "can't access your crontab or at-job file. Resubmit it."
#define BADPROJID "can't set project id for your job."
#define CANTCDHOME "can't change directory to %s.\
\nYour commands will not be executed."
#define CANTEXECSH "unable to exec the shell, %s, for one of your \
commands."
sizeof (CANTEXECSH) : sizeof (CANTCDHOME))
#define NOREAD "can't read your crontab file. Resubmit it."
#define BADTYPE "crontab or at-job file is not a regular file.\n"
#define NOSTDIN "unable to create a standard input file for \
one of your crontab commands. \
\nThat command was not executed."
#define NOTALLOWED "you are not authorized to use cron. Sorry."
#define STDERRMSG "\n\n********************************************\
*****\nCron: The previous message is the \
standard output and standard error \
\nof one of your cron commands.\n"
#define STDOUTERR "one of your commands generated output or errors, \
but cron was unable to mail you this output.\
\nRemember to redirect standard output and standard \
error for each of your commands."
#define CLOCK_DRIFT "clock time drifted backwards after event!\n"
#define PIDERR "unexpected pid returned %d (ignored)"
#define CRONTABERR "Subject: Your crontab file has an error in it\n\n"
#define MALLOCERR "out of space, cannot create new string\n"
#define LINELIMIT 80
#define ERR_CRONTABENT 0 /* error in crontab file entry */
#define PROJECT "project="
#define FORMAT "%a %b %e %H:%M:%S %Y"
static char timebuf[80];
struct shared {
int count; /* usage count */
void *obj; /* object */
};
struct event {
short etype; /* what type of event; 0=cron, 1=at */
char *cmd; /* command for cron, job name for at */
struct usr *u; /* ptr to the owner (usr) of this event */
union {
struct { /* for crontab events */
char *minute; /* (these */
char *hour; /* fields */
char *daymon; /* are */
char *month; /* from */
char *dayweek; /* crontab) */
char *input; /* ptr to stdin */
} ct;
struct { /* for at events */
short exists; /* for revising at events */
int eventid; /* for el_remove-ing at events */
} at;
} of;
};
struct usr {
char *name; /* name of user (e.g. "root") */
char *home; /* home directory for user */
int aruncnt; /* counter for running jobs per uid */
int cruncnt; /* counter for running cron jobs per uid */
int ctid; /* for el_remove-ing crontab events */
short ctexists; /* for revising crontab events */
}; /* ptr to next user */
static struct queue
{
int njob; /* limit */
int nice; /* nice for execution */
int nwait; /* wait time to next execution attempt */
int nrun; /* number running */
}
static struct runinfo
{
short que;
char *outfile; /* file where stdout & stderr are trapped */
short jobtype; /* what type of event: 0=cron, 1=at */
char *jobname; /* command for "cron", jobname for "at" */
int mailwhendone; /* 1 = send mail even if no ouptut */
} *rthead;
static struct miscpid {
} *miscpid_head;
static char didfork = 0; /* flag to see if I'm process group leader */
static int msgfd; /* file descriptor for fifo queue */
static int delayed; /* is job being rescheduled or did it run first time */
static int cwd; /* current working directory */
/* Variables for error handling at reading crontabs. */
static char cte_intro[] = "Line(s) with errors:\n\n";
static char cte_trail1[] = "\nMax number of errors encountered.";
static char cte_trail2[] = " Evaluation of crontab aborted.\n";
static char *cte_lp; /* Next free line in cte_text */
static int cte_nvalid; /* Valid lines found */
/* user's default environment for the shell */
#define NONROOTPATH "PATH=/usr/bin:"
static char *Def_supath = NULL;
static char *envinit[] = {
};
extern char **environ;
#define DEFTZ "GMT"
static int log = 0;
static char hzname[10];
static void cronend(int);
static void thaw_handler(int);
static void child_handler(int);
static void child_sigreset(void);
static void rm_ctevents(struct usr *);
static void crabort(char *, int);
static void ignore_msg(char *, char *, struct event *);
static void parsqdef(char *);
static void defaults();
static void initialize(int);
static void quedefs(int);
static int idle(long);
static void read_dirs(int);
static void mail(char *, char *, int);
static char *next_field(int, int);
static int next_ge(int, char *);
static void free_if_unused(struct usr *);
static void del_atjob(char *, char *);
static void del_ctab(char *);
static void resched(int);
static int msg_wait(long);
static void reap_child(void);
static void miscpid_insert(pid_t);
static int miscpid_delete(pid_t);
static void contract_set_template(void);
static void contract_clear_template(void);
static void contract_abandon_latest(pid_t);
static void cte_init(void);
static void cte_add(int, char *);
static void cte_valid(void);
static int cte_istoomany(void);
static void cte_sendmail(char *);
/*
* last_time is set immediately prior to exection of an event (via ex())
* to indicate the last time an event was executed. This was (surely)
* it's original intended use.
*/
static int reset_needed; /* set to 1 when cron(1M) needs to re-initialize */
static int refresh;
/*
* BSM hooks
*/
extern void audit_cron_new_job(char *, int, void *);
extern void audit_cron_bad_user(char *);
extern void audit_cron_user_acct_expired(char *);
extern int audit_cron_create_anc_file(char *, char *, char *, uid_t);
extern int audit_cron_delete_anc_file(char *, char *);
extern int audit_cron_is_anc_name(char *);
extern int audit_cron_mode();
static int cron_conv(int, struct pam_message **,
struct pam_response **, void *);
/*
* Function to help check a user's credentials.
*/
static int verify_user_cred(struct usr *u);
/*
* Values returned by verify_user_cred and set_user_cred:
*/
#define VUC_OK 0
#define VUC_BADUSER 1
#define VUC_NOTINGROUP 2
#define VUC_EXPIRED 3
#define VUC_NEW_AUTH 4
/*
* Modes of process_anc_files function
*/
#define CRON_ANC_DELETE 1
#define CRON_ANC_CREATE 0
/*
* Functions to remove a user or job completely from the running database.
*/
static void clean_out_atjobs(struct usr *u);
static void clean_out_ctab(struct usr *u);
static void clean_out_user(struct usr *u);
static void cron_unlink(char *name);
static void process_anc_files(int);
/*
* functions in elm.c
*/
extern void el_remove(int, int);
extern int el_empty(void);
extern void *el_first(void);
extern void el_delete(void);
static int valid_entry(char *, int);
static struct usr *create_ulist(char *, int);
static void init_cronevent(char *, int);
static void init_atevent(char *, time_t, int, int);
int
{
time_t t;
struct usr *u;
/*
* reset_needed is set to 1 whenever el_add() finds out that a cron
* job is scheduled to be run before the time when cron(1M) daemon
* initialized.
* Other cases where a reset is needed is when ex() finds that the
* event to be executed is being run at the wrong time, or when idle()
* determines that time was reset.
* We immediately return to the top of the while (TRUE) loop in
* main() where the event list is cleared and rebuilt, and reset_needed
* is set back to 0.
*/
reset_needed = 0;
/*
* Only the privileged user can run this command.
*/
if (getuid() != 0)
crabort(NOTALLOWED, 0);
/* fork unless 'nofork' is specified */
(void) sleep(30);
goto begin;
}
return (0);
}
didfork++;
(void) setpgrp(); /* detach cron from console */
}
(void) umask(022);
defaults();
initialize(1);
/* setup THAW handler */
/* setup CHLD handler */
(void) sigemptyset(&defmask);
(void) sigemptyset(&sigmask);
for (;;) { /* MAIN LOOP */
reset_needed = 0;
/*
* the time was set backwards or forward or
* refresh is requested.
*/
if (refresh)
msg("re-scheduling jobs");
else
msg("time was reset, re-initializing");
el_delete();
u = uhead;
while (u != NULL) {
rm_ctevents(u);
e = u->atevents;
while (e != NULL) {
free(e);
e = e2;
}
u = u->nextusr;
}
initialize(0);
last_time = t;
/*
* reset_needed might have been set in the functions
* call path from initialize()
*/
if (reset_needed) {
continue;
}
}
t_old = t;
}
if (next_event == NULL) {
} else {
#ifdef DEBUG
#endif
}
if (ne_time > 0) {
/*
* reset_needed may be set in the functions call path
* from idle()
*/
reset_needed = 1;
continue;
}
}
msg("cannot stat QUEDEFS file");
}
/*
* reset_needed may be set in the functions call path
* from ex()
*/
reset_needed = 1;
continue;
}
switch (next_event->etype) {
case CRONEVENT:
/* add cronevent back into the main event list */
if (delayed) {
delayed = 0;
break;
}
/*
* check if time(0)< last_time. if so, then the
* system clock has gone backwards. to prevent this
* job from being started twice, we reschedule this
* job for the >>next time after last_time<<, and
* then set next_event->time to this. note that
* crontab's resolution is 1 minute.
*/
/*
* bump up to next 30 second
* increment
* 1 <= newtime <= 30
*/
/*
* get the next scheduled event,
* not the one that we just
* kicked off!
*/
next_event->time =
} else {
next_event->time =
}
#ifdef DEBUG
"pushing back cron event %s at %ld (%s)\n",
#endif
(next_event->u)->ctid)) {
case -1:
break;
case -2: /* event time lower than init time */
reset_needed = 1;
break;
}
break;
default:
/* remove at or batch job from system */
if (delayed) {
delayed = 0;
break;
}
e = (next_event->u)->atevents;
while (e != NULL) {
if (e == next_event) {
else
free(e);
break;
} else {
eprev = e;
e = e->link;
}
}
break;
}
next_event = NULL;
}
/*NOTREACHED*/
}
static void
initialize(int firstpass)
{
#ifdef DEBUG
#endif
if (firstpass) {
/* for mail(1), make sure messages come from root */
if (putenv("LOGNAME=root") != 0) {
crabort("cannot expand env variable",
}
crabort("cannot create fifo queue",
} else {
if (NOFORK) {
/* didn't fork... init(1M) is waiting */
(void) sleep(60);
}
perror("FIFO");
crabort("cannot access fifo queue",
}
} else {
if (NOFORK) {
/* didn't fork... init(1M) is waiting */
(void) sleep(60);
/*
* the wait is painful, but we don't want
* init respawning this quickly
*/
}
}
}
perror("! open");
}
/*
* read directories, create users list, and add events to the
* main event list. Only zero user list on firstpass.
*/
if (firstpass)
next_event = NULL;
if (!firstpass)
return;
/* stdout is log file */
/* log should be root-only */
/* stderr also goes to ACCTFILE */
(void) dup(1);
/* null for stdin */
}
static void
{
char *ptr;
int jobtype;
continue;
}
msg("cannot chdir to at directory");
return;
}
msg("cannot read at at directory");
return;
}
continue;
continue;
ptr++;
continue;
continue;
}
}
}
static int
{
return (0);
/* skip over ancillary file names */
if (audit_cron_is_anc_name(name))
return (0);
return (0);
}
return (0);
}
return (0);
}
}
return (1);
}
struct usr *
{
struct usr *u;
} else {
u->ctid = 0;
}
uhead = u;
return (u);
}
void
{
struct usr *u;
if (first) {
readcron(u, 0);
} else {
readcron(u, 0);
} else {
rm_ctevents(u);
readcron(u, 0);
}
}
}
void
{
struct usr *u;
if (first) {
} else {
} else {
}
}
}
static void
{
struct usr *u;
char *pname;
/* skip over ancillary file names */
if (audit_cron_is_anc_name(name))
return;
return;
}
msg("Too long path name %s - cron entries not created",
namebuf);
return;
}
} else {
}
/*
* a warning message is given by the crontab command so there is
* no need to give one here...... use this code if you only want
*/
#ifdef BOURNESHELLONLY
return;
}
#endif
return;
}
return;
}
#ifdef DEBUG
#endif
} else {
}
} else {
}
if (u->ctid == 0) {
#ifdef DEBUG
u->name);
#endif
/* user didnt have a crontab last time */
return;
}
#ifdef DEBUG
#endif
rm_ctevents(u);
}
}
/* ARGSUSED */
static void
{
char *ptr;
struct usr *u;
char *pname;
int jobtype;
return;
ptr++;
return;
/* check for audit ancillary file */
if (audit_cron_is_anc_name(name))
return;
>= sizeof (namebuf)) {
return;
}
} else {
}
return;
}
return;
}
return;
}
/*
* a warning message is given by the at command so there is no
* need to give one here......use this code if you only want
*/
#ifdef BOURNESHELLONLY
return;
}
#endif
#ifdef DEBUG
#endif
} else {
}
}
static void
{
struct event *e;
e->u = u;
u->atevents = e;
else
#ifdef DEBUG
#endif
}
}
void
{
struct event *e;
e = u->atevents;
while (e != NULL) {
break;
} else {
e = e->link;
}
}
if (e == NULL) {
#ifdef DEBUG
#endif
}
}
static int cursor; /* cursor for the above line */
static void
{
/*
* readcron reads in a crontab file for a user (u). The list of
* events for user u is built, and u->events is made to point to
* this list. Each event is also entered into the main event
* list.
*/
struct event *e;
int start;
unsigned int i;
char *pname;
int lineno = 0;
/* read the crontab file */
cte_init(); /* Init error handling */
return;
}
} else {
}
return;
}
char *tmp;
/* process a line of a crontab file */
lineno++;
if (cte_istoomany())
break;
cursor = 0;
cursor++;
continue;
}
_VTZ_ALL)) {
break;
}
rel_shared(tz);
}
continue;
}
}
}
continue;
}
}
}
continue;
}
free(e);
continue;
}
cursor++;
continue;
/* get the command to execute */
cursor++;
cursor += 2;
goto again;
}
/* see if there is any standard input */
}
} else {
}
/* set the timezone of this entry */
/* set the shell of this entry */
/* set the home of this entry */
/* have the event point to it's owner */
e->u = u;
/* insert this event at the front of this user's event list */
u->ctevents = e;
/* set the time for the first occurance of this event */
/* finally, add this event to the main event list */
case -1:
break;
case -2: /* event time lower than init time */
reset_needed = 1;
break;
}
cte_valid();
#ifdef DEBUG
#endif
}
rel_shared(tz);
}
/*
* Below are the functions for handling of errors in crontabs. Concept is to
* collect faulty lines and send one email at the end of the crontab
* evaluation. If there are erroneous lines only ((cte_nvalid == 0), evaluation
* of crontab is aborted. Otherwise reading of crontab is continued to the end
* of the file but no further error logging appears.
*/
static void
cte_init()
{
cte_nvalid = 0;
}
static void
{
int len;
char *p;
}
for (p = cte_lp; *p; p++) {
continue;
*p = '.';
}
if (cte_nvalid == 0)
}
}
}
static void
{
cte_nvalid++;
}
static int
{
/*
* Return TRUE only if all lines are faulty. So evaluation of
* a crontab is not aborted if at least one valid line was found.
*/
}
static void
cte_sendmail(char *username)
{
if (cte_free < MAILBINITFREE)
}
/*
* Send mail with error message to a user
*/
static void
{
/* mail mails a user a message. */
char *temp;
#ifdef TESTING
return;
#endif
msg("cron cannot fork\n");
return;
}
if (fork_val == 0) {
exit(0);
switch (format) {
case ERR_CRONTABENT:
"\nEntries or crontab have been ignored\n");
break;
case ERR_UNIXERR:
"The error on %s was \"%s\"\n",
break;
case ERR_CANTEXECCRON:
"Subject: Couldn't run your \"cron\" job\n\n");
break;
case ERR_CANTEXECAT:
"Subject: Couldn't run your \"at\" job\n\n");
break;
default:
break;
}
}
exit(0);
}
}
}
static char *
{
/*
* next_field returns a pointer to a string which holds the next
* field of a line of a crontab file.
* if (numbers in this field are out of range (lower..upper),
* or there is a syntax error) then
* NULL is returned, and a mail message is sent to the
* user telling him which line the error was in.
*/
char *s;
cursor++;
return (NULL);
}
cursor++;
return (NULL);
s = xmalloc(2);
(void) strcpy(s, "*");
return (s);
}
for (;;) {
return (NULL);
num = 0;
do {
return (NULL);
return (NULL);
num2 = 0;
do {
return (NULL);
}
break;
return (NULL);
return (NULL);
}
return (s);
}
/*
* modification for bugid 1104537. the second argument to next_time is
* now the value of time(2) to be used. if this is 0, then use the
* current time. otherwise, the second argument is the time from which to
* calculate things. this is useful to correct situations where you've
* gone backwards in time (I.e. the system's internal clock is correcting
* itself backwards).
*/
static time_t
{
/*
* returns the integer time for the next occurance of event e.
* the following fields have ranges as indicated:
* PRGM | min hour day of month mon day of week
* ------|-------------------------------------------------------
* cron | 0-59 0-23 1-31 1-12 0-6 (0=sunday)
* time | 0-59 0-23 1-31 0-11 0-6 (0=sunday)
* NOTE: this routine is hard to understand.
*/
int today;
int fallback;
extern int days_btwn(int, int, int, int, int, int);
if (tflag == 0) {
} else {
t = tflag;
}
ref_t = t; /* keep a copy of the reference time */
fallback = 0;
(void) localtime_r(&t, tm);
if (daylight) {
/*
* see if we will have timezone switch over, and clock will
* fall back. zone_start will hold the time when it happens
* (ie time of PST -> PDT switch over).
*/
fallback = 1;
}
}
}
m = 0;
}
/* this event must occur today */
return (0);
}
/* In case we are falling back */
if (fallback) {
/* we may need to run the job once more. */
t = zone_start;
goto recalc;
}
/*
* In case we are not in falling back period,
* calculate the time assuming the DST. If the
* time to execute the job.
*/
return (0);
}
/*
* We got a valid time.
*/
return (t1);
} else {
/*
* If the date does not match even if
* we assume the alternate timezone, then
* it must be the invalid time. eg
* 2am while switching 1:59am to 3am.
* t1 should point the time before the
* switching over as we've calculate the
* time with assuming alternate zone.
*/
t1);
} else {
/* does this really happen? */
}
if (t == (time_t)-1) {
return (0);
}
}
goto recalc;
}
/* got valid time */
return (t1);
} else {
/*
* This should never happen, but just in
* case, we fall back to the old code.
*/
} else {
}
t1 = t;
(void) localtime_r(&t, &tmp);
}
}
/*
* Job won't run today, however if we have a switch over within
* one hour and we will have one hour time drifting back in this
* period, we may need to run the job one more time if the job was
* set to run on this hour of clock.
*/
if (fallback) {
t = zone_start;
goto recalc;
}
/*
* calculate the date of the next occurance of this event, which
* will be on a different day than the current
*/
/* check monthly day specification */
/* check weekly day specification */
else
/*
* based on their respective specifications, day1, and day2 give
* the day of the month for the next occurance of this event.
*/
}
}
/* event does not occur in this month */
/* recompute day1 and day2 */
/* wd is the day of the week of the first of month mon */
else
} else { /* event occurs in this month */
else if (!carry1)
else
}
/*
* now that we have the min, hr, day, mon, yr of the next event,
* figure out what time that turns out to be.
*/
return (0);
}
/*
* mktime returns clock for the current time zone. If the
* target date was in fallback period, it needs to be adjusted
* to the time comes first.
* Suppose, we are at Jan and scheduling job at 1:30am10/26/03.
* mktime returns the time in PST, but 1:30am in PDT comes
* first. So reverse the tm_isdst, and see if we have such
*/
if (daylight) {
return (0);
}
/*
* check the clock to see which comes early.
*/
}
}
}
return (t1);
} else {
/*
* This means that the next job is scheduled to be run on the
* 1. Non existing day of the month. such as April 31th.
* 2. Feb 29th in the non-leap year.
* 3. Time gap during the DST switch over.
*/
/*
* see if we have got a specific date which
* is invalid.
*/
/* job never run */
return (0);
}
/*
* Since the day has gone invalid, we need to go to
* next month, and recalcuate the first occurrence.
* eg the cron tab such as:
* 2/31 is invalid, so the next job is 3/1.
*/
if (mon == 11) {
} else {
}
return (0);
}
/*
* ie 29th in the non-leap year. Forwarding the
* clock to Feb 29th 00:00 (March 1st), and recalculate
* the next time.
*/
return (0);
}
} else if (daylight) {
/*
* Non existing time, eg 2am PST during summer time
* switch.
* We need to get the correct isdst which we are
* swithing to, by adding time difference to make sure
* that t2 is in the zone being switched.
*/
return (0);
}
t = zone_start;
} else {
/*
* This should never happen, but fall back to the
* old code.
*/
t1 = t;
(void) localtime_r(&t, &tmp);
}
goto recalc;
}
/*NOTREACHED*/
}
static time_t
{
tzset();
tzset();
return (ret);
} else {
return (tz_next_time(e, tflag));
}
}
/*
* This returns TOD in time_t that zone switch will happen, and this
* will be called when clock fallback is about to happen.
* (ie 30minutes before the time of PST -> PDT switch. 2:00 AM PST
* will fall back to 1:00 PDT. So this function will be called only
* for the time between 1:00 AM PST and 2:00 PST(1:00 PST)).
* First goes through the common time differences to see if zone
* switch happens at those minutes later. If not, check every minutes
* until 6 hours ahead see if it happens(We might have 45minutes
* fallback).
*/
static time_t
{
int i;
return ((time_t)-1);
/* fast path */
for (i = 0; hints[i] != 0; i++) {
t1--;
return (t1 + 1);
}
}
}
/* ugly, but don't know other than this. */
return ((time_t)-1);
t += 60; /* at least one minute, I assume */
(void) localtime_r(&t, &tmp);
return (t);
}
return ((time_t)-1);
}
static time_t
{
return ((time_t)-1);
}
crabort("internal error: mktime failed",
}
return (ret);
}
#define DUMMY 100
static int
{
/*
* list is a character field as in a crontab file;
* for example: "40, 20, 50-10"
* next_ge returns the next number in the list that is
* greater than or equal to current. if no numbers of list
* are >= current, the smallest element of list is returned.
* NOTE: current must be in the appropriate range.
*/
char *ptr;
return (current);
for (;;) {
return (current);
if (n < min)
min = n;
min_gt = n;
if (*ptr == '-') {
ptr++;
return (current);
} else { /* range that wraps around */
if (current > n)
return (current);
return (current);
}
}
if (*ptr == '\0')
break;
ptr += 1;
}
return (min_gt);
else
return (min);
}
static void
free_if_unused(struct usr *u)
{
/*
* To make sure a usr structure is idle we must check that
* there are no at jobs queued for the user; the user does
* not have a crontab, and also that there are no running at
* or cron jobs (since the runinfo structure also has a
* pointer to the usr structure).
*/
#ifdef DEBUG
#endif
cur != u;
return;
}
}
else
free(u);
}
}
static void
{
struct usr *u;
return;
e = u->atevents;
while (e != NULL) {
if (next_event == e)
next_event = NULL;
else
free(e);
break;
} else {
eprev = e;
e = e->link;
}
}
free_if_unused(u);
}
static void
{
struct usr *u;
return;
rm_ctevents(u);
u->ctid = 0;
u->ctexists = 0;
free_if_unused(u);
}
static void
rm_ctevents(struct usr *u)
{
/*
* see if the next event (to be run by cron) is a cronevent
* owned by this user.
*/
if ((next_event != NULL) &&
(next_event->u == u)) {
next_event = NULL;
}
}
}
static struct usr *
{
struct usr *u;
u = uhead;
while (u != NULL) {
return (u);
u = u->nextusr;
}
return (NULL);
}
/*
* If ever a premature return is added to this function pay attention to
* free at_cmdfile and outfile plus jobname buffers of the runinfo structure.
*/
static int
{
int r;
int fd;
char mailvar[4];
char *at_cmdfile = NULL;
union {
struct {
char buf[PROJECT_BUFSZ];
char buf2[PROJECT_BUFSZ];
} p;
} bufs;
char *tmpfile;
int projflag = 0;
char *home;
char *sh;
return (0);
}
/*
* the tempnam() function uses malloc(3C) to allocate space for the
* constructed file name, and returns a pointer to this area, which
* is assigned to rp->outfile. Here rp->outfile is not overwritten.
*/
/* "cron" jobs only produce mail if there's output */
rp->mailwhendone = 0;
} else {
if (errno == ENAMETOOLONG) {
cron_unlink(e->cmd);
} else {
}
rinfo_free(rp);
return (0);
}
/*
* Skip over the first two lines.
*/
mailvar) == 1) {
/*
* Check to see if we should always send mail
* to the owner.
*/
} else {
rp->mailwhendone = 0;
}
projflag = 1;
}
}
/*
* we make sure that the system time
* hasn't drifted backwards. if it has, el_add() is now
* called, to make sure that the event queue is back in order,
* and we set the delayed flag. cron will pick up the request
* later on at the proper time.
*/
msg("clock time drifted backwards!\n");
msg("correcting cron event\n");
(next_event->u)->ctid)) {
case -1:
break;
case -2: /* event time lower than init time */
reset_needed = 1;
break;
}
} else { /* etype == ATEVENT */
msg("correcting batch event\n");
}
}
delayed++;
rinfo_free(rp);
return (0);
}
reap_child();
msg("cannot fork");
rinfo_free(rp);
resched(60);
(void) sleep(30);
return (0);
}
}
if (rfork) { /* parent process */
(e->u)->aruncnt++;
else
(e->u)->cruncnt++;
return (0);
}
/* open jobfile as stdin to shell */
if (errno == ENAMETOOLONG) {
cron_unlink(e->cmd);
} else
exit(1);
}
/*
* if setuid bit off, original owner has
* given this file to someone else
*/
exit(1);
}
exit(1);
}
if (fd != 0) {
}
/*
* retrieve the project id of the at job and convert it
* to a project name. fail if it's not a valid project
* or if the user isn't a member of the project.
*/
if (projflag == 1) {
exit(1);
}
}
}
/*
* Put process in a new session, and create a new task.
*/
if (setsid() < 0) {
msg("setsid failed with errno = %d. job failed (%s)"
exit(1);
}
/*
* set correct user identification and check his account
*/
r = set_user_cred(e->u, pproj);
if (r == VUC_EXPIRED) {
audit_cron_user_acct_expired(e->u->name);
clean_out_user(e->u);
exit(1);
}
if (r == VUC_NEW_AUTH) {
audit_cron_user_acct_expired(e->u->name);
clean_out_user(e->u);
exit(1);
}
if (r != VUC_OK) {
audit_cron_bad_user(e->u->name);
clean_out_user(e->u);
exit(1);
}
/*
* check user and initialize the supplementary group access list.
* bugid 1230784: deleted from parent to avoid cron hang. Now
* only child handles the call.
*/
if (verify_user_cred(e->u) != VUC_OK ||
msg("bad user (%s) or setgid failed (%s)",
audit_cron_bad_user(e->u->name);
clean_out_user(e->u);
exit(1);
}
if ((e->u)->uid == 0) { /* set default path */
/* path settable in defaults file */
} else {
}
} else {
}
if (r != 0) {
msg("cron audit problem. job failed (%s) for user %s",
exit(1);
}
clean_out_user(e->u);
exit(1);
}
/* check for standard input to command */
exit(1);
}
exit(1);
}
exit(1);
}
if (fd != 0) {
}
}
}
}
/* redirect stdout and stderr for the shell */
if (fd != 1)
}
} else {
}
exit(1);
}
#ifdef TESTING
exit(1);
#endif
/*
* make sure that all file descriptors EXCEPT 0, 1 and 2
* will be closed.
*/
closefrom(3);
if ((e->u)->uid != 0)
}
char *name;
else
name++;
} else {
}
} else { /* type == ATEVENT */
}
exit(1);
/*NOTREACHED*/
}
/*
* Main idle loop.
* When timed out to run the job, return 0.
* If for some reasons we need to reschedule jobs, return 1.
*/
static int
idle(long t)
{
refresh = 0;
while (t > 0L) {
if (msg_wait(t) != 0) {
/* we need to run next job immediately */
return (0);
}
reap_child();
if (refresh) {
/* We got THAW or REFRESH message */
return (1);
}
/* clock has been reset to backward */
return (1);
}
}
if (next_event == NULL)
t = INFINITY;
else
}
return (0);
}
/*
* This used to be in the idle(), but moved to the separate function.
* This called from various place when cron needs to reap the
* child. It includes the situation that cron hit maxrun, and needs
* to reschedule the job.
*/
static void
{
int prc;
for (;;) {
if (pid <= 0)
break;
#ifdef DEBUG
#endif
if (miscpid_delete(pid) == 0) {
/* not found in anywhere */
}
rinfo_free(rp);
} else {
}
}
}
static void
{
int nextfork = 1;
struct usr *p;
--p->aruncnt;
else
--p->cruncnt;
/* mail user stdout and stderr */
for (;;) {
/*
* if fork fails try forever in doubling
* retry times, up to 16 seconds
*/
if (nextfork < 16)
continue;
/* NOTREACHED */
} else {
break;
}
}
} else {
rinfo_free(pr);
}
} else {
rinfo_free(pr);
}
free_if_unused(p);
}
/*
* Mail stdout and stderr of a job to user. Get uid for real user and become
* that person. We do this so that mail won't come from root since this
* could be a security hole. If failure, quit - don't send mail as root.
*/
static void
{
int nbytes;
char *cmd;
exit(0);
exit(127);
/*
* RFC3834 (Section 5) defines the Auto-Submitted header to prevent
* vacation replies, et al, from being sent in response to
* machine-generated mail.
*/
/*
* Additional headers for mail filtering and diagnostics:
*/
/*
* Message Body:
*
* (Temporary file is fopen'ed with "r", secure open.)
*/
if (filesize > 0 &&
} else {
}
exit(0);
}
static int
{
int cnt;
static int pending_msg;
static time_t pending_reftime;
if (pending_msg) {
pending_msg = 0;
return (0);
}
#ifdef CRON_MAXSLEEP
/*
* CRON_MAXSLEEP can be defined to have cron periodically wake
* up, so that cron can detect a change of TOD and adjust the
* sleep time more frequently.
*/
#endif
}
perror("! pselect");
/* pselect timeout or interrupted */
if (cnt <= 0)
return (0);
errno = 0;
perror("! read");
return (0);
}
/*
* we need to run the job before reloading crontab.
*/
pending_msg = 1;
return (1);
}
return (0);
}
/*
* process the message supplied via pipe. This will be called either
* immediately after cron read the message from pipe, or idle time
* if the message was pending due to the job execution.
*/
static void
{
return;
case AT:
else
break;
case CRON:
else
break;
case REFRESH:
refresh = 1;
return;
default:
msg("message received - bad format");
break;
}
if (next_event != NULL) {
(next_event->u)->ctid)) {
case -1:
break;
case -2: /* event time lower than init time */
reset_needed = 1;
break;
}
} else { /* etype == ATEVENT */
}
}
next_event = NULL;
}
}
/*
* Allocate a new or find an existing runinfo structure
*/
static struct runinfo *
{
if (pid == 0) { /* allocate a new entry */
return (rp);
}
/* search the list for an existing entry */
break;
}
return (rp);
}
/*
* Free a runinfo structure and its associated memory
*/
static void
{
#ifdef DEBUG
#endif
break;
}
}
}
/* ARGSUSED */
static void
thaw_handler(int sig)
{
refresh = 1;
}
/* ARGSUSED */
static void
{
}
/*ARGSUSED*/
static void
child_handler(int sig)
{
;
}
static void
child_sigreset(void)
{
}
/*
* crabort() - handle exits out of cron
*/
static void
{
int c;
if (action & REMOVE_FIFO) {
/* FIFO vanishes when cron finishes */
perror("cron could not unlink FIFO");
}
if (action & CONSOLE_MSG) {
/* write error msg to console */
(void) close(c);
}
}
/* always log the message */
msg("******* CRON ABORTED ********");
exit(1);
}
/*
* msg() - time-stamped error reporting function
*/
/*PRINTFLIKE1*/
static void
{
time_t t;
}
static void
{
msg("%s: ignoring %s job (user: %s, cmd: %s, time: %ld)",
}
static void
{
time_t t;
int ret;
if (!log)
return;
(void) printf("%c %s %u %c %s",
(void) putchar('\n');
}
static void
{
/* run job at a later time */
(next_event->u)->ctid)) {
case -1:
break;
case -2: /* event time lower than init time */
reset_needed = 1;
break;
}
delayed = 1;
msg("rescheduling a cron job");
return;
}
msg("rescheduling at job");
}
static void
{
int i;
int j;
/* set up default queue definitions */
for (i = 0; i < NQUEUE; i++) {
}
return;
msg("cannot open quedefs file");
msg("using default queue definitions");
return;
}
continue;
}
}
static void
{
int i;
while (*name) {
i = 0;
i *= 10;
i += *name++ - '0';
}
switch (*name++) {
case JOBF:
break;
case NICEF:
break;
case WAITF:
break;
}
}
}
/*
*/
static void
defaults()
{
int flags;
char *deflog;
/*
* get HZ value for environment
*/
else
/*
* get TZ value for environment
*/
/* ignore case */
log = 0;
else
log = 1;
/* fix for 1087611 - allow paths to be set in defaults file */
} else {
}
} else {
}
}
}
/*
* Determine if a user entry for a job is still ok. The method used here
* is a lot (about 75x) faster than using setgrent() / getgrent()
* endgrent(). It should be safe because we use the sysconf to determine
* the max, and it tolerates the max being 0.
*/
static int
verify_user_cred(struct usr *u)
{
size_t numUsrGrps = 0;
size_t numOrigGrps = 0;
size_t i;
int retval;
/*
* Maximum number of groups a user may be in concurrently. This
* is a value which we obtain at runtime through a sysconf()
* call.
*/
/*
* Arrays for cron user's group list, constructed at startup to
* be nGroupsMax elements long, used for verifying user
* credentials prior to execution.
*/
return (VUC_BADUSER);
}
} else {
}
/*
* Create the group id lists needed for job credential
* verification.
*/
}
#ifdef DEBUG
#endif
}
#ifdef DEBUG
#endif
if (nGroupsMax > 0) {
for (i = 0; i < numUsrGrps; i++) {
break;
}
}
if (OrigGrps) {
}
}
#ifdef DEBUG
#endif
return (retval);
}
static int
{
static char *progname = "cron";
int r = 0, rval = 0;
!= PAM_SUCCESS) {
#ifdef DEBUG
msg("pam_start returns %d\n", r);
#endif
rval = VUC_BADUSER;
goto set_eser_cred_exit;
}
r = pam_acct_mgmt(pamh, 0);
#ifdef DEBUG
msg("pam_acc_mgmt returns %d\n", r);
#endif
if (r == PAM_ACCT_EXPIRED) {
rval = VUC_EXPIRED;
goto set_eser_cred_exit;
}
if (r == PAM_NEW_AUTHTOK_REQD) {
rval = VUC_NEW_AUTH;
goto set_eser_cred_exit;
}
if (r != PAM_SUCCESS) {
rval = VUC_BADUSER;
goto set_eser_cred_exit;
}
}
if (r != PAM_SUCCESS)
rval = VUC_BADUSER;
return (rval);
}
static void
clean_out_user(struct usr *u)
{
if (next_event->u == u) {
next_event = NULL;
}
clean_out_ctab(u);
clean_out_atjobs(u);
free_if_unused(u);
}
static void
clean_out_atjobs(struct usr *u)
{
else {
< PATH_MAX) {
}
}
}
}
static void
clean_out_ctab(struct usr *u)
{
rm_ctevents(u);
u->ctid = 0;
u->ctexists = 0;
}
static void
cron_unlink(char *name)
{
int r;
}
}
static void
create_anc_ctab(struct event *e)
{
if (audit_cron_create_anc_file(e->u->name,
crabort("cannot create ancillary files for crontabs",
}
}
static void
delete_anc_ctab(struct event *e)
{
(void) audit_cron_delete_anc_file(e->u->name,
}
static void
create_anc_atjob(struct event *e)
{
return;
if (audit_cron_create_anc_file(e->cmd,
crabort("cannot create ancillary files for atjobs",
}
}
static void
delete_anc_atjob(struct event *e)
{
return;
(void) audit_cron_delete_anc_file(e->cmd,
}
static void
process_anc_files(int del)
{
struct event *e;
if (!audit_cron_mode())
return;
for (;;) {
e = u->ctevents;
for (;;) {
if (del)
delete_anc_ctab(e);
else
create_anc_ctab(e);
break;
}
}
e = u->atevents;
for (;;) {
if (del)
delete_anc_atjob(e);
else
create_anc_atjob(e);
break;
}
}
break;
}
}
/*ARGSUSED*/
static int
{
struct pam_message **m = msgs;
int i;
for (i = 0; i < num_msg; i++) {
switch (m[i]->msg_style) {
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
}
break;
default:
break;
}
}
return (0);
}
/*
* Cron creates process for other than job. Mail process is the
* one which rinfo does not cover. Therefore, miscpid will keep
* track of the pids executed from cron. Otherwise, we will see
* "unexpected pid returned.." messages appear in the log file.
*/
static void
{
miscpid_head = mp;
}
static int
{
int found = 0;
found = 1;
break;
}
}
if (found) {
else
miscpid_head = NULL;
}
return (found);
}
/*
* Establish contract terms such that all children are in abandoned
* process contracts.
*/
static void
contract_set_template(void)
{
int fd;
crabort("cannot open process contract template",
if (ct_pr_tmpl_set_param(fd, 0) ||
ct_tmpl_set_informative(fd, 0) ||
crabort("cannot establish contract template terms",
if (ct_tmpl_activate(fd))
crabort("cannot activate contract template",
}
/*
* Clear active process contract template.
*/
static void
contract_clear_template(void)
{
int fd;
crabort("cannot open process contract template",
if (ct_tmpl_clear(fd))
crabort("cannot clear contract template",
}
/*
* Abandon latest process contract unconditionally. If we have leaked [some
* critical amount], exit such that the kernel reaps our contracts.
*/
static void
{
int r;
if (cts_lost > MAX_LOST_CONTRACTS)
crabort("repeated failure to abandon contracts",
if (r = contract_latest(&id)) {
msg("could not obtain latest contract for "
cts_lost++;
return;
}
if (r = contract_abandon_id(id)) {
strerror(r));
cts_lost++;
return;
}
}
static struct shared *
void (*obj_free)(void *))
{
return (NULL);
}
return (NULL);
}
return (out);
}
static struct shared *
create_shared_str(char *str)
{
}
static struct shared *
{
}
return (obj);
}
static void
{
}
}
static void *
{
}