diff options
| author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
|---|---|---|
| committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
| commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
| tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/cron | |
| download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz | |
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/cron')
| -rw-r--r-- | usr/src/cmd/cron/Makefile | 208 | ||||
| -rw-r--r-- | usr/src/cmd/cron/at.c | 1012 | ||||
| -rw-r--r-- | usr/src/cmd/cron/atq.c | 562 | ||||
| -rw-r--r-- | usr/src/cmd/cron/atrm.c | 417 | ||||
| -rw-r--r-- | usr/src/cmd/cron/att1.y | 227 | ||||
| -rw-r--r-- | usr/src/cmd/cron/att2.ed | 6 | ||||
| -rw-r--r-- | usr/src/cmd/cron/att2.l | 90 | ||||
| -rw-r--r-- | usr/src/cmd/cron/batch.sh | 40 | ||||
| -rw-r--r-- | usr/src/cmd/cron/batch.xpg4.sh | 40 | ||||
| -rw-r--r-- | usr/src/cmd/cron/cron.c | 3312 | ||||
| -rw-r--r-- | usr/src/cmd/cron/cron.dfl | 7 | ||||
| -rw-r--r-- | usr/src/cmd/cron/cron.h | 108 | ||||
| -rw-r--r-- | usr/src/cmd/cron/cron.xcl | 83 | ||||
| -rw-r--r-- | usr/src/cmd/cron/cron.xml | 113 | ||||
| -rw-r--r-- | usr/src/cmd/cron/crontab.c | 547 | ||||
| -rw-r--r-- | usr/src/cmd/cron/elm.c | 487 | ||||
| -rw-r--r-- | usr/src/cmd/cron/funcs.c | 286 | ||||
| -rw-r--r-- | usr/src/cmd/cron/inc.flg | 30 | ||||
| -rw-r--r-- | usr/src/cmd/cron/permit.c | 118 | ||||
| -rw-r--r-- | usr/src/cmd/cron/svc-cron | 47 |
20 files changed, 7740 insertions, 0 deletions
diff --git a/usr/src/cmd/cron/Makefile b/usr/src/cmd/cron/Makefile new file mode 100644 index 0000000000..2601337eec --- /dev/null +++ b/usr/src/cmd/cron/Makefile @@ -0,0 +1,208 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (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 +# or http://www.opensolaris.org/os/licensing. +# 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 2005 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.cmd + +MANIFEST = cron.xml + +ROOTMANIFESTDIR = $(ROOTSVCSYSTEM) +ROOTMETHOD = $(ROOTLIBSVCMETHOD)/svc-cron + +CPPFLAGS += -D_FILE_OFFSET_BITS=64 + +ROOTVAR = $(ROOT)/var + +ROOTSPCRON = $(ROOTVAR)/spool/cron +ROOTCROND = $(ROOTETC)/cron.d +ROOTDEFAULT = $(ROOTETC)/default +ROOTCRONTABS = $(ROOTSPCRON)/crontabs +ROOTATJOBS = $(ROOTSPCRON)/atjobs +ROOTLIBCRON = $(ROOTLIB)/cron + +PROG1 = cron +PROG2 = at atq atrm crontab +XPG4PROG = at +PROG = $(PROG1) $(PROG2) + +SCRIPT = batch +XPG4SCRIPT = batch.xpg4 + +FILES = cron.dfl + +POFILE= $(PROG1)_cmd.po +POFILES= at.po crontab.po funcs.po batch.po +XGETFLAGS= -a -x $(PROG1).xcl + +ROOTDIRS = $(ROOTSPCRON) $(ROOTCROND) $(ROOTDEFAULT) \ + $(ROOTCRONTABS) $(ROOTATJOBS) + +ROOTPROG = $(PROG1:%=$(ROOTUSRSBIN)/%) $(PROG2:%=$(ROOTBIN)/%) \ + $(SCRIPT:%=$(ROOTBIN)/%) $(FILES:%=$(ROOTDEFAULT)/%) \ + $(XPG4PROG:%=$(ROOTXPG4BIN)/%) \ + $(XPG4SCRIPT:%.xpg4=$(ROOTXPG4BIN)/%) + +ROOTSETUP = $(ROOTDEFAULT)/cron + +ROOTSYMLINK = $(ROOTLIBCRON) $(ROOTETC)/cron + +COMMONOBJ1= permit.o +COMMONOBJ2= funcs.o +COMMONOBJS= $(COMMONOBJ1) $(COMMONOBJ2) +CRONOBJS= cron.o elm.o +ATOBJS= at.o att1.o att2.o +XPG4OBJS= values-xpg4.o +ATRMOBJS= atrm.o +ATQOBJS= atq.o +CRONTABOBJS= crontab.o + +XPG4ATOBJS= $(ATOBJS:%=atobjs.xpg4/%) $(COMMONOBJS:%=atobjs.xpg4/%) \ + $(XPG4OBJS:%=atobjs.xpg4/%) + +cron := POBJS = $(CRONOBJS) $(COMMONOBJ2) +at := POBJS = $(ATOBJS) $(COMMONOBJS) +at.xpg4 := POBJS = $(XPG4ATOBJS) +atrm := POBJS = $(ATRMOBJS) $(COMMONOBJS) +atq := POBJS = $(ATQOBJS) $(COMMONOBJS) +crontab := POBJS = crontab.o $(COMMONOBJS) + +at.o atobjs.xpg4/at.o funcs.o permit.o crontab.o elm.o := CFLAGS += $(CCVERBOSE) + +XPG4POBJS = atobjs.xpg4/at.o atobjs.xpg4/att1.o \ + atobjs.xpg4/att2.o atobjs.xpg4/funcs.o \ + atobjs.xpg4/permit.o atobjs.xpg4/values-xpg4.o +NOBJS= $(CRONOBJS) $(ATOBJS) $(ATRMOBJS) $(ATQOBJS) $(CRONTABOBJS) \ + $(COMMONOBJS) +OBJS = $(NOBJS) $(XPG4ATOBJS) +SRCS = $(NOBJS:%.o=%.c) + +CLOBBERFILES += $(SCRIPT) $(XPG4SCRIPT) + +$(ROOTLIBCRON) := SYMLNKDEST = ../../etc/cron.d +$(ROOTETC)/cron := SYMLNKDEST = ../usr/sbin/cron + +$(ROOTBIN)/at := FILEMODE = 04755 +$(ROOTBIN)/at := OWNER = root +$(ROOTBIN)/at := GROUP = sys +$(ROOTXPG4BIN)/at := FILEMODE = 04755 +$(ROOTXPG4BIN)/at := OWNER = root +$(ROOTXPG4BIN)/at := GROUP = sys +$(ROOTBIN)/atrm := FILEMODE = 04755 +$(ROOTBIN)/atrm := OWNER = root +$(ROOTBIN)/atrm := GROUP = sys +$(ROOTBIN)/atq := FILEMODE = 04755 +$(ROOTBIN)/atq := OWNER = root +$(ROOTBIN)/atq := GROUP = sys +$(ROOTBIN)/crontab := FILEMODE = 04555 +$(ROOTBIN)/crontab := OWNER = root +$(ROOTUSRSBIN)/cron := FILEMODE = 0555 +$(ROOTUSRSBIN)/cron := OWNER = root +$(ROOTUSRSBIN)/cron := GROUP = sys + +LDLIBS += -lbsm + +at := LDLIBS += -lproject -lsecdb +at.xpg4 := LDLIBS += -lproject -lsecdb +atq := LDLIBS += -lsecdb +atrm := LDLIBS += -lsecdb +cron := LDLIBS += -lcmd -lpam -lproject -lcontract +crontab := LDLIBS += -lsecdb -lpam + +lint := LDLIBS += -lproject -lsecdb -lcontract -lpam + +$(XPG4) := CFLAGS += -DXPG4 + +$(ROOTSVCSYSTEM)/cron.xml := OWNER = root +$(ROOTSVCSYSTEM)/cron.xml := GROUP = sys +$(ROOTSVCSYSTEM)/cron.xml := FILEMODE = 0444 + +$(ROOTLIBSVCMETHOD)/svc-cron := OWNER = root +$(ROOTLIBSVCMETHOD)/svc-cron := GROUP = bin +$(ROOTLIBSVCMETHOD)/svc-cron := FILEMODE = 0555 + + +.KEEP_STATE: + +all : $(PROG) $(XPG4) $(SCRIPT) $(XPG4SCRIPT) $(FILES) + +install : all $(ROOTPROG) $(ROOTSETUP) $(ROOTSYMLINK) \ + $(ROOTMANIFEST) $(ROOTMETHOD) + +$(PROG) : $$(POBJS) + $(LINK.c) $(POBJS) -o $@ $(LDLIBS) + $(POST_PROCESS) + +$(XPG4) : atobjs.xpg4 $(XPG4POBJS) + $(LINK.c) $(XPG4POBJS) -o $@ $(LDLIBS) + $(POST_PROCESS) + +atobjs.xpg4/%.o: %.c + $(COMPILE.c) -o $@ $< + +atobjs.xpg4: + -@mkdir -p $@ + +atobjs.xpg4/values-xpg4.o: ../../lib/common/common/values-xpg4.c + $(COMPILE.c) -o $@ ../../lib/common/common/values-xpg4.c + +att1.c : att1.y + $(YACC.y) -d att1.y + $(MV) y.tab.c att1.c + $(MV) y.tab.h att1.h + +att2.c : att2.l att2.ed att1.c + $(LEX) att2.l + ed - lex.yy.c < att2.ed + $(MV) lex.yy.c att2.c + +# Don't re-install direcories installed by Targetdirs +#$(ROOTDIRS): +# $(INS.dir) + +$(ROOTCROND)/% $(ROOTDEFAULT)/% : % + $(INS.file) + +$(ROOTSYMLINK) : + $(RM) $@; $(SYMLINK) $(SYMLNKDEST) $@ + +$(ROOTSETUP) : $(ROOTDEFAULT)/cron.dfl + $(RM) $@; $(MV) $(ROOTDEFAULT)/cron.dfl $@ + +check: $(CHKMANIFEST) + +$(POFILE): $(POFILES) + $(RM) $@; cat $(POFILES) > $@ + +clean : + $(RM) $(OBJS) att1.h att1.c att2.c + +lint : lint_SRCS + +strip : + $(STRIP) $(PROG) $(XPG4) + +include ../Makefile.targ diff --git a/usr/src/cmd/cron/at.c b/usr/src/cmd/cron/at.c new file mode 100644 index 0000000000..02e368b3db --- /dev/null +++ b/usr/src/cmd/cron/at.c @@ -0,0 +1,1012 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 + * or http://www.opensolaris.org/os/licensing. + * 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +/* + * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <dirent.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <pwd.h> +#include <stdio.h> +#include <ctype.h> +#include <time.h> +#include <signal.h> +#include <errno.h> +#include <limits.h> +#include <ulimit.h> +#include <unistd.h> +#include <locale.h> +#include <libintl.h> +#include <tzfile.h> +#include <project.h> + +#include "cron.h" + +#define TMPFILE "_at" /* prefix for temporary files */ +/* + * Mode for creating files in ATDIR. + * Setuid bit on so that if an owner of a file gives that file + * away to someone else, the setuid bit will no longer be set. + * If this happens, atrun will not execute the file + */ +#define ATMODE (S_ISUID | S_IRUSR | S_IRGRP | S_IROTH) +#define ROOT 0 /* user-id of super-user */ +#define MAXTRYS 100 /* max trys to create at job file */ + +#define BADTIME "bad time specification" +#define BADQUEUE "queue name must be a single character a-z" +#define NOTCQUEUE "queue c is reserved for cron entries" +#define BADSHELL "because your login shell isn't /usr/bin/sh,"\ + "you can't use at" +#define WARNSHELL "commands will be executed using %s\n" +#define CANTCD "can't change directory to the at directory" +#define CANTCHOWN "can't change the owner of your job to you" +#define CANTCREATE "can't create a job for you" +#define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)" +#define NOOPENDIR "can't open the at directory" +#define NOTALLOWED "you are not authorized to use at. Sorry." +#define USAGE\ + "usage: at [-c|-k|-s] [-m] [-f file] [-p project] [-q queuename] "\ + "-t time\n"\ + " at [-c|-k|-s] [-m] [-f file] [-p project] [-q queuename] "\ + "timespec\n"\ + " at -l [-p project] [-q queuename] [at_job_id...]\n"\ + " at -r at_job_id ...\n" + +#define FORMAT "%a %b %e %H:%M:%S %Y" + +/* Macros used in format for fscanf */ +#define AQ(x) #x +#define BUFFMT(p) AQ(p) + +static int leap(int); +static int atoi_for2(char *); +static int check_queue(char *, int); +static int list_jobs(int, char **, int, int); +static int remove_jobs(int, char **, char *); +static void usage(void); +static void catch(int); +static void copy(char *, FILE *, int); +static void atime(struct tm *, struct tm *); +static int not_this_project(char *); +static char *mkjobname(time_t); +static time_t parse_time(char *); +static time_t gtime(struct tm *); +void atabort(char *); +void yyerror(void); +extern int yyparse(void); + +extern void audit_at_delete(char *, char *, int); +extern int audit_at_create(char *, int); +extern int audit_cron_is_anc_name(char *); +extern int audit_cron_delete_anc_file(char *, char *); + +/* + * Error in getdate(3G) + */ +static char *errlist[] = { +/* 0 */ "", +/* 1 */ "getdate: The DATEMSK environment variable is not set", +/* 2 */ "getdate: Error on \"open\" of the template file", +/* 3 */ "getdate: Error on \"stat\" of the template file", +/* 4 */ "getdate: The template file is not a regular file", +/* 5 */ "getdate: An error is encountered while reading the template", +/* 6 */ "getdate: Malloc(3C) failed", +/* 7 */ "getdate: There is no line in the template that matches the input", +/* 8 */ "getdate: Invalid input specification" +}; + +int gmtflag = 0; +int mday[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +uid_t user; +struct tm *tp, at, rt; +static int cshflag = 0; +static int kshflag = 0; +static int shflag = 0; +static int mflag = 0; +static int pflag = 0; +static char *Shell; +static char *tfname; +static char pname[80]; +static char pname1[80]; +static short jobtype = ATEVENT; /* set to 1 if batch job */ +extern char *argp; +extern int per_errno; +static projid_t project; + +main(argc, argv) +int argc; +char **argv; +{ + FILE *inputfile; + int i, fd; + int try = 0; + int fflag = 0; + int lflag = 0; + int qflag = 0; + int rflag = 0; + int tflag = 0; + int c; + int tflen; + char *file; + char *login; + char *job; + char *jobfile = NULL; /* file containing job to be run */ + char argpbuf[LINE_MAX], timebuf[80]; + time_t now; + time_t when = 0; + struct tm *ct; + char *proj; + struct project prj, *pprj; + char mybuf[PROJECT_BUFSZ]; + char ipbuf[PROJECT_BUFSZ]; + + (void) setlocale(LC_ALL, ""); +#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ +#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ +#endif + (void) textdomain(TEXT_DOMAIN); + + user = getuid(); + login = getuser(user); + if (login == NULL) { + if (per_errno == 2) + atabort(BADSHELL); + else + atabort(INVALIDUSER); + } + + if (!allowed(login, ATALLOW, ATDENY)) + atabort(NOTALLOWED); + + while ((c = getopt(argc, argv, "cklmsrf:p:q:t:")) != EOF) + switch (c) { + case 'c': + cshflag++; + break; + case 'f': + fflag++; + jobfile = optarg; + break; + case 'k': + kshflag++; + break; + case 'l': + lflag++; + break; + case 'm': + mflag++; + break; + case 'p': + proj = optarg; + pprj = &prj; + if ((pprj = getprojbyname(proj, pprj, + (void *)&mybuf, sizeof (mybuf))) != NULL) { + project = pprj->pj_projid; + if (inproj(login, pprj->pj_name, + (void *)&ipbuf, sizeof (ipbuf))) + pflag++; + else { + (void) fprintf(stderr, + gettext("at: user %s is " + "not a member of " + "project %s (%d)\n"), + login, pprj->pj_name, + project); + exit(2); + } + break; + } + pprj = &prj; + if (isdigit(proj[0]) && + (pprj = getprojbyid(atoi(proj), pprj, + (void *)&mybuf, sizeof (mybuf))) != NULL) { + project = pprj->pj_projid; + if (inproj(login, pprj->pj_name, + (void *)&ipbuf, sizeof (ipbuf))) + pflag++; + else { + (void) fprintf(stderr, + gettext("at: user %s is " + "not a member of " + "project %s (%d)\n"), + login, pprj->pj_name, + project); + exit(2); + } + break; + } + (void) fprintf(stderr, gettext("at: project " + "%s not found.\n"), proj); + exit(2); + break; + case 'q': + qflag++; + if (optarg[1] != '\0') + atabort(BADQUEUE); + jobtype = *optarg - 'a'; + if ((jobtype < 0) || (jobtype > 25)) + atabort(BADQUEUE); + if (jobtype == 2) + atabort(NOTCQUEUE); + break; + case 'r': + rflag++; + break; + case 's': + shflag++; + break; + case 't': + tflag++; + when = parse_time(optarg); + break; + default: + usage(); + } + + argc -= optind; + argv += optind; + + if (lflag + rflag > 1) + usage(); + + if (lflag) { + if (cshflag || kshflag || shflag || mflag || + fflag || tflag || rflag) + usage(); + return (list_jobs(argc, argv, qflag, jobtype)); + } + + if (rflag) { + if (cshflag || kshflag || shflag || mflag || + fflag || tflag || qflag) + usage(); + return (remove_jobs(argc, argv, login)); + } + + if ((argc + tflag == 0) && (jobtype != BATCHEVENT)) + usage(); + + if (cshflag + kshflag + shflag > 1) + atabort("ambiguous shell request"); + + time(&now); + + if (jobtype == BATCHEVENT) + when = now; + + if (when == 0) { /* figure out what time to run the job */ + int argplen = sizeof (argpbuf) - 1; + + argpbuf[0] = '\0'; + argp = argpbuf; + i = 0; + while (i < argc) { + /* guard against buffer overflow */ + argplen -= strlen(argv[i]) + 1; + if (argplen < 0) + atabort(BADTIME); + + strcat(argp, argv[i]); + strcat(argp, " "); + i++; + } + if ((file = getenv("DATEMSK")) == 0 || file[0] == '\0') { + tp = localtime(&now); + /* + * Fix for 1047182 - we have to let yyparse + * check bounds on mday[] first, then fixup + * the leap year case. + */ + yyparse(); + + mday[1] = 28 + leap(at.tm_year); + + if (at.tm_mday > mday[at.tm_mon]) + atabort("bad date"); + + atime(&at, &rt); + when = gtime(&at); + if (!gmtflag) { + when += timezone; + if (localtime(&when)->tm_isdst) + when -= (timezone-altzone); + } + } else { /* DATEMSK is set */ + if ((ct = getdate(argpbuf)) == NULL) + atabort(errlist[getdate_err]); + else + when = mktime(ct); + } + } + + if (when < now) /* time has already past */ + atabort("too late"); + + tflen = strlen(ATDIR) + 1 + strlen(TMPFILE) + + 10 + 1; /* 10 for an INT_MAX pid */ + tfname = xmalloc(tflen); + snprintf(tfname, tflen, "%s/%s%d", ATDIR, TMPFILE, getpid()); + + /* catch INT, HUP, TERM and QUIT signals */ + if (signal(SIGINT, catch) == SIG_IGN) + signal(SIGINT, SIG_IGN); + if (signal(SIGHUP, catch) == SIG_IGN) + signal(SIGHUP, SIG_IGN); + if (signal(SIGQUIT, catch) == SIG_IGN) + signal(SIGQUIT, SIG_IGN); + if (signal(SIGTERM, catch) == SIG_IGN) + signal(SIGTERM, SIG_IGN); + if ((fd = open(tfname, O_CREAT|O_EXCL|O_WRONLY, ATMODE)) < 0) + atabort(CANTCREATE); + if (chown(tfname, user, getgid()) == -1) { + unlink(tfname); + atabort(CANTCHOWN); + } + close(1); + dup(fd); + close(fd); + sprintf(pname, "%s", PROTO); + sprintf(pname1, "%s.%c", PROTO, 'a'+jobtype); + + /* + * Open the input file with the user's permissions. + */ + if (jobfile != NULL) { + if ((seteuid(user) < 0) || + (inputfile = fopen(jobfile, "r")) == NULL) { + unlink(tfname); + fprintf(stderr, "at: %s: %s\n", jobfile, errmsg(errno)); + exit(1); + } + else + seteuid(0); + } else + inputfile = stdin; + + copy(jobfile, inputfile, when); + while (rename(tfname, job = mkjobname(when)) == -1) { + sleep(1); + if (++try > MAXTRYS / 10) { + unlink(tfname); + atabort(CANTCREATE); + } + } + unlink(tfname); + if (audit_at_create(job, 0)) + atabort(CANTCREATE); + + cron_sendmsg(ADD, login, strrchr(job, '/')+1, AT); + if (per_errno == 2) + fprintf(stderr, gettext(WARNSHELL), Shell); + cftime(timebuf, FORMAT, &when); + fprintf(stderr, gettext("job %s at %s\n"), + strrchr(job, '/')+1, timebuf); + if (when - MINUTE < HOUR) + fprintf(stderr, gettext( + "at: this job may not be executed at the proper time.\n")); + return (0); +} + + +static char * +mkjobname(t) +time_t t; +{ + int i, fd; + char *name; + + name = xmalloc(200); + for (i = 0; i < MAXTRYS; i++) { + sprintf(name, "%s/%ld.%c", ATDIR, t, 'a'+jobtype); + /* fix for 1099183, 1116833 - create file here, avoid race */ + if ((fd = open(name, O_CREAT | O_EXCL, ATMODE)) > 0) { + close(fd); + return (name); + } + t += 1; + } + atabort("queue full"); +} + + +static void +catch(int x) +{ + unlink(tfname); + exit(1); +} + + +void +atabort(msg) +char *msg; +{ + fprintf(stderr, "at: %s\n", gettext(msg)); + + exit(1); +} + +yywrap(void) +{ + return (1); +} + +void +yyerror(void) +{ + atabort(BADTIME); +} + +/* + * add time structures logically + */ +static void +atime(struct tm *a, struct tm *b) +{ + if ((a->tm_sec += b->tm_sec) >= 60) { + b->tm_min += a->tm_sec / 60; + a->tm_sec %= 60; + } + if ((a->tm_min += b->tm_min) >= 60) { + b->tm_hour += a->tm_min / 60; + a->tm_min %= 60; + } + if ((a->tm_hour += b->tm_hour) >= 24) { + b->tm_mday += a->tm_hour / 24; + a->tm_hour %= 24; + } + a->tm_year += b->tm_year; + if ((a->tm_mon += b->tm_mon) >= 12) { + a->tm_year += a->tm_mon / 12; + a->tm_mon %= 12; + } + a->tm_mday += b->tm_mday; + mday[1] = 28 + leap(a->tm_year); + while (a->tm_mday > mday[a->tm_mon]) { + a->tm_mday -= mday[a->tm_mon++]; + if (a->tm_mon > 11) { + a->tm_mon = 0; + mday[1] = 28 + leap(++a->tm_year); + } + } + +} + +static int +leap(int year) +{ + return (isleap(year + TM_YEAR_BASE)); +} + +/* + * return time from time structure + */ +static time_t +gtime(tptr) +struct tm *tptr; +{ + register i; + long tv; + int dmsize[12] = + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + + tv = 0; + for (i = 1970; i != tptr->tm_year+TM_YEAR_BASE; i++) + tv += (365 + isleap(i)); + /* + * We call isleap since leap() adds + * 1900 onto any value passed + */ + + if (!leap(tptr->tm_year) && at.tm_mday == 29 && at.tm_mon == 1) + atabort("bad date - not a leap year"); + + if ((leap(tptr->tm_year)) && tptr->tm_mon >= 2) + ++tv; + + for (i = 0; i < tptr->tm_mon; ++i) + tv += dmsize[i]; + tv += tptr->tm_mday - 1; + tv = 24 * tv + tptr->tm_hour; + tv = 60 * tv + tptr->tm_min; + tv = 60 * tv + tptr->tm_sec; + return (tv); +} + +/* + * make job file from proto + stdin + */ +static void +copy(char *jobfile, FILE *inputfile, int when) +{ + register c; + register FILE *pfp; + register FILE *xfp; + char *shell; + char dirbuf[PATH_MAX + 1]; + char line[LINE_MAX]; + register char **ep; + mode_t um; + char *val; + extern char **environ; + int pfd[2]; + pid_t pid; + uid_t realusr; + int ttyinput; + int ulimit_flag = 0; + struct rlimit rlp; + struct project prj, *pprj; + char pbuf[PROJECT_BUFSZ]; + char pbuf2[PROJECT_BUFSZ]; + char *user; + + /* + * Fix for 1099381: + * If the inputfile is from a tty, then turn on prompting, and + * put out a prompt now, instead of waiting for a lot of file + * activity to complete. + */ + ttyinput = isatty(fileno(inputfile)); + if (ttyinput) { + fputs("at> ", stderr); + fflush(stderr); + } + + /* + * Fix for 1053807: + * Determine what shell we should use to run the job. If the user + * didn't explicitly request that his/her current shell be over- + * ridden (shflag or cshflag), then we use the current shell. + */ + if (cshflag) + Shell = shell = "/bin/csh"; + else if (kshflag) { + Shell = shell = "/bin/ksh"; + ulimit_flag = 1; + } else if (shflag) { + Shell = shell = "/bin/sh"; + ulimit_flag = 1; + } else if (((Shell = val = getenv("SHELL")) != NULL) && + (*val != '\0')) { + shell = "$SHELL"; + if ((strstr(val, "/sh") != NULL) || + (strstr(val, "/ksh") != NULL)) + ulimit_flag = 1; + } else { + /* SHELL is NULL or unset, therefore use default */ +#ifdef XPG4 + Shell = shell = "/usr/xpg4/bin/sh"; +#else + Shell = shell = "/bin/sh"; +#endif /* XPG4 */ + ulimit_flag = 1; + } + + printf(": %s job\n", jobtype ? "batch" : "at"); + printf(": jobname: %.127s\n", (jobfile == NULL) ? "stdin" : jobfile); + printf(": notify by mail: %s\n", (mflag) ? "yes" : "no"); + + if (pflag) { + (void) printf(": project: %d\n", project); + } else { + /* + * Check if current user is a member of current project. + * This check is done here to avoid setproject() failure + * later when the job gets executed. If current user does + * not belong to current project, user's default project + * will be used instead. This is achieved by not specifying + * the project (": project: <project>\n") in the job file. + */ + if ((user = getuser(getuid())) == NULL) + atabort(INVALIDUSER); + project = getprojid(); + pprj = getprojbyid(project, &prj, pbuf, sizeof (pbuf)); + if (pprj != NULL) { + if (inproj(user, pprj->pj_name, pbuf2, sizeof (pbuf2))) + (void) printf(": project: %d\n", project); + } + } + + for (ep = environ; *ep; ep++) { + if (strchr(*ep, '\'') != NULL) + continue; + if ((val = strchr(*ep, '=')) == NULL) + continue; + *val++ = '\0'; + printf("export %s; %s='%s'\n", *ep, *ep, val); + *--val = '='; + } + if ((pfp = fopen(pname1, "r")) == NULL && + (pfp = fopen(pname, "r")) == NULL) + atabort("no prototype"); + /* + * Put in a line to run the proper shell using the rest of + * the file as input. Note that 'exec'ing the shell will + * cause sh() to leave a /tmp/sh### file around. (1053807) + */ + printf("%s << '...the rest of this file is shell input'\n", shell); + + um = umask(0); + while ((c = getc(pfp)) != EOF) { + if (c != '$') + putchar(c); + else switch (c = getc(pfp)) { + case EOF: + goto out; + case 'd': + /* + * fork off a child with submitter's permissions, + * otherwise, when IFS=/, /usr/bin/pwd would be parsed + * by the shell as file "bin". The shell would + * then search according to the submitter's PATH + * and run the file bin with root permission + */ + + (void) fflush(stdout); + dirbuf[0] = NULL; + if (pipe(pfd) != 0) + atabort("pipe open failed"); + realusr = getuid(); /* get realusr before the fork */ + if ((pid = fork()) == (pid_t)-1) + atabort("fork failed"); + if (pid == 0) { /* child process */ + (void) close(pfd[0]); + /* remove setuid for pwd */ + (void) setuid(realusr); + if ((xfp = popen("/usr/bin/pwd", "r")) + != NULL) { + fscanf(xfp, "%" BUFFMT(PATH_MAX) "s", + dirbuf); + (void) pclose(xfp); + xfp = fdopen(pfd[1], "w"); + fprintf(xfp, "%s", dirbuf); + (void) fclose(xfp); + } + _exit(0); + } + (void) close(pfd[1]); /* parent process */ + xfp = fdopen(pfd[0], "r"); + fscanf(xfp, "%" BUFFMT(PATH_MAX) "s", dirbuf); + printf("%s", dirbuf); + (void) fclose(xfp); + break; + case 'm': + printf("%o", um); + break; + case '<': + if (ulimit_flag) { + if (getrlimit(RLIMIT_FSIZE, &rlp) == 0) { + if (rlp.rlim_cur == RLIM_INFINITY) + printf("ulimit unlimited\n"); + else + printf("ulimit %lld\n", + rlp.rlim_cur / 512); + } + } + /* + * fix for 1113572 - use fputs() so that a + * newline isn't appended to the one returned + * with fgets(); 1099381 - prompt for input. + */ + while (fgets(line, LINE_MAX, inputfile) != NULL) { + fputs(line, stdout); + if (ttyinput) + fputs("at> ", stderr); + } + if (ttyinput) /* clean up the final output */ + fputs("<EOT>\n", stderr); + break; + case 't': + printf(":%lu", when); + break; + default: + putchar(c); + } + } +out: + fclose(pfp); + fflush(NULL); +} + +static int +remove_jobs(int argc, char **argv, char *login) +/* remove jobs that are specified */ +{ + int i, r; + int error = 0; + struct stat buf; + struct passwd *pw; + + pw = getpwuid(user); + if (pw == NULL) { + atabort("Invalid user.\n"); + } + + if (argc == 0) + usage(); + if (chdir(ATDIR) == -1) + atabort(CANTCD); + for (i = 0; i < argc; i++) + if (strchr(argv[i], '/') != NULL) { + fprintf(stderr, "at: %s: not a valid job-id\n", + argv[i]); + } else if (stat(argv[i], &buf)) { + fprintf(stderr, "at: %s: ", argv[i]); + perror(""); + } else if ((user != buf.st_uid) && + (!chkauthattr(CRONADMIN_AUTH, pw->pw_name))) { + fprintf(stderr, "at: you don't own %s\n", + argv[i]); + error = 1; + } else { + if (chkauthattr(CRONADMIN_AUTH, pw->pw_name)) { + login = getuser((uid_t)buf.st_uid); + if (login == NULL) { + if (per_errno == 2) + atabort(BADSHELL); + else + atabort(INVALIDUSER); + } + } + cron_sendmsg(DELETE, login, argv[i], AT); + r = unlink(argv[i]); + audit_at_delete(argv[i], ATDIR, r); + } + return (error); +} + + + +static int +list_jobs(int argc, char **argv, int qflag, int queue) +{ + DIR *dir; + int i; + int error = 0; + char *patdir, *atdir, *ptr; + char timebuf[80]; + time_t t; + struct stat buf, st1, st2; + struct dirent *dentry; + struct passwd *pw; + unsigned int atdirlen; + int r; + struct passwd *pwd, pwds; + char buf_pwd[1024]; + char job_file[PATH_MAX]; + + pwd = getpwuid_r(user, &pwds, buf_pwd, sizeof (buf_pwd)); + if (pwd == NULL) { + atabort("Invalid user.\n"); + } + + /* list jobs for user */ + if (chdir(ATDIR) == -1) + atabort(CANTCD); + + atdirlen = strlen(ATDIR); + atdir = xmalloc(atdirlen + 1); + strcpy(atdir, ATDIR); + patdir = strrchr(atdir, '/'); + *patdir = '\0'; + if (argc == 0) { + /* list all jobs for a user */ + if (stat(ATDIR, &st1) != 0 || stat(atdir, &st2) != 0) + atabort("Can not get status of spooling" + "directory for at"); + if ((dir = opendir(ATDIR)) == NULL) + atabort(NOOPENDIR); + while (1) { + if ((dentry = readdir(dir)) == NULL) + break; + if ((dentry->d_ino == st1.st_ino) || + (dentry->d_ino == st2.st_ino)) + continue; + if ((r = audit_cron_is_anc_name(dentry->d_name)) == 1) + continue; + if (stat(dentry->d_name, &buf)) { + unlink(dentry->d_name); + audit_cron_delete_anc_file(dentry->d_name, + NULL); + continue; + } + if ((!chkauthattr(CRONADMIN_AUTH, pwd->pw_name)) && + (buf.st_uid != user)) + continue; + ptr = dentry->d_name; + if (((t = num(&ptr)) == 0) || (*ptr != '.')) + continue; + strcpy(job_file, patdir); + strcat(job_file, dentry->d_name); + if (pflag && not_this_project(job_file)) + continue; + ascftime(timebuf, FORMAT, localtime(&t)); + if ((chkauthattr(CRONADMIN_AUTH, pwd->pw_name)) && + ((pw = getpwuid(buf.st_uid)) != NULL)) { + if (!qflag || (qflag && + check_queue(ptr, queue))) + printf("user = %s\t%s\t%s\n", + pw->pw_name, dentry->d_name, + timebuf); + } else + if (!qflag || (qflag && + check_queue(ptr, queue))) + printf("%s\t%s\n", + dentry->d_name, timebuf); + } + (void) closedir(dir); + } else /* list particular jobs for user */ + for (i = 0; i < argc; i++) { + ptr = argv[i]; + strlcpy(job_file, patdir, PATH_MAX); + strlcat(job_file, ptr, PATH_MAX); + if (((t = num(&ptr)) == 0) || (*ptr != '.')) { + fprintf(stderr, gettext( + "at: invalid job name %s\n"), argv[i]); + error = 1; + } else if (stat(argv[i], &buf)) { + fprintf(stderr, "at: %s: ", argv[i]); + perror(""); + error = 1; + } else if ((user != buf.st_uid) && + (!chkauthattr(CRONADMIN_AUTH, pwd->pw_name))) { + fprintf(stderr, gettext( + "at: you don't own %s\n"), argv[i]); + error = 1; + } else if (pflag && not_this_project(job_file)) { + continue; + } else { + if (!qflag || (qflag && + check_queue(ptr, queue))) { + ascftime(timebuf, FORMAT, + localtime(&t)); + printf("%s\t%s\n", argv[i], timebuf); + } + } + } + return (error); +} + +/* + * open the command file and read the project id line + * compare to the project number provided via -p on the command line + * return 0 if they match, 1 if they don't match or an error occurs. + */ +#define SKIPCOUNT 3 /* lines to skip to get to project line in file */ + +static int +not_this_project(char *filename) +{ + FILE *fp; + projid_t sproj; + int i; + + if ((fp = fopen(filename, "r")) == NULL) + return (1); + + for (i = 0; i < SKIPCOUNT; i++) + fscanf(fp, "%*[^\n]\n"); + + fscanf(fp, ": project: %d\n", &sproj); + fclose(fp); + + return (sproj == project ? 0 : 1); +} + +static int +check_queue(char *name, int queue) +{ + if ((name[strlen(name) - 1] - 'a') == queue) + return (1); + else + return (0); +} + +static time_t +parse_time(char *t) +{ + int century = 0; + int seconds = 0; + char *p; + time_t when = 0; + struct tm tm; + + /* + * time in the following format (defined by the touch(1) spec): + * [[CC]YY]MMDDhhmm[.SS] + */ + if ((p = strchr(t, '.')) != NULL) { + if (strchr(p+1, '.') != NULL) + atabort(BADTIME); + seconds = atoi_for2(p+1); + *p = '\0'; + } + + memset(&tm, 0, sizeof (struct tm)); + when = time(0); + tm.tm_year = localtime(&when)->tm_year; + + switch (strlen(t)) { + case 12: /* CCYYMMDDhhmm */ + century = atoi_for2(t); + t += 2; + case 10: /* YYMMDDhhmm */ + tm.tm_year = atoi_for2(t); + t += 2; + if (century == 0) { + if (tm.tm_year < 69) + tm.tm_year += 100; + } else + tm.tm_year += (century - 19) * 100; + case 8: /* MMDDhhmm */ + tm.tm_mon = atoi_for2(t) - 1; + t += 2; + tm.tm_mday = atoi_for2(t); + t += 2; + tm.tm_hour = atoi_for2(t); + t += 2; + tm.tm_min = atoi_for2(t); + t += 2; + tm.tm_sec = seconds; + break; + default: + atabort(BADTIME); + } + + if ((when = mktime(&tm)) == -1) + atabort(BADTIME); + if (tm.tm_isdst) + when -= (timezone-altzone); + return (when); +} + +static int +atoi_for2(char *p) { + int value; + + value = (*p - '0') * 10 + *(p+1) - '0'; + if ((value < 0) || (value > 99)) + atabort(BADTIME); + return (value); +} + +static void +usage(void) +{ + fprintf(stderr, USAGE); + exit(1); +} diff --git a/usr/src/cmd/cron/atq.c b/usr/src/cmd/cron/atq.c new file mode 100644 index 0000000000..1b3503683d --- /dev/null +++ b/usr/src/cmd/cron/atq.c @@ -0,0 +1,562 @@ +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +/* + * Copyright (c) 1983 Regents of the University of California. + * All rights reserved. The Berkeley software License Agreement + * specifies the terms and conditions for redistribution. + */ + +/* + * Copyright 1984-1988, 2002-2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.6 */ + +/* + * + * Synopsis: atq [ -c ] [ -n ] [ name ... ] + * + * + * Print the queue of files waiting to be executed. These files + * were created by using the "at" command and are located in the + * directory defined by ATDIR. + */ + +#include <stdio.h> +#include <sys/types.h> +#include <sys/file.h> +#include <dirent.h> +#include <sys/stat.h> +#include <time.h> +#include <pwd.h> +#include <ctype.h> +#include <unistd.h> +#include <locale.h> +#include <errno.h> +#include "cron.h" + +extern char *errmsg(); +extern char *strchr(); + +/* + * Months of the year + */ +static char *mthnames[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", + "Aug", "Sep", "Oct", "Nov", "Dec", +}; + +int numentries; /* number of entries in spooling area */ +int namewanted = 0; /* print jobs for a certain person */ +struct dirent **queue; /* the queue itself */ + +#define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)" +#define NOTALLOWED "you are not authorized to use at. Sorry." + + +main(argc, argv) +int argc; +char **argv; +{ + + register struct passwd *pp; /* password file entry pointer */ + struct passwd pr; + register int i; + int cflag = 0; /* print in order of creation time */ + int nflag = 0; /* just print the number of jobs in */ + /* queue */ + int usage(); /* print usage info and exit */ + extern int creation(); /* sort jobs by date of creation */ + extern int execution(); /* sort jobs by date of execution */ + int filewanted(); /* should file be included in queue? */ + int printqueue(); /* print the queue */ + int countfiles(); /* count the number of files in queue */ + /* for a given person */ + uid_t *uidlist = NULL; /* array of spec. owner ID(s) requ. */ + int argnum = 0; /* number of names passed as arg't */ + int badarg = 0; + char *c; + + + --argc, ++argv; + + (void) setlocale(LC_ALL, ""); + pp = getpwuid(getuid()); + pr.pw_uid = pp->pw_uid; + pr.pw_name = pp->pw_name; + + if (pp == NULL) + atabort(INVALIDUSER); + if (!allowed(pp->pw_name, ATALLOW, ATDENY)) + atabort(NOTALLOWED); + + /* + * Interpret command line flags if they exist. + */ + while (argc > 0 && **argv == '-') { + (*argv)++; + while (**argv) { + switch (*(*argv)++) { + + case 'c' : cflag++; + break; + + case 'n' : nflag++; + break; + + default : usage(); + + } + } + --argc, ++argv; + } + + /* + * If a certain name (or names) is requested, set a pointer to the + * beginning of the list. + */ + if (argc > 0) { + ++namewanted; + uidlist = (uid_t *)malloc(argc * sizeof (uid_t)); + if (uidlist == NULL) + atabortperror("can't allocate list of users"); + for (i = 0; i < argc; i++) { + if ((chkauthattr(CRONADMIN_AUTH, pr.pw_name)) || + strcmp(pr.pw_name, argv[i]) == 0) { + if ((pp = getpwnam(argv[i])) == NULL) { + (void) fprintf(stderr, + "atq: No such user %s\n", argv[i]); + exit(1); + } + uidlist[argnum] = pp->pw_uid; + argnum++; + } + else + badarg++; + } + if (badarg) + if (argnum) + printf("Printing queue information only " + "for %s:\n", pr.pw_name); + else { + printf("atq: Non-priviledged user cannot " + "request information regarding other " + "users\n"); + exit(1); + } + } else if (!chkauthattr(CRONADMIN_AUTH, pr.pw_name)) { + /* no argument specified and the invoker is not root */ + ++namewanted; + argnum = 1; + if ((uidlist = (uid_t *)malloc(sizeof (uid_t))) == NULL) + atabortperror("can't allocate list of users"); + *uidlist = pr.pw_uid; + } + + /* + * Move to the spooling area and scan the directory, placing the + * files in the queue structure. The queue comes back sorted by + * execution time or creation time. + */ + if (chdir(ATDIR) == -1) + atabortperror(ATDIR); + if ((numentries = ascandir(".", &queue, filewanted, + (cflag) ? creation : execution)) < 0) + atabortperror(ATDIR); + + + /* + * Either print a message stating: + * + * 1) that the spooling area is empty. + * 2) the number of jobs in the spooling area. + * 3) the number of jobs in the spooling area belonging to + * a certain person. + * 4) that the person requested doesn't have any files in the + * spooling area. + * + * or send the queue off to "printqueue" for printing. + * + * This whole process might seem a bit elaborate, but it's worthwhile + * to print some informative messages for the user. + * + */ + if ((numentries == 0) && (!nflag)) { + printf("no files in queue.\n"); + exit(0); + } + if (nflag) { + printf("%d\n", (namewanted) ? + countfiles(uidlist, argnum) : numentries); + exit(0); + } + if ((namewanted) && (countfiles(uidlist, argnum) == 0)) { + if (argnum == 1) + if (argnum != argc) c = pr.pw_name; + else c = *argv; + printf("no files for %s.\n", (argnum == 1) ? + c : "specified users"); + exit(0); + } + printqueue(uidlist, argnum); + exit(0); +} + +/* + * Count the number of jobs in the spooling area owned by a certain person(s). + */ +countfiles(uidlist, nuids) +uid_t *uidlist; +int nuids; +{ + register int i, j; /* for loop indices */ + int entryfound; /* found file owned by users */ + int numfiles = 0; /* number of files owned by a */ + /* certain person(s) */ + register uid_t *ptr; /* scratch pointer */ + struct stat stbuf; /* buffer for file stats */ + + + /* + * For each file in the queue, see if the user(s) own the file. We + * have to use "entryfound" (rather than simply incrementing "numfiles") + * so that if a person's name appears twice on the command line we + * don't double the number of files owned by him/her. + */ + for (i = 0; i < numentries; i++) { + if ((stat(queue[i]->d_name, &stbuf)) < 0) { + continue; + } + ptr = uidlist; + entryfound = 0; + + for (j = 0; j < nuids; j++) { + if (*ptr == stbuf.st_uid) + ++entryfound; + ++ptr; + } + if (entryfound) + ++numfiles; + } + return (numfiles); +} + +/* + * Print the queue. If only jobs belonging to a certain person(s) are requested, + * only print jobs that belong to that person(s). + */ +printqueue(uidlist, nuids) +uid_t *uidlist; +int nuids; +{ + register int i, j; /* for loop indices */ + int rank; /* rank of a job */ + int entryfound; /* found file owned by users */ + int printrank(); /* print the rank of a job */ + char *getname(); + register uid_t *ptr; /* scratch pointer */ + struct stat stbuf; /* buffer for file stats */ + char curqueue; /* queue of current job */ + char lastqueue; /* queue of previous job */ + + /* + * Print the header for the queue. + */ + printf(" Rank Execution Date Owner Job " + "Queue Job Name\n"); + + /* + * Print the queue. If a certain name(s) was requested, print only jobs + * belonging to that person(s), otherwise print the entire queue. + * Once again, we have to use "entryfound" (rather than simply + * comparing each command line argument) so that if a person's name + * appears twice we don't print each file owned by him/her twice. + * + * + * "printrank", "printdate", and "printjobname" all take existing + * data and display it in a friendly manner. + * + */ + lastqueue = '\0'; + for (i = 0; i < numentries; i++) { + if ((stat(queue[i]->d_name, &stbuf)) < 0) { + continue; + } + curqueue = *(strchr(queue[i]->d_name, '.') + 1); + if (curqueue != lastqueue) { + rank = 1; + lastqueue = curqueue; + } + if (namewanted) { + ptr = uidlist; + entryfound = 0; + + for (j = 0; j < nuids; j++) { + if (*ptr == stbuf.st_uid) + ++entryfound; + ++ptr; + } + if (!entryfound) + continue; + } + printrank(rank++); + printdate(queue[i]->d_name); + printf("%-10s ", getname(stbuf.st_uid)); + printf("%-14s ", queue[i]->d_name); + printf(" %c", curqueue); + printjobname(queue[i]->d_name); + } + ++ptr; +} + +/* + * Get the uid of a person using his/her login name. Return -1 if no + * such account name exists. + */ +uid_t +getid(name) +char *name; +{ + + struct passwd *pwdinfo; /* password info structure */ + + + if ((pwdinfo = getpwnam(name)) == 0) + return ((uid_t)-1); + + return (pwdinfo->pw_uid); +} + +/* + * Get the full login name of a person using his/her user id. + */ +char * +getname(uid) +uid_t uid; +{ + register struct passwd *pwdinfo; /* password info structure */ + + + if ((pwdinfo = getpwuid(uid)) == 0) + return ("???"); + return (pwdinfo->pw_name); +} + +/* + * Print the rank of a job. (I've got to admit it, I stole it from "lpq") + */ +static +printrank(n) +{ + static char *r[] = { + "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" + }; + + if ((n/10) == 1) + printf("%3d%-5s", n, "th"); + else + printf("%3d%-5s", n, r[n%10]); +} + +/* + * Print the date that a job is to be executed. This takes some manipulation + * of the file name. + */ +printdate(filename) +char *filename; +{ + time_t jobdate; + extern time_t num(); + register struct tm *unpackeddate; + char date[18]; /* reformatted execution date */ + + /* + * Convert the file name to a date. + */ + jobdate = num(&filename); + unpackeddate = localtime(&jobdate); + + /* years since 1900 + base century 1900 */ + unpackeddate->tm_year += 1900; + /* + * Format the execution date of a job. + */ + sprintf(date, "%3s %2d, %4d %02d:%02d", mthnames[unpackeddate->tm_mon], + unpackeddate->tm_mday, unpackeddate->tm_year, + unpackeddate->tm_hour, unpackeddate->tm_min); + + /* + * Print the date the job will be executed. + */ + printf("%-21.18s", date); +} + +/* + * Print a job name. If the old "at" has been used to create the spoolfile, + * the three line header that the new version of "at" puts in the spoolfile. + * Thus, we just print "???". + */ +printjobname(file) +char *file; +{ + char *ptr; /* scratch pointer */ + char jobname[28]; /* the job name */ + FILE *filename; /* job file in spooling area */ + + /* + * Open the job file and grab the third line. + */ + printf(" "); + + if ((filename = fopen(file, "r")) == NULL) { + printf("%.27s\n", "???"); + (void) fprintf(stderr, "atq: Can't open job file %s: %s\n", + file, errmsg(errno)); + return; + } + /* + * Skip over the first and second lines. + */ + fscanf(filename, "%*[^\n]\n"); + + /* + * Now get the job name. + */ + if (fscanf(filename, ": jobname: %27s%*[^\n]\n", jobname) != 1) { + printf("%.27s\n", "???"); + fclose(filename); + return; + } + fclose(filename); + + /* + * Put a pointer at the begining of the line and remove the basename + * from the job file. + */ + ptr = jobname; + if ((ptr = (char *)strrchr(jobname, '/')) != 0) + ++ptr; + else + ptr = jobname; + + if (strlen(ptr) > 23) + printf("%.23s ...\n", ptr); + else + printf("%.27s\n", ptr); +} + + + +/* + * Sort files by queue, time of creation, and sequence. (used by "ascandir") + */ +creation(d1, d2) +struct dirent **d1, **d2; +{ + register char *p1, *p2; + register int i; + struct stat stbuf1, stbuf2; + register int seq1, seq2; + + if ((p1 = strchr((*d1)->d_name, '.')) == NULL) + return (0); + if ((p2 = strchr((*d2)->d_name, '.')) == NULL) + return (0); + p1++; + p2++; + if ((i = *p1++ - *p2++) != 0) + return (i); + + if (stat((*d1)->d_name, &stbuf1) < 0) + return (0); + + if (stat((*d2)->d_name, &stbuf2) < 0) + return (0); + + if (stbuf1.st_ctime < stbuf2.st_ctime) + return (-1); + else if (stbuf1.st_ctime > stbuf2.st_ctime) + return (1); + p1++; + p2++; + seq1 = atoi(p1); + seq2 = atoi(p2); + return (seq1 - seq2); +} + +/* + * Sort files by queue, time of execution, and sequence. (used by "ascandir") + */ +execution(d1, d2) +struct dirent **d1, **d2; +{ + register char *p1, *p2; + register int i; + char *name1, *name2; + register time_t time1, time2; + register int seq1, seq2; + extern time_t num(); + + name1 = (*d1)->d_name; + name2 = (*d2)->d_name; + if ((p1 = strchr(name1, '.')) == NULL) + return (1); + if ((p2 = strchr(name2, '.')) == NULL) + return (1); + p1++; + p2++; + if ((i = *p1++ - *p2++) != 0) + return (i); + + time1 = num(&name1); + time2 = num(&name2); + + if (time1 < time2) + return (-1); + else if (time1 > time2) + return (1); + p1++; + p2++; + seq1 = atoi(p1); + seq2 = atoi(p2); + return (seq1 - seq2); +} + + +/* + * Print usage info and exit. + */ +usage() +{ + fprintf(stderr, "usage: atq [-c] [-n] [name ...]\n"); + exit(1); +} + +aterror(msg) + char *msg; +{ + fprintf(stderr, "atq: %s\n", msg); +} + +atperror(msg) + char *msg; +{ + fprintf(stderr, "atq: %s: %s\n", msg, errmsg(errno)); +} + +atabort(msg) + char *msg; +{ + aterror(msg); + exit(1); +} + +atabortperror(msg) + char *msg; +{ + atperror(msg); + exit(1); +} diff --git a/usr/src/cmd/cron/atrm.c b/usr/src/cmd/cron/atrm.c new file mode 100644 index 0000000000..161b56ece4 --- /dev/null +++ b/usr/src/cmd/cron/atrm.c @@ -0,0 +1,417 @@ +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +/* + * Copyright (c) 1983 Regents of the University of California. + * All rights reserved. The Berkeley software License Agreement + * specifies the terms and conditions for redistribution. + + * Copyright 1984-2002 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.5 */ + +/* + * synopsis: atrm [-f] [-i] [-a] [[job #] [user] ...] + * + * + * Remove "at" jobs. + */ + +#include <stdio.h> +#include <pwd.h> +#include <ctype.h> +#include <sys/types.h> +#include <dirent.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> +#include <locale.h> +#include "cron.h" + +extern time_t num(); +extern char *errmsg(); +extern int errno; + +extern void audit_at_delete(char *, char *, int); + +#define SUPERUSER 0 /* is user super-user? */ +#define CANTCD "can't change directory to the at directory" +#define NOREADDIR "can't read the at directory" + +uid_t user; /* person requesting removal */ +int fflag = 0; /* suppress announcements? */ +int iflag = 0; /* run interactively? */ + +char login[UNAMESIZE]; +char login_authchk[UNAMESIZE]; /* used for authorization checks */ + +#define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)" +#define NOTALLOWED "you are not authorized to use at. Sorry." +#define NAMETOOLONG "login name too long" + +main(argc,argv) +int argc; +char **argv; + +{ + int i; /* for loop index */ + int numjobs; /* # of jobs in spooling area */ + int usage(); /* print usage info and exit */ + int allflag = 0; /* remove all jobs belonging to user? */ + int jobexists; /* does a requested job exist? */ + extern int strcmp(); /* sort jobs by date of execution */ + char *pp; + char *getuser(); + struct dirent **namelist; /* names of jobs in spooling area */ + struct stat **statlist; + struct passwd *pwd; + + /* + * If job number, user name, or "-" is not specified, just print + * usage info and exit. + */ + (void)setlocale(LC_ALL, ""); + if (argc < 2) + usage(); + + --argc; ++argv; + + pp = getuser((user=getuid())); + if (pp == NULL) + atabort(INVALIDUSER); + if (strlcpy(login, pp, sizeof (login)) >= sizeof (login)) + atabort(NAMETOOLONG); + if (strlcpy(login_authchk, pp, sizeof (login_authchk)) + >= sizeof (NAMETOOLONG)) + atabort(INVALIDUSER); + if (!allowed(login, ATALLOW, ATDENY)) + atabort(NOTALLOWED); + + /* + * Process command line flags. + * Special case the "-" option so that others may be grouped. + */ + while (argc > 0 && **argv == '-') { + *(*argv)++; + while (**argv) switch (*(*argv)++) { + + case 'a': ++allflag; + break; + + case 'f': ++fflag; + break; + + case 'i': ++iflag; + break; + + default: usage(); + } + ++argv; --argc; + } + + /* + * If all jobs are to be removed and extra command line arguments + * are given, print usage info and exit. + */ + if (allflag && argc) + usage(); + + /* + * If only certain jobs are to be removed and no job #'s or user + * names are specified, print usage info and exit. + */ + if (!allflag && !argc) + usage(); + + /* + * If interactive removal and quiet removal are requested, override + * quiet removal and run interactively. + */ + if (iflag && fflag) + fflag = 0; + + + /* + * Move to spooling directory and get a list of the files in the + * spooling area. + */ + numjobs = getjoblist(&namelist,&statlist,strcmp); + /* + * If all jobs belonging to the user are to be removed, compare + * the user's id to the owner of the file. If they match, remove + * the file. If the user is the super-user, don't bother comparing + * the id's. After all files are removed, exit (status 0). + */ + if (allflag) { + for (i = 0; i < numjobs; ++i) { + if (chkauthattr(CRONADMIN_AUTH, login_authchk) || + user == statlist[i]->st_uid) + (void) removentry(namelist[i]->d_name, + statlist[i], user); + } + exit(0); + } + + /* + * If only certain jobs are to be removed, interpret each command + * line argument. A check is done to see if it is a user's name or + * a job number (inode #). If it's a user's name, compare the argument + * to the files owner. If it's a job number, compare the argument to + * the file name. In either case, if a match occurs, try to + * remove the file. + */ + + while (argc--) { + jobexists = 0; + for (i = 0; i < numjobs; ++i) { + + /* if the inode number is 0, this entry was removed */ + if (statlist[i]->st_ino == 0) + continue; + + /* + * if argv is a username, compare his/her uid to + * the uid of the owner of the file...... + */ + if (pwd = getpwnam(*argv)) { + if (statlist[i]->st_uid != pwd->pw_uid) + continue; + /* + * otherwise, we assume that the argv is a job # and + * thus compare argv to the file name. + */ + } else { + if (strcmp(namelist[i]->d_name,*argv)) + continue; + } + ++jobexists; + /* + * if the entry is ultimately removed, don't + * try to remove it again later. + */ + if (removentry(namelist[i]->d_name, statlist[i], user)) { + statlist[i]->st_ino = 0; + } + } + + /* + * If a requested argument doesn't exist, print a message. + */ + if (!jobexists && !fflag) { + fprintf(stderr, "atrm: %s: no such job number\n", *argv); + } + ++argv; + } + exit(0); +} + +/* + * Print usage info and exit. + */ +usage() +{ + fprintf(stderr,"usage: atrm [-f] [-i] [-a] [[job #] [user] ...]\n"); + exit(1); +} + + +/* + * Remove an entry from the queue. The access of the file is checked for + * write permission (since all jobs are mode 644). If access is granted, + * unlink the file. If the fflag (suppress announcements) is not set, + * print the job number that we are removing and the result of the access + * check (either "permission denied" or "removed"). If we are running + * interactively (iflag), prompt the user before we unlink the file. If + * the super-user is removing jobs, inform him/her who owns each file before + * it is removed. Return TRUE if file removed, else FALSE. + */ +int +removentry(filename,statptr,user) +char *filename; +register struct stat *statptr; +uid_t user; +{ + struct passwd *pwd; + char *pp; + char *getuser(); + int r; + + if (!fflag) + printf("%s: ",filename); + + if (user != statptr->st_uid && + !chkauthattr(CRONADMIN_AUTH, login_authchk)) { + + if (!fflag) { + printf("permission denied\n"); + } + return (0); + + } else { + if (iflag) { + if (chkauthattr(CRONADMIN_AUTH, login_authchk)) { + printf("\t(owned by "); + powner(filename); + printf(") "); + } + printf("remove it? "); + if (!yes()) + return (0); + } + + if (chkauthattr(CRONADMIN_AUTH, login_authchk)) { + pp = getuser((uid_t) statptr->st_uid); + if (pp == NULL) + atabort(INVALIDUSER); + if (strlcpy(login, pp, sizeof (login)) >= + sizeof (login)) + atabort(NAMETOOLONG); + } + cron_sendmsg(DELETE,login,filename,AT); + if ((r = unlink(filename)) < 0) { + if (!fflag) { + fputs("could not remove\n", stdout); + (void) fprintf(stderr, "atrm: %s: %s\n", + filename, errmsg(errno)); + } + audit_at_delete(filename, NULL, r); + return (0); + } + audit_at_delete(filename, NULL, r); + if (!fflag && !iflag) + printf("removed\n"); + return (1); + } +} + +/* + * Print the owner of the job. This is the owner of the spoolfile. + * If we run into trouble getting the name, we'll just print "???". + */ +powner(file) +char *file; +{ + struct stat statb; + char *getname(); + + if (stat(file,&statb) < 0) { + printf("%s","???"); + (void) fprintf(stderr,"atrm: Couldn't stat spoolfile %s: %s\n", + file, errmsg(errno)); + return(0); + } + + printf("%s",getname(statb.st_uid)); +} + + +int +getjoblist(namelistp, statlistp,sortfunc) + struct dirent ***namelistp; + struct stat ***statlistp; + int (*sortfunc)(); +{ + register int numjobs; + register struct dirent **namelist; + register int i; + register struct stat *statptr; /* pointer to file stat structure */ + register struct stat **statlist; + extern int alphasort(); /* sort jobs by date of execution */ + extern int filewanted(); /* should a file be listed in queue? */ + + if (chdir(ATDIR) < 0) + atabortperror(CANTCD); + + /* + * Get a list of the files in the spooling area. + */ + if ((numjobs = ascandir(".",namelistp,filewanted,sortfunc)) < 0) + atabortperror(NOREADDIR); + + if ((statlist = (struct stat **) malloc(numjobs * sizeof (struct stat ***))) == NULL) + atabort("Out of memory"); + + namelist = *namelistp; + + /* + * Build an array of pointers to the file stats for all jobs in + * the spooling area. + */ + for (i = 0; i < numjobs; ++i) { + statptr = (struct stat *) malloc(sizeof(struct stat)); + if (statptr == NULL) + atabort("Out of memory"); + if (stat(namelist[i]->d_name, statptr) < 0) { + atperror("Can't stat", namelist[i]->d_name); + continue; + } + statlist[i] = statptr; + } + + *statlistp = statlist; + return (numjobs); +} + + +/* + * Get answer to interactive prompts, eating all characters beyond the first + * one. If a 'y' is typed, return 1. + */ +yes() +{ + register int ch; /* dummy variable */ + register int ch1; /* dummy variable */ + + ch = ch1 = getchar(); + while (ch1 != '\n' && ch1 != EOF) + ch1 = getchar(); + if (isupper(ch)) + ch = tolower(ch); + return(ch == 'y'); +} + + +/* + * Get the full login name of a person using his/her user id. + */ +char * +getname(uid) +uid_t uid; +{ + register struct passwd *pwdinfo; /* password info structure */ + + + if ((pwdinfo = getpwuid(uid)) == 0) + return("???"); + return(pwdinfo->pw_name); +} + +aterror(msg) + char *msg; +{ + fprintf(stderr,"atrm: %s\n",msg); +} + +atperror(msg) + char *msg; +{ + fprintf(stderr,"atrm: %s: %s\n", msg, errmsg(errno)); +} + +atabort(msg) + char *msg; +{ + aterror(msg); + exit(1); +} + +atabortperror(msg) + char *msg; +{ + atperror(msg); + exit(1); +} diff --git a/usr/src/cmd/cron/att1.y b/usr/src/cmd/cron/att1.y new file mode 100644 index 0000000000..c11414c96a --- /dev/null +++ b/usr/src/cmd/cron/att1.y @@ -0,0 +1,227 @@ +%{ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 + * or http://www.opensolaris.org/os/licensing. + * 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +%{ +#ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.4 */ +%} +%{ + +#include "stdio.h" +#include "ctype.h" +#include "time.h" + +extern int yylex(void); +extern void yyerror(); +extern void atabort(char *); +int yyparse(void); +extern int gmtflag; +extern int mday[]; +extern struct tm *tp, at, rt; +%} +%token TIME +%token NOW +%token NOON +%token MIDNIGHT +%token MINUTE +%token HOUR +%token DAY +%token WEEK +%token MONTH +%token YEAR +%token UNIT +%token SUFF +%token ZONE +%token AM +%token PM +%token ZULU +%token NEXT +%token NUMB +%token COLON +%token COMMA +%token PLUS +%token UNKNOWN +%right NUMB +%% + +args + : time ampm timezone date incr { + if (at.tm_min >= 60 || at.tm_hour >= 24) + atabort("bad time"); + if (at.tm_mon >= 12 || at.tm_mday > mday[at.tm_mon]) + atabort("bad date"); + if (at.tm_year >= 1900) + at.tm_year -= 1900; + if (at.tm_year < 70 || at.tm_year >= 139) + atabort("bad year"); + } + | time ampm timezone date incr UNKNOWN { + yyerror(); + } + ; + +time + : hour { + at.tm_hour = $1; + } + | hour COLON number { + at.tm_hour = $1; + at.tm_min = $3; + $3 = $1; + } + | hour minute { + at.tm_hour = $1; + at.tm_min = $2; + $2 = $1; + } + | TIME { + switch ($1) { + case NOON: + at.tm_hour = 12; + break; + case MIDNIGHT: + at.tm_hour = 0; + break; + case NOW: + at.tm_hour = tp->tm_hour; + at.tm_min = tp->tm_min; + at.tm_sec = tp->tm_sec; + break; + } + } + ; + +ampm + : /*empty*/ + { + } + | SUFF { + switch ($1) { + case PM: + if (at.tm_hour < 1 || at.tm_hour > 12) + atabort("bad hour"); + at.tm_hour %= 12; + at.tm_hour += 12; + break; + case AM: + if (at.tm_hour < 1 || at.tm_hour > 12) + atabort("bad hour"); + at.tm_hour %= 12; + break; + } + } + ; + +timezone + : /*empty*/ + { + } + | ZONE { + if (at.tm_hour == 24 && at.tm_min != 0) + atabort("bad time"); + at.tm_hour %= 24; + gmtflag = 1; + } + ; + +date + : /*empty*/ { + at.tm_mday = tp->tm_mday; + at.tm_mon = tp->tm_mon; + at.tm_year = tp->tm_year; + if ((at.tm_hour < tp->tm_hour) + || ((at.tm_hour==tp->tm_hour)&&(at.tm_min<tp->tm_min))) + rt.tm_mday++; + } + | MONTH number { + at.tm_mon = $1; + at.tm_mday = $2; + at.tm_year = tp->tm_year; + if (at.tm_mon < tp->tm_mon) + at.tm_year++; + } + | MONTH number COMMA number { + at.tm_mon = $1; + at.tm_mday = $2; + at.tm_year = $4; + } + | DAY { + at.tm_mon = tp->tm_mon; + at.tm_mday = tp->tm_mday; + at.tm_year = tp->tm_year; + if ($1 < 7) { + rt.tm_mday = $1 - tp->tm_wday; + if (rt.tm_mday < 0) + rt.tm_mday += 7; + } else if ($1 == 8) + rt.tm_mday += 1; + } + ; + +incr + : /*empty*/ + | NEXT UNIT { addincr: + switch ($2) { + case MINUTE: + rt.tm_min += $1; + break; + case HOUR: + rt.tm_hour += $1; + break; + case DAY: + rt.tm_mday += $1; + break; + case WEEK: + rt.tm_mday += $1 * 7; + break; + case MONTH: + rt.tm_mon += $1; + break; + case YEAR: + rt.tm_year += $1; + break; + } + } + | PLUS opt_number UNIT { goto addincr; } + ; + +hour + : NUMB { $$ = $1; } + | NUMB NUMB { $$ = 10 * $1 + $2; } + ; +minute + : NUMB NUMB { $$ = 10 * $1 + $2; } + ; +number + : NUMB { $$ = $1; } + | number NUMB { $$ = 10 * $1 + $2; } + ; +opt_number + : /* empty */ { $$ = 1; } + | number { $$ = $1; } + ; + +%% diff --git a/usr/src/cmd/cron/att2.ed b/usr/src/cmd/cron/att2.ed new file mode 100644 index 0000000000..ded3fd6905 --- /dev/null +++ b/usr/src/cmd/cron/att2.ed @@ -0,0 +1,6 @@ +/feof(yyin)/p +s?/\*?? +s?\*/?? +p +w +q diff --git a/usr/src/cmd/cron/att2.l b/usr/src/cmd/cron/att2.l new file mode 100644 index 0000000000..fcfdaada0a --- /dev/null +++ b/usr/src/cmd/cron/att2.l @@ -0,0 +1,90 @@ +%{ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 + * or http://www.opensolaris.org/os/licensing. + * 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +%} +%{ +/* All Rights Reserved */ +%} + +%{ +#ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.2 */ +%} + +%{ + +#include "att1.h" +#include <ctype.h> +#define LL(t,v) return(yylval = v, t) +#undef getc +#define getc(x) (*argp?tolower(*argp++):EOF) +#undef feof +#define feof(x) (*argp?0:1) +extern int yylval; +char *argp = ""; +%} +%% +[ \t\n]+ ; +"jan"|"january" { LL(MONTH, 0); } +"feb"|"february" { LL(MONTH, 1); } +"mar"|"march" { LL(MONTH, 2); } +"apr"|"april" { LL(MONTH, 3); } +"may" { LL(MONTH, 4); } +"jun"|"june" { LL(MONTH, 5); } +"jul"|"july" { LL(MONTH, 6); } +"aug"|"august" { LL(MONTH, 7); } +"sep"|"september" { LL(MONTH, 8); } +"oct"|"october" { LL(MONTH, 9); } +"nov"|"november" { LL(MONTH, 10); } +"dec"|"december" { LL(MONTH, 11); } +"sun"|"sunday" { LL(DAY, 0); } +"mon"|"monday" { LL(DAY, 1); } +"tue"|"tuesday" { LL(DAY, 2); } +"wed"|"wednesday" { LL(DAY, 3); } +"thu"|"thursday" { LL(DAY, 4); } +"fri"|"friday" { LL(DAY, 5); } +"sat"|"saturday" { LL(DAY, 6); } +"today" { LL(DAY, 7); } +"tomorrow" { LL(DAY, 8); } +"noon" { LL(TIME, NOON); } +"midnight" { LL(TIME, MIDNIGHT); } +"now" { LL(TIME, NOW); } +"am" { LL(SUFF, AM); } +"pm" { LL(SUFF, PM); } +"zulu" { LL(ZONE, ZULU); } +"utc" { LL(ZONE, ZULU); } +"gmt" { LL(ZONE, ZULU); } +"next" { LL(NEXT, 1); } +"min"s?|"minute"s? { LL(UNIT, MINUTE); } +"hour"s? { LL(UNIT, HOUR); } +"day"s? { LL(UNIT, DAY); } +"week"s? { LL(UNIT, WEEK); } +"month"s? { LL(UNIT, MONTH); } +"year"s? { LL(UNIT, YEAR); } +[0-9] { yylval = yytext[0] - '0'; return(NUMB); } +[:] { LL(COLON, 0); } +[,] { LL(COMMA, 0); } +[+] { LL(PLUS, 0); } +. { LL(UNKNOWN, 0); } +%% diff --git a/usr/src/cmd/cron/batch.sh b/usr/src/cmd/cron/batch.sh new file mode 100644 index 0000000000..a47bb370f4 --- /dev/null +++ b/usr/src/cmd/cron/batch.sh @@ -0,0 +1,40 @@ +#!/usr/bin/sh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (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 +# or http://www.opensolaris.org/os/licensing. +# 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T +# All Rights Reserved + +# +# Copyright 2005 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. + +#ident "%Z%%M% %I% %E% SMI" + +set -- `getopt p: $*` +if [ $? != 0 ]; then + TEXTDOMAIN=SUNW_OST_OSCMD + export TEXTDOMAIN + /usr/bin/gettext "Usage: batch [-p project]\n" >&2 + exit 2 +fi + +exec /usr/bin/at -qb $* diff --git a/usr/src/cmd/cron/batch.xpg4.sh b/usr/src/cmd/cron/batch.xpg4.sh new file mode 100644 index 0000000000..1d304e6737 --- /dev/null +++ b/usr/src/cmd/cron/batch.xpg4.sh @@ -0,0 +1,40 @@ +#!/usr/bin/sh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (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 +# or http://www.opensolaris.org/os/licensing. +# 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T +# All Rights Reserved + +# +# Copyright 2005 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. + +#ident "%Z%%M% %I% %E% SMI" + +set -- `getopt p: $*` +if [ $? != 0 ]; then + TEXTDOMAIN=SUNW_OST_OSCMD + export TEXTDOMAIN + /usr/bin/gettext "Usage: batch [-p project]\n" >&2 + exit 2 +fi + +exec /usr/xpg4/bin/at -qb -m $* diff --git a/usr/src/cmd/cron/cron.c b/usr/src/cmd/cron/cron.c new file mode 100644 index 0000000000..f29c1917ef --- /dev/null +++ b/usr/src/cmd/cron/cron.c @@ -0,0 +1,3312 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 + * or http://www.opensolaris.org/os/licensing. + * 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 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + +/* Copyright (c) 1987, 1988 Microsoft Corporation */ +/* All Rights Reserved */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef lint +/* make lint happy */ +#define __EXTENSIONS__ +#endif + +#include <sys/contract/process.h> +#include <sys/ctfs.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/task.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/utsname.h> +#include <sys/wait.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 "cron.h" + +/* + * #define DEBUG + */ + +#define MAIL "/usr/bin/mail" /* mail program to use */ +#define CONSOLE "/dev/console" /* where messages go when cron dies */ + +#define TMPINFILE "/tmp/crinXXXXXX" /* file to put stdin in for cmd */ +#define TMPDIR "/tmp" +#define PFX "crout" +#define TMPOUTFILE "/tmp/croutXXXXXX" /* file to place stdout, stderr */ + +#define INMODE 00400 /* mode for stdin file */ +#define OUTMODE 00600 /* mode for stdout file */ +#define ISUID S_ISUID /* mode for verifing at jobs */ + +#define INFINITY 2147483647L /* upper bound on time */ +#define CUSHION 180L +#define MAXRUN 100 /* max total jobs allowed in system */ +#define ZOMB 100 /* proc slot used for mailing output */ + +#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 \ +isn't /usr/bin/sh, you can't use cron." + +#define BADSTAT "can't access your crontab file. Resubmit it." +#define BADPROJID "can't set project id for your job." +#define CANTCDHOME "can't change directory to your home directory.\ +\nYour commands will not be executed." +#define CANTEXECSH "unable to exec the shell for one of your commands." +#define NOREAD "can't read your crontab file. Resubmit it." +#define BADTYPE "crontab 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 CRONOUT "Subject: Output from \"cron\" command\n\n" +#define MALLOCERR "out of space, cannot create new string\n" + +#define DIDFORK didfork +#define NOFORK !didfork + +#define MAILBUFLEN (8*1024) +#define LINELIMIT 80 +#define MAILBINITFREE (MAILBUFLEN - (sizeof (cte_intro) - 1) \ + - (sizeof (cte_trail1) - 1) - (sizeof (cte_trail2) - 1) - 1) + +#define ERR_CRONTABENT 0 /* error in crontab file entry */ +#define ERR_UNIXERR 1 /* error in some system call */ +#define ERR_CANTEXECCRON 2 /* error setting up "cron" job environment */ +#define ERR_CANTEXECAT 3 /* error setting up "at" job environment */ + +#define PROJECT "project=" + +#define MAX_LOST_CONTRACTS 2048 /* reset if this many failed abandons */ + +#define FORMAT "%a %b %e %H:%M:%S %Y" +static char timebuf[80]; + +struct event { + time_t time; /* time of the 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 */ + struct event *link; /* ptr to another event for this user */ + 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 */ + uid_t uid; /* user id */ + gid_t gid; /* group id */ + 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 */ + struct event *ctevents; /* list of this usr's crontab events */ + struct event *atevents; /* list of this usr's at events */ + struct usr *nextusr; +}; /* 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 */ +} + qd = {100, 2, 60}, /* default values for queue defs */ + qt[NQUEUE]; +static struct queue qq; + +struct runinfo +{ + pid_t pid; + short que; + struct usr *rusr; /* pointer to usr struct */ + 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 */ +} rt[MAXRUN]; + +static struct miscpid { + pid_t pid; + struct miscpid *next; +} *miscpid_head; + +static pid_t cron_pid; /* own pid */ +static char didfork = 0; /* flag to see if I'm process group leader */ +static int msgfd; /* file descriptor for fifo queue */ +static int ecid = 1; /* event class id for el_remove(); MUST be set to 1 */ +static int delayed; /* is job being rescheduled or did it run first time */ +static int cwd; /* current working directory */ +static struct event *next_event; /* the next event to execute */ +static struct usr *uhead; /* ptr to the list of users */ + +/* 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 int cte_free = MAILBINITFREE; /* Free buffer space */ +static char *cte_text = NULL; /* Text buffer pointer */ +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 ROOTPATH "PATH=/usr/sbin:/usr/bin" +#define NONROOTPATH "PATH=/usr/bin:" + +static char *Def_supath = NULL; +static char *Def_path = NULL; +static char path[LINE_MAX] = "PATH="; +static char supath[LINE_MAX] = "PATH="; +static char homedir[LINE_MAX] = "HOME="; +static char logname[LINE_MAX] = "LOGNAME="; +static char tzone[LINE_MAX] = "TZ="; +static char *envinit[] = { + homedir, + logname, + ROOTPATH, + "SHELL=/usr/bin/sh", + tzone, + NULL +}; + +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 dscan(DIR *dir, void (*fp)(char *, time_t)); +static void mod_ctab(char *, time_t); +static void mod_atjob(char *, time_t); +static void add_atevent(struct usr *, char *, time_t, int); +static void rm_ctevents(struct usr *); +static void cleanup(struct runinfo *rn, int r); +static void crabort(char *, int); +static void msg(char *fmt, ...); +static void logit(int, struct runinfo *, int); +static void parsqdef(char *); +static void defaults(); +static void initialize(int); +static void quedefs(int); +static int idle(long); +static struct usr *find_usr(char *); +static int ex(struct event *e); +static void read_dirs(void); +static void mail(char *, char *, int); +static char *next_field(int, int); +static void readcron(struct usr *, time_t); +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 struct runinfo *rinfo_get(pid_t); +static void rinfo_free(struct runinfo *rp); +static void mail_result(struct usr *p, struct runinfo *pr, size_t filesize); +static time_t next_time(struct event *, time_t); +static time_t get_switching_time(int, time_t); +static time_t xmktime(struct tm *); +static void process_msg(struct message *, time_t); +static void reap_child(void); +static void miscpid_insert(pid_t); +static int miscpid_delete(pid_t); +static int contract_set_template(void); +static int 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 *); + +static int set_user_cred(const struct usr *, struct project *); + +/* + * 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 time_t last_time, init_time, t_old; + +static int accept_sigcld, notifypipe[2]; +static sigset_t defmask, childmask; + +/* + * BSM hooks + */ +extern int audit_cron_session(char *, char *, uid_t, gid_t, char *); +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 *); + +static struct pam_conv pam_conv = {cron_conv, NULL}; +static pam_handle_t *pamh; /* Authentication handle */ + +/* + * Function to help check a user's credentials. + */ + +static int verify_user_cred(const 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_init(int, time_t, time_t, int); +extern void el_add(void *, time_t, int); +extern void el_remove(int, int); +extern int el_empty(void); +extern void *el_first(void); +extern void el_delete(void); + +int +main(int argc, char *argv[]) +{ + time_t t; + time_t ne_time; /* amt of time until next event execution */ + time_t newtime, lastmtime = 0L; + struct usr *u; + struct event *e, *e2, *eprev; + struct stat buf; + pid_t rfork; + struct sigaction act; + + /* + * reset is set to 1 via the return from ex() should ex() find + * that the event to be executed is being run at the wrong time. + * We immediately return to the top of the while (TRUE) loop in + * main() where the event list is cleared and rebuilt, and reset + * is set back to 0. + */ + int reset = 0; + + /* + * Only the privileged user can run this command. + */ + if (getuid() != 0) + crabort(NOTALLOWED, 0); + +begin: + (void) setlocale(LC_ALL, ""); + /* fork unless 'nofork' is specified */ + if ((argc <= 1) || (strcmp(argv[1], "nofork"))) { + if (rfork = fork()) { + if (rfork == (pid_t)-1) { + (void) sleep(30); + goto begin; + } + return (0); + } + didfork++; + (void) setpgrp(); /* detach cron from console */ + } + + (void) umask(022); + (void) signal(SIGHUP, SIG_IGN); + (void) signal(SIGINT, SIG_IGN); + (void) signal(SIGQUIT, SIG_IGN); + (void) signal(SIGTERM, cronend); + + defaults(); + initialize(1); + quedefs(DEFAULT); /* load default queue definitions */ + cron_pid = getpid(); + msg("*** cron started *** pid = %d", cron_pid); + (void) sigset(SIGTHAW, thaw_handler); + /* + * setup SIGCLD handler/mask + */ + act.sa_handler = child_handler; + act.sa_flags = 0; + (void) sigemptyset(&act.sa_mask); + (void) sigaddset(&act.sa_mask, SIGCLD); + (void) sigaction(SIGCLD, &act, NULL); + (void) sigemptyset(&childmask); + (void) sigaddset(&childmask, SIGCLD); + (void) sigprocmask(SIG_BLOCK, &childmask, &defmask); + + if (pipe(notifypipe) != 0) { + crabort("cannot create pipe", REMOVE_FIFO|CONSOLE_MSG); + } + /* + * will set O_NONBLOCK, so that the write() from child_handler + * never be blocked. + */ + (void) fcntl(notifypipe[0], F_SETFL, O_WRONLY|O_NONBLOCK); + + t_old = time(NULL); + last_time = t_old; + for (;;) { /* MAIN LOOP */ + t = time(NULL); + if ((t_old > t) || (t-last_time > CUSHION) || reset) { + reset = 0; + /* the time was set backwards or forward */ + el_delete(); + u = uhead; + while (u != NULL) { + rm_ctevents(u); + e = u->atevents; + while (e != NULL) { + free(e->cmd); + e2 = e->link; + free(e); + e = e2; + } + u->atevents = NULL; + u = u->nextusr; + } + (void) close(msgfd); + initialize(0); + t = time(NULL); + last_time = t; + } + t_old = t; + + if (next_event == NULL && !el_empty()) { + next_event = (struct event *)el_first(); + } + if (next_event == NULL) { + ne_time = INFINITY; + } else { + ne_time = next_event->time - t; +#ifdef DEBUG + cftime(timebuf, "%C", &next_event->time); + (void) fprintf(stderr, "next_time=%ld %s\n", + next_event->time, timebuf); +#endif + } + if (ne_time > 0) { + if ((reset = idle(ne_time)) != 0) + continue; + } + + if (stat(QUEDEFS, &buf)) { + msg("cannot stat QUEDEFS file"); + } else if (lastmtime != buf.st_mtime) { + quedefs(LOAD); + lastmtime = buf.st_mtime; + } + + last_time = next_event->time; /* save execution time */ + + if (reset = ex(next_event)) + 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. + */ + + if (last_time > time(NULL)) { + msg(CLOCK_DRIFT); + /* + * bump up to next 30 second + * increment + * 1 <= newtime <= 30 + */ + newtime = 30 - (last_time % 30); + newtime += last_time; + + /* + * get the next scheduled event, + * not the one that we just + * kicked off! + */ + next_event->time = + next_time(next_event, newtime); + t_old = time(NULL); + } else { + next_event->time = + next_time(next_event, (time_t)0); + } +#ifdef DEBUG + cftime(timebuf, "%C", &next_event->time); + (void) fprintf(stderr, + "pushing back cron event %s at %ld (%s)\n", + next_event->cmd, next_event->time, timebuf); +#endif + + el_add(next_event, next_event->time, + (next_event->u)->ctid); + break; + default: + /* remove at or batch job from system */ + if (delayed) { + delayed = 0; + break; + } + eprev = NULL; + e = (next_event->u)->atevents; + while (e != NULL) { + if (e == next_event) { + if (eprev == NULL) + (e->u)->atevents = e->link; + else + eprev->link = e->link; + free(e->cmd); + free(e); + break; + } else { + eprev = e; + e = e->link; + } + } + break; + } + next_event = NULL; + } + + /*NOTREACHED*/ +} + +static void +initialize(int firstpass) +{ +#ifdef DEBUG + (void) fprintf(stderr, "in initialize\n"); +#endif + init_time = time(NULL); + el_init(8, init_time, (time_t)(60*60*24), 10); + if (firstpass) { + /* for mail(1), make sure messages come from root */ + if (putenv("LOGNAME=root") != 0) { + crabort("cannot expand env variable", + REMOVE_FIFO|CONSOLE_MSG); + } + if (access(FIFO, R_OK) == -1) { + if (errno == ENOENT) { + if (mknod(FIFO, S_IFIFO|0600, 0) != 0) + crabort("cannot create fifo queue", + REMOVE_FIFO|CONSOLE_MSG); + } else { + if (NOFORK) { + /* didn't fork... init(1M) is waiting */ + (void) sleep(60); + } + perror("FIFO"); + crabort("cannot access fifo queue", + REMOVE_FIFO|CONSOLE_MSG); + } + } 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 + */ + } + crabort("cannot start cron; FIFO exists", CONSOLE_MSG); + } + } + + if ((msgfd = open(FIFO, O_RDWR)) < 0) { + perror("! open"); + crabort("cannot open fifo queue", REMOVE_FIFO|CONSOLE_MSG); + } + + /* + * read directories, create users list, and add events to the + * main event list. Only zero user list on firstpass. + */ + if (firstpass) + uhead = NULL; + read_dirs(); + next_event = NULL; + + if (!firstpass) + return; + + /* stdout is log file */ + if (freopen(ACCTFILE, "a", stdout) == NULL) + (void) fprintf(stderr, "cannot open %s\n", ACCTFILE); + + /* log should be root-only */ + (void) fchmod(1, S_IRUSR|S_IWUSR); + + /* stderr also goes to ACCTFILE */ + (void) close(fileno(stderr)); + (void) dup(1); + + /* null for stdin */ + (void) freopen("/dev/null", "r", stdin); + + contract_set_template(); +} + +static void +read_dirs() +{ + DIR *dir; + + if (chdir(CRONDIR) == -1) + crabort(BADCD, REMOVE_FIFO|CONSOLE_MSG); + cwd = CRON; + if ((dir = opendir(".")) == NULL) + crabort(NOREADDIR, REMOVE_FIFO|CONSOLE_MSG); + dscan(dir, mod_ctab); + (void) closedir(dir); + if (chdir(ATDIR) == -1) { + msg("cannot chdir to at directory"); + return; + } + cwd = AT; + if ((dir = opendir(".")) == NULL) { + msg("cannot read at at directory"); + return; + } + dscan(dir, mod_atjob); + (void) closedir(dir); +} + +static void +dscan(DIR *df, void (*fp)(char *, time_t)) +{ + struct dirent *dp; + + while ((dp = readdir(df)) != NULL) { + if (strcmp(dp->d_name, ".") == 0 || + strcmp(dp->d_name, "..") == 0) { + continue; + } + (*fp)(dp->d_name, 0); + } +} + +static void +mod_ctab(char *name, time_t reftime) +{ + struct passwd *pw; + struct stat buf; + struct usr *u; + char namebuf[PATH_MAX]; + char *pname; + + /* skip over ancillary file names */ + if (audit_cron_is_anc_name(name)) + return; + + if ((pw = getpwnam(name)) == NULL) { + msg("No such user as %s - cron entries not created", name); + return; + } + if (cwd != CRON) { + if (snprintf(namebuf, sizeof (namebuf), "%s/%s", + CRONDIR, name) >= sizeof (namebuf)) { + msg("Too long path name %s - cron entries not created", + namebuf); + return; + } + pname = namebuf; + } else { + pname = name; + } + /* + * 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 + * users with a login shell of /usr/bin/sh to use cron + */ +#ifdef BOURNESHELLONLY + if ((strcmp(pw->pw_shell, "") != 0) && + (strcmp(pw->pw_shell, SHELL) != 0)) { + mail(name, BADSHELL, ERR_CANTEXECCRON); + cron_unlink(pname); + return; + } +#endif + if (stat(pname, &buf)) { + mail(name, BADSTAT, ERR_UNIXERR); + cron_unlink(pname); + return; + } + if (!S_ISREG(buf.st_mode)) { + mail(name, BADTYPE, ERR_CRONTABENT); + return; + } + if ((u = find_usr(name)) == NULL) { +#ifdef DEBUG + (void) fprintf(stderr, "new user (%s) with a crontab\n", name); +#endif + u = xmalloc(sizeof (struct usr)); + u->name = xmalloc(strlen(name)+1); + (void) strcpy(u->name, name); + u->home = xmalloc(strlen(pw->pw_dir)+1); + (void) strcpy(u->home, pw->pw_dir); + u->uid = pw->pw_uid; + u->gid = pw->pw_gid; + u->ctexists = TRUE; + u->ctid = ecid++; + u->ctevents = NULL; + u->atevents = NULL; + u->aruncnt = 0; + u->cruncnt = 0; + u->nextusr = uhead; + uhead = u; + readcron(u, reftime); + } else { + u->uid = pw->pw_uid; + u->gid = pw->pw_gid; + if (strcmp(u->home, pw->pw_dir) != 0) { + free(u->home); + u->home = xmalloc(strlen(pw->pw_dir)+1); + (void) strcpy(u->home, pw->pw_dir); + } + u->ctexists = TRUE; + if (u->ctid == 0) { +#ifdef DEBUG + (void) fprintf(stderr, "%s now has a crontab\n", + u->name); +#endif + /* user didnt have a crontab last time */ + u->ctid = ecid++; + u->ctevents = NULL; + readcron(u, reftime); + return; + } +#ifdef DEBUG + (void) fprintf(stderr, "%s has revised his crontab\n", u->name); +#endif + rm_ctevents(u); + el_remove(u->ctid, 0); + readcron(u, reftime); + } +} + +/* ARGSUSED */ +static void +mod_atjob(char *name, time_t reftime) +{ + char *ptr; + time_t tim; + struct passwd *pw; + struct stat buf; + struct usr *u; + struct event *e; + char namebuf[PATH_MAX]; + char *pname; + int jobtype; + + ptr = name; + if (((tim = num(&ptr)) == 0) || (*ptr != '.')) + return; + ptr++; + if (!isalpha(*ptr)) + return; + jobtype = *ptr - 'a'; + + /* check for audit ancillary file */ + if (audit_cron_is_anc_name(name)) + return; + + if (cwd != AT) { + if (snprintf(namebuf, sizeof (namebuf), "%s/%s", ATDIR, name) + >= sizeof (namebuf)) { + return; + } + pname = namebuf; + } else { + pname = name; + } + if (stat(pname, &buf) || jobtype >= NQUEUE) { + cron_unlink(pname); + return; + } + if (!(buf.st_mode & ISUID) || !S_ISREG(buf.st_mode)) { + cron_unlink(pname); + return; + } + if ((pw = getpwuid(buf.st_uid)) == NULL) { + cron_unlink(pname); + 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 + * users with a login shell of /usr/bin/sh to use cron + */ +#ifdef BOURNESHELLONLY + if ((strcmp(pw->pw_shell, "") != 0) && + (strcmp(pw->pw_shell, SHELL) != 0)) { + mail(pw->pw_name, BADSHELL, ERR_CANTEXECAT); + cron_unlink(pname); + return; + } +#endif + if ((u = find_usr(pw->pw_name)) == NULL) { +#ifdef DEBUG + (void) fprintf(stderr, "new user (%s) with an at job = %s\n", + pw->pw_name, name); +#endif + u = xmalloc(sizeof (struct usr)); + u->name = xmalloc(strlen(pw->pw_name)+1); + (void) strcpy(u->name, pw->pw_name); + u->home = xmalloc(strlen(pw->pw_dir)+1); + (void) strcpy(u->home, pw->pw_dir); + u->uid = pw->pw_uid; + u->gid = pw->pw_gid; + u->ctexists = FALSE; + u->ctid = 0; + u->ctevents = NULL; + u->atevents = NULL; + u->aruncnt = 0; + u->cruncnt = 0; + u->nextusr = uhead; + uhead = u; + add_atevent(u, name, tim, jobtype); + } else { + u->uid = pw->pw_uid; + u->gid = pw->pw_gid; + if (strcmp(u->home, pw->pw_dir) != 0) { + free(u->home); + u->home = xmalloc(strlen(pw->pw_dir)+1); + (void) strcpy(u->home, pw->pw_dir); + } + e = u->atevents; + while (e != NULL) { + if (strcmp(e->cmd, name) == 0) { + e->of.at.exists = TRUE; + break; + } else { + e = e->link; + } + } + if (e == NULL) { +#ifdef DEBUG + (void) fprintf(stderr, "%s has a new at job = %s\n", + u->name, name); +#endif + add_atevent(u, name, tim, jobtype); + } + } +} + +static void +add_atevent(struct usr *u, char *job, time_t tim, int jobtype) +{ + struct event *e; + + e = xmalloc(sizeof (struct event)); + e->etype = jobtype; + e->cmd = xmalloc(strlen(job)+1); + (void) strcpy(e->cmd, job); + e->u = u; + e->link = u->atevents; + u->atevents = e; + e->of.at.exists = TRUE; + e->of.at.eventid = ecid++; + if (tim < init_time) /* old job */ + e->time = init_time; + else + e->time = tim; +#ifdef DEBUG + (void) fprintf(stderr, "add_atevent: user=%s, job=%s, time=%ld\n", + u->name, e->cmd, e->time); +#endif + el_add(e, e->time, e->of.at.eventid); +} + + +static char line[CTLINESIZE]; /* holds a line from a crontab file */ +static int cursor; /* cursor for the above line */ + +static void +readcron(struct usr *u, time_t reftime) +{ + /* + * 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. + */ + FILE *cf; /* cf will be a user's crontab file */ + struct event *e; + int start; + unsigned int i; + char namebuf[PATH_MAX]; + char *pname; + int lineno = 0; + + /* read the crontab file */ + cte_init(); /* Init error handling */ + if (cwd != CRON) { + if (snprintf(namebuf, sizeof (namebuf), "%s/%s", + CRONDIR, u->name) >= sizeof (namebuf)) { + return; + } + pname = namebuf; + } else { + pname = u->name; + } + if ((cf = fopen(pname, "r")) == NULL) { + mail(u->name, NOREAD, ERR_UNIXERR); + return; + } + while (fgets(line, CTLINESIZE, cf) != NULL) { + /* process a line of a crontab file */ + lineno++; + if (cte_istoomany()) + break; + cursor = 0; + while (line[cursor] == ' ' || line[cursor] == '\t') + cursor++; + if (line[cursor] == '#' || line[cursor] == '\n') + continue; + e = xmalloc(sizeof (struct event)); + e->etype = CRONEVENT; + if (!(((e->of.ct.minute = next_field(0, 59)) != NULL) && + ((e->of.ct.hour = next_field(0, 23)) != NULL) && + ((e->of.ct.daymon = next_field(1, 31)) != NULL) && + ((e->of.ct.month = next_field(1, 12)) != NULL) && + ((e->of.ct.dayweek = next_field(0, 6)) != NULL))) { + free(e); + cte_add(lineno, line); + continue; + } + while (line[cursor] == ' ' || line[cursor] == '\t') + cursor++; + if (line[cursor] == '\n' || line[cursor] == '\0') + continue; + /* get the command to execute */ + start = cursor; +again: + while ((line[cursor] != '%') && + (line[cursor] != '\n') && + (line[cursor] != '\0') && + (line[cursor] != '\\')) + cursor++; + if (line[cursor] == '\\') { + cursor += 2; + goto again; + } + e->cmd = xmalloc(cursor-start+1); + (void) strncpy(e->cmd, line+start, cursor-start); + e->cmd[cursor-start] = '\0'; + /* see if there is any standard input */ + if (line[cursor] == '%') { + e->of.ct.input = xmalloc(strlen(line)-cursor+1); + (void) strcpy(e->of.ct.input, line+cursor+1); + for (i = 0; i < strlen(e->of.ct.input); i++) { + if (e->of.ct.input[i] == '%') + e->of.ct.input[i] = '\n'; + } + } else { + e->of.ct.input = NULL; + } + /* have the event point to it's owner */ + e->u = u; + /* insert this event at the front of this user's event list */ + e->link = u->ctevents; + u->ctevents = e; + /* set the time for the first occurance of this event */ + e->time = next_time(e, reftime); + /* finally, add this event to the main event list */ + el_add(e, e->time, u->ctid); + cte_valid(); +#ifdef DEBUG + cftime(timebuf, "%C", &e->time); + (void) fprintf(stderr, "inserting cron event %s at %ld (%s)\n", + e->cmd, e->time, timebuf); +#endif + } + cte_sendmail(u->name); /* mail errors if any to user */ + (void) fclose(cf); +} + +/* + * 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() +{ + if (cte_text == NULL) + cte_text = xmalloc(MAILBUFLEN); + (void) strlcpy(cte_text, cte_intro, MAILBUFLEN); + cte_lp = cte_text + sizeof (cte_intro) - 1; + cte_free = MAILBINITFREE; + cte_nvalid = 0; +} + +static void +cte_add(int lineno, char *ctline) +{ + int len; + char *p; + + if (cte_free >= LINELIMIT) { + (void) sprintf(cte_lp, "%4d: ", lineno); + (void) strlcat(cte_lp, ctline, LINELIMIT - 1); + len = strlen(cte_lp); + if (cte_lp[len - 1] != '\n') { + cte_lp[len++] = '\n'; + cte_lp[len] = '\0'; + } + for (p = cte_lp; *p; p++) { + if (isprint(*p) || *p == '\n' || *p == '\t') + continue; + *p = '.'; + } + cte_lp += len; + cte_free -= len; + if (cte_free < LINELIMIT) { + size_t buflen = MAILBUFLEN - (cte_lp - cte_text); + (void) strlcpy(cte_lp, cte_trail1, buflen); + if (cte_nvalid == 0) + (void) strlcat(cte_lp, cte_trail2, buflen); + } + } +} + +static void +cte_valid() +{ + cte_nvalid++; +} + +static int +cte_istoomany() +{ + /* + * Return TRUE only if all lines are faulty. So evaluation of + * a crontab is not aborted if at least one valid line was found. + */ + return (cte_nvalid == 0 && cte_free < LINELIMIT); +} + +static void +cte_sendmail(char *username) +{ + if (cte_free < MAILBINITFREE) + mail(username, cte_text, ERR_CRONTABENT); +} + +/* + * Send mail with error message to a user + */ +static void +mail(char *usrname, char *mesg, int format) +{ + /* mail mails a user a message. */ + FILE *pipe; + char *temp; + struct passwd *ruser_ids; + pid_t fork_val; + int saveerrno = errno; + struct utsname name; + +#ifdef TESTING + return; +#endif + (void) uname(&name); + if ((fork_val = fork()) == (pid_t)-1) { + msg("cron cannot fork\n"); + return; + } + if (fork_val == 0) { + child_sigreset(); + contract_clear_template(); + if ((ruser_ids = getpwnam(usrname)) == NULL) + exit(0); + (void) setuid(ruser_ids->pw_uid); + temp = xmalloc(strlen(MAIL)+strlen(usrname)+2); + (void) sprintf(temp, "%s %s", MAIL, usrname); + pipe = popen(temp, "w"); + if (pipe != NULL) { + (void) fprintf(pipe, "To: %s\n", usrname); + switch (format) { + case ERR_CRONTABENT: + (void) fprintf(pipe, CRONTABERR); + (void) fprintf(pipe, "Your \"crontab\" on %s\n", + name.nodename); + (void) fprintf(pipe, mesg); + (void) fprintf(pipe, + "\nEntries or crontab have been ignored\n"); + break; + case ERR_UNIXERR: + (void) fprintf(pipe, "Subject: %s\n\n", mesg); + (void) fprintf(pipe, + "The error on %s was \"%s\"\n", + name.nodename, errmsg(saveerrno)); + break; + + case ERR_CANTEXECCRON: + (void) fprintf(pipe, + "Subject: Couldn't run your \"cron\" job\n\n"); + (void) fprintf(pipe, + "Your \"cron\" job on %s ", name.nodename); + (void) fprintf(pipe, "couldn't be run\n"); + (void) fprintf(pipe, "%s\n", mesg); + (void) fprintf(pipe, + "The error was \"%s\"\n", errmsg(saveerrno)); + break; + + case ERR_CANTEXECAT: + (void) fprintf(pipe, + "Subject: Couldn't run your \"at\" job\n\n"); + (void) fprintf(pipe, "Your \"at\" job on %s ", + name.nodename); + (void) fprintf(pipe, "couldn't be run\n"); + (void) fprintf(pipe, "%s\n", mesg); + (void) fprintf(pipe, + "The error was \"%s\"\n", errmsg(saveerrno)); + break; + + default: + break; + } + (void) pclose(pipe); + } + free(temp); + exit(0); + } + + contract_abandon_latest(fork_val); + + if (cron_pid == getpid()) { + miscpid_insert(fork_val); + } +} + +static char * +next_field(int lower, int upper) +{ + /* + * 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; + int num, num2, start; + + while ((line[cursor] == ' ') || (line[cursor] == '\t')) + cursor++; + start = cursor; + if (line[cursor] == '\0') { + return (NULL); + } + if (line[cursor] == '*') { + cursor++; + if ((line[cursor] != ' ') && (line[cursor] != '\t')) + return (NULL); + s = xmalloc(2); + (void) strcpy(s, "*"); + return (s); + } + for (;;) { + if (!isdigit(line[cursor])) + return (NULL); + num = 0; + do { + num = num*10 + (line[cursor]-'0'); + } while (isdigit(line[++cursor])); + if ((num < lower) || (num > upper)) + return (NULL); + if (line[cursor] == '-') { + if (!isdigit(line[++cursor])) + return (NULL); + num2 = 0; + do { + num2 = num2*10 + (line[cursor]-'0'); + } while (isdigit(line[++cursor])); + if ((num2 < lower) || (num2 > upper)) + return (NULL); + } + if ((line[cursor] == ' ') || (line[cursor] == '\t')) + break; + if (line[cursor] == '\0') + return (NULL); + if (line[cursor++] != ',') + return (NULL); + } + s = xmalloc(cursor-start+1); + (void) strncpy(s, line+start, cursor-start); + s[cursor-start] = '\0'; + return (s); +} + +#define tm_cmp(t1, t2) (\ + (t1)->tm_year == (t2)->tm_year && \ + (t1)->tm_mon == (t2)->tm_mon && \ + (t1)->tm_mday == (t2)->tm_mday && \ + (t1)->tm_hour == (t2)->tm_hour && \ + (t1)->tm_min == (t2)->tm_min) + +#define tm_setup(tp, yr, mon, dy, hr, min, dst) \ + (tp)->tm_year = yr; \ + (tp)->tm_mon = mon; \ + (tp)->tm_mday = dy; \ + (tp)->tm_hour = hr; \ + (tp)->tm_min = min; \ + (tp)->tm_isdst = dst; \ + (tp)->tm_sec = 0; \ + (tp)->tm_wday = 0; \ + (tp)->tm_yday = 0; + +/* + * 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 +next_time(struct event *e, time_t tflag) +{ + /* + * 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. + */ + + struct tm *tm, ref_tm, tmp, tmp1, tmp2; + int tm_mon, tm_mday, tm_wday, wday, m, min, h, hr, carry, day, days, + d1, day1, carry1, d2, day2, carry2, daysahead, mon, yr, db, wd, today; + time_t t, ref_t, t1, t2, zone_start; + int fallback; + extern int days_btwn(int, int, int, int, int, int); + + if (tflag == 0) { + t = time(NULL); /* original way of doing things */ + } else { + t = tflag; + } + + tm = &ref_tm; /* use a local variable and call localtime_r() */ + ref_t = t; /* keep a copy of the reference time */ + +recalc: + fallback = 0; + + (void) localtime_r(&t, tm); + + if (daylight) { + tmp = *tm; + tmp.tm_isdst = (tm->tm_isdst > 0 ? 0 : 1); + t1 = xmktime(&tmp); + /* + * 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). + */ + if (tm->tm_isdst != tmp.tm_isdst && + (t1 - t) == (timezone - altzone) && + tm_cmp(tm, &tmp)) { + zone_start = get_switching_time(tmp.tm_isdst, t); + fallback = 1; + } + } + + tm_mon = next_ge(tm->tm_mon+1, e->of.ct.month) - 1; /* 0-11 */ + tm_mday = next_ge(tm->tm_mday, e->of.ct.daymon); /* 1-31 */ + tm_wday = next_ge(tm->tm_wday, e->of.ct.dayweek); /* 0-6 */ + today = TRUE; + if ((strcmp(e->of.ct.daymon, "*") == 0 && tm->tm_wday != tm_wday) || + (strcmp(e->of.ct.dayweek, "*") == 0 && tm->tm_mday != tm_mday) || + (tm->tm_mday != tm_mday && tm->tm_wday != tm_wday) || + (tm->tm_mon != tm_mon)) { + today = FALSE; + } + m = tm->tm_min + (t == ref_t ? 1 : 0); + if ((tm->tm_hour + 1) <= next_ge(tm->tm_hour, e->of.ct.hour)) { + m = 0; + } + min = next_ge(m%60, e->of.ct.minute); + carry = (min < m) ? 1 : 0; + h = tm->tm_hour + carry; + hr = next_ge(h%24, e->of.ct.hour); + carry = (hr < h) ? 1 : 0; + + if (carry == 0 && today) { + /* this event must occur today */ + tm_setup(&tmp, tm->tm_year, tm->tm_mon, tm->tm_mday, + hr, min, tm->tm_isdst); + tmp1 = tmp; + if ((t1 = xmktime(&tmp1)) == (time_t)-1) { + return (0); + } + if (daylight && tmp.tm_isdst != tmp1.tm_isdst) { + /* 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 + * date/time is not altered by mktime, it is the + * time to execute the job. + */ + tmp2 = tmp; + tmp2.tm_isdst = tmp1.tm_isdst; + if ((t1 = xmktime(&tmp2)) == (time_t)-1) { + return (0); + } + if (tmp1.tm_isdst == tmp2.tm_isdst && + tm_cmp(&tmp, &tmp2)) { + /* + * 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. + */ + if (tmp1.tm_isdst != tmp2.tm_isdst) { + t = get_switching_time(tmp1.tm_isdst, + t1); + } else { + /* does this really happen? */ + t = get_switching_time(tmp1.tm_isdst, + t1 - abs(timezone - altzone)); + } + if (t == (time_t)-1) + return (0); + } + goto recalc; + } + if (tm_cmp(&tmp, &tmp1)) { + /* got valid time */ + return (t1); + } else { + /* + * This should never happen, but just in + * case, we fall back to the old code. + */ + if (tm->tm_min > min) { + t += (time_t)(hr-tm->tm_hour-1) * HOUR + + (time_t)(60-tm->tm_min+min) * MINUTE; + } else { + t += (time_t)(hr-tm->tm_hour) * HOUR + + (time_t)(min-tm->tm_min) * MINUTE; + } + t1 = t; + t -= (time_t)tm->tm_sec; + (void) localtime_r(&t, &tmp); + if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0)) + t -= (timezone - altzone); + return ((t <= ref_t) ? t1 : t); + } + } + + /* + * 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; + } + + min = next_ge(0, e->of.ct.minute); + hr = next_ge(0, e->of.ct.hour); + + /* + * calculate the date of the next occurance of this event, which + * will be on a different day than the current + */ + + /* check monthly day specification */ + d1 = tm->tm_mday+1; + day1 = next_ge((d1-1)%days_in_mon(tm->tm_mon, tm->tm_year)+1, + e->of.ct.daymon); + carry1 = (day1 < d1) ? 1 : 0; + + /* check weekly day specification */ + d2 = tm->tm_wday+1; + wday = next_ge(d2%7, e->of.ct.dayweek); + if (wday < d2) + daysahead = 7 - d2 + wday; + else + daysahead = wday - d2; + day2 = (d1+daysahead-1)%days_in_mon(tm->tm_mon, tm->tm_year)+1; + carry2 = (day2 < d1) ? 1 : 0; + + /* + * based on their respective specifications, day1, and day2 give + * the day of the month for the next occurance of this event. + */ + if ((strcmp(e->of.ct.daymon, "*") == 0) && + (strcmp(e->of.ct.dayweek, "*") != 0)) { + day1 = day2; + carry1 = carry2; + } + if ((strcmp(e->of.ct.daymon, "*") != 0) && + (strcmp(e->of.ct.dayweek, "*") == 0)) { + day2 = day1; + carry2 = carry1; + } + + yr = tm->tm_year; + if ((carry1 && carry2) || (tm->tm_mon != tm_mon)) { + /* event does not occur in this month */ + m = tm->tm_mon+1; + mon = next_ge(m%12+1, e->of.ct.month) - 1; /* 0..11 */ + carry = (mon < m) ? 1 : 0; + yr += carry; + /* recompute day1 and day2 */ + day1 = next_ge(1, e->of.ct.daymon); + db = days_btwn(tm->tm_mon, tm->tm_mday, tm->tm_year, mon, + 1, yr) + 1; + wd = (tm->tm_wday+db)%7; + /* wd is the day of the week of the first of month mon */ + wday = next_ge(wd, e->of.ct.dayweek); + if (wday < wd) + day2 = 1 + 7 - wd + wday; + else + day2 = 1 + wday - wd; + if ((strcmp(e->of.ct.daymon, "*") != 0) && + (strcmp(e->of.ct.dayweek, "*") == 0)) + day2 = day1; + if ((strcmp(e->of.ct.daymon, "*") == 0) && + (strcmp(e->of.ct.dayweek, "*") != 0)) + day1 = day2; + day = (day1 < day2) ? day1 : day2; + } else { /* event occurs in this month */ + mon = tm->tm_mon; + if (!carry1 && !carry2) + day = (day1 < day2) ? day1 : day2; + else if (!carry1) + day = day1; + else + day = day2; + } + + /* + * now that we have the min, hr, day, mon, yr of the next event, + * figure out what time that turns out to be. + */ + tm_setup(&tmp, yr, mon, day, hr, min, -1); + tmp2 = tmp; + if ((t1 = xmktime(&tmp2)) == (time_t)-1) { + return (0); + } + if (tm_cmp(&tmp, &tmp2)) { + /* + * 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 + * time/date. + */ + if (daylight) { + int dst = tmp2.tm_isdst; + + tmp2 = tmp; + tmp2.tm_isdst = (dst > 0 ? 0 : 1); + if ((t2 = xmktime(&tmp2)) == (time_t)-1) { + return (0); + } + if (tm_cmp(&tmp, &tmp2)) { + /* + * same time/date found in the opposite zone. + * check the clock to see which comes early. + */ + if (t2 > ref_t && t2 < t1) { + t1 = t2; + } + } + } + return (t1); + } else { + /* + * mktime has set different time/date for the given date. + * This means that the next job is scheduled to be run on the + * invalid time. There are three possible invalid date/time. + * 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. + */ + d1 = days_in_mon(mon, yr); + if ((mon != 1 && day > d1) || (mon == 1 && day > 29)) { + /* + * see if we have got a specific date which + * is invalid. + */ + if (strcmp(e->of.ct.dayweek, "*") == 0 && + mon == (next_ge((mon+1)%12+1, e->of.ct.month)-1) && + day <= next_ge(1, e->of.ct.daymon)) { + /* 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: + * 0 0 1,15,31 1,2,3,4,5 * /usr/bin.... + * 2/31 is invalid, so the next job is 3/1. + */ + tmp2 = tmp; + tmp2.tm_min = 0; + tmp2.tm_hour = 0; + tmp2.tm_mday = 1; /* 1st day of the month */ + if (mon == 11) { + tmp2.tm_mon = 0; + tmp2.tm_year = yr + 1; + } else { + tmp2.tm_mon = mon + 1; + } + if ((t = xmktime(&tmp2)) == (time_t)-1) { + return (0); + } + } else if (mon == 1 && day > d1) { + /* + * ie 29th in the non-leap year. Forwarding the + * clock to Feb 29th 00:00 (March 1st), and recalculate + * the next time. + */ + tmp2 = tmp; + tmp2.tm_min = 0; + tmp2.tm_hour = 0; + if ((t = xmktime(&tmp2)) == (time_t)-1) { + 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. + */ + t2 = t1; + t2 += abs(timezone - altzone); + (void) localtime_r(&t2, &tmp2); + zone_start = get_switching_time(tmp2.tm_isdst, + t1 - abs(timezone - altzone)); + if (zone_start == (time_t)-1) { + return (0); + } + t = zone_start; + } else { + /* + * This should never happen, but fall back to the + * old code. + */ + days = days_btwn(tm->tm_mon, + tm->tm_mday, tm->tm_year, mon, day, yr); + t += (time_t)(23-tm->tm_hour)*HOUR + + (time_t)(60-tm->tm_min)*MINUTE + + (time_t)hr*HOUR + (time_t)min*MINUTE + + (time_t)days*DAY; + t1 = t; + t -= (time_t)tm->tm_sec; + (void) localtime_r(&t, &tmp); + if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0)) + t -= (timezone - altzone); + return (t <= ref_t ? t1 : t); + } + goto recalc; + } + /*NOTREACHED*/ +} + +/* + * 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 +get_switching_time(int to_dst, time_t t_ref) +{ + time_t t, t1; + struct tm tmp, tmp1; + int hints[] = { 60, 120, 30, 90, 0}; /* minutes */ + int i; + + (void) localtime_r(&t_ref, &tmp); + tmp1 = tmp; + tmp1.tm_sec = 0; + tmp1.tm_min = 0; + if ((t = xmktime(&tmp1)) == (time_t)-1) + return ((time_t)-1); + + /* fast path */ + for (i = 0; hints[i] != 0; i++) { + t1 = t + hints[i] * 60; + (void) localtime_r(&t1, &tmp1); + if (tmp1.tm_isdst == to_dst) { + t1--; + (void) localtime_r(&t1, &tmp1); + if (tmp1.tm_isdst != to_dst) { + return (t1 + 1); + } + } + } + + /* ugly, but don't know other than this. */ + tmp1 = tmp; + tmp1.tm_sec = 0; + if ((t = xmktime(&tmp1)) == (time_t)-1) + return ((time_t)-1); + while (t < (t_ref + 6*60*60)) { /* 6 hours should be enough */ + t += 60; /* at least one minute, I assume */ + (void) localtime_r(&t, &tmp); + if (tmp.tm_isdst == to_dst) + return (t); + } + return ((time_t)-1); +} + +static time_t +xmktime(struct tm *tmp) +{ + time_t ret; + + if ((ret = mktime(tmp)) == (time_t)-1) { + if (errno == EOVERFLOW) { + return ((time_t)-1); + } + crabort("internal error: mktime failed", + REMOVE_FIFO|CONSOLE_MSG); + } + return (ret); +} + +#define DUMMY 100 + +static int +next_ge(int current, char *list) +{ + /* + * 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; + int n, n2, min, min_gt; + + if (strcmp(list, "*") == 0) + return (current); + ptr = list; + min = DUMMY; + min_gt = DUMMY; + for (;;) { + if ((n = (int)num(&ptr)) == current) + return (current); + if (n < min) + min = n; + if ((n > current) && (n < min_gt)) + min_gt = n; + if (*ptr == '-') { + ptr++; + if ((n2 = (int)num(&ptr)) > n) { + if ((current > n) && (current <= n2)) + return (current); + } else { /* range that wraps around */ + if (current > n) + return (current); + if (current <= n2) + return (current); + } + } + if (*ptr == '\0') + break; + ptr += 1; + } + if (min_gt != DUMMY) + return (min_gt); + else + return (min); +} + +static void +free_if_unused(struct usr *u) +{ + struct usr *cur, *prev; + /* + * 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). + */ + if (!u->ctexists && u->atevents == NULL && + u->cruncnt == 0 && u->aruncnt == 0) { +#ifdef DEBUG + (void) fprintf(stderr, "%s removed from usr list\n", u->name); +#endif + for (cur = uhead, prev = NULL; + cur != u; + prev = cur, cur = cur->nextusr) { + if (cur == NULL) { + return; + } + } + + if (prev == NULL) + uhead = u->nextusr; + else + prev->nextusr = u->nextusr; + free(u->name); + free(u->home); + free(u); + } +} + +static void +del_atjob(char *name, char *usrname) +{ + + struct event *e, *eprev; + struct usr *u; + + if ((u = find_usr(usrname)) == NULL) + return; + e = u->atevents; + eprev = NULL; + while (e != NULL) { + if (strcmp(name, e->cmd) == 0) { + if (next_event == e) + next_event = NULL; + if (eprev == NULL) + u->atevents = e->link; + else + eprev->link = e->link; + el_remove(e->of.at.eventid, 1); + free(e->cmd); + free(e); + break; + } else { + eprev = e; + e = e->link; + } + } + + free_if_unused(u); +} + +static void +del_ctab(char *name) +{ + + struct usr *u; + + if ((u = find_usr(name)) == NULL) + return; + rm_ctevents(u); + el_remove(u->ctid, 0); + u->ctid = 0; + u->ctexists = 0; + + free_if_unused(u); +} + +static void +rm_ctevents(struct usr *u) +{ + struct event *e2, *e3; + + /* + * see if the next event (to be run by cron) is a cronevent + * owned by this user. + */ + + if ((next_event != NULL) && + (next_event->etype == CRONEVENT) && + (next_event->u == u)) { + next_event = NULL; + } + e2 = u->ctevents; + while (e2 != NULL) { + free(e2->cmd); + free(e2->of.ct.minute); + free(e2->of.ct.hour); + free(e2->of.ct.daymon); + free(e2->of.ct.month); + free(e2->of.ct.dayweek); + if (e2->of.ct.input != NULL) + free(e2->of.ct.input); + e3 = e2->link; + free(e2); + e2 = e3; + } + u->ctevents = NULL; +} + + +static struct usr * +find_usr(char *uname) +{ + struct usr *u; + + u = uhead; + while (u != NULL) { + if (strcmp(u->name, uname) == 0) + return (u); + u = u->nextusr; + } + return (NULL); +} + +/* + * Execute cron command or at/batch job. + * 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 +ex(struct event *e) +{ + int r; + int fd; + pid_t rfork; + FILE *atcmdfp; + char mailvar[4]; + char *at_cmdfile = NULL; + struct stat buf; + struct queue *qp; + struct runinfo *rp; + struct project proj, *pproj = NULL; + char mybuf[PROJECT_BUFSZ]; + char mybuf2[PROJECT_BUFSZ]; + char *tmpfile; + FILE *fptr; + time_t dhltime; + projid_t projid; + int projflag = 0; + + qp = &qt[e->etype]; /* set pointer to queue defs */ + if (qp->nrun >= qp->njob) { + msg("%c queue max run limit reached", e->etype+'a'); + resched(qp->nwait); + return (0); + } + if ((rp = rinfo_get(0)) == NULL) { + msg("MAXRUN (%d) procs reached", MAXRUN); + resched(qp->nwait); + return (0); + } + +#ifdef ATLIMIT + if ((e->u)->uid != 0 && (e->u)->aruncnt >= ATLIMIT) { + msg("ATLIMIT (%d) reached for uid %d", + ATLIMIT, (e->u)->uid); + rinfo_free(rp); + resched(qp->nwait); + return (0); + } +#endif +#ifdef CRONLIMIT + if ((e->u)->uid != 0 && (e->u)->cruncnt >= CRONLIMIT) { + msg("CRONLIMIT (%d) reached for uid %d", + CRONLIMIT, (e->u)->uid); + rinfo_free(rp); + resched(qp->nwait); + return (0); + } +#endif + if ((e->u)->uid == 0) { /* set default path */ + /* path settable in defaults file */ + envinit[2] = supath; + } else { + envinit[2] = path; + } + + /* + * 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. + */ + + rp->outfile = tempnam(TMPDIR, PFX); + rp->jobtype = e->etype; + if (e->etype == CRONEVENT) { + rp->jobname = xmalloc(strlen(e->cmd)+1); + (void) strcpy(rp->jobname, e->cmd); + /* "cron" jobs only produce mail if there's output */ + rp->mailwhendone = 0; + } else { + at_cmdfile = xmalloc(strlen(ATDIR)+strlen(e->cmd)+2); + (void) sprintf(at_cmdfile, "%s/%s", ATDIR, e->cmd); + if ((atcmdfp = fopen(at_cmdfile, "r")) == NULL) { + if (errno == ENAMETOOLONG) { + if (chdir(ATDIR) == 0) + cron_unlink(e->cmd); + } else { + cron_unlink(at_cmdfile); + } + mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECAT); + free(at_cmdfile); + rinfo_free(rp); + return (0); + } + rp->jobname = xmalloc(strlen(at_cmdfile)+1); + (void) strcpy(rp->jobname, at_cmdfile); + + /* + * Skip over the first two lines. + */ + (void) fscanf(atcmdfp, "%*[^\n]\n"); + (void) fscanf(atcmdfp, "%*[^\n]\n"); + if (fscanf(atcmdfp, ": notify by mail: %3s%*[^\n]\n", + mailvar) == 1) { + /* + * Check to see if we should always send mail + * to the owner. + */ + rp->mailwhendone = (strcmp(mailvar, "yes") == 0); + } else { + rp->mailwhendone = 0; + } + + if (fscanf(atcmdfp, "\n: project: %d\n", &projid) == 1) { + projflag = 1; + } + (void) fclose(atcmdfp); + } + + /* + * 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. + */ + dhltime = time(NULL); + if ((dhltime - e->time) < 0) { + msg("clock time drifted backwards!\n"); + if (next_event->etype == CRONEVENT) { + msg("correcting cron event\n"); + next_event->time = next_time(next_event, dhltime); + el_add(next_event, next_event->time, + (next_event->u)->ctid); + } else { /* etype == ATEVENT */ + msg("correcting batch event\n"); + el_add(next_event, next_event->time, + next_event->of.at.eventid); + } + delayed++; + t_old = time(NULL); + free(at_cmdfile); + rinfo_free(rp); + return (0); + } + + if ((rfork = fork()) == (pid_t)-1) { + reap_child(); + if ((rfork = fork()) == (pid_t)-1) { + msg("cannot fork"); + free(at_cmdfile); + rinfo_free(rp); + resched(60); + (void) sleep(30); + return (0); + } + } + if (rfork) { /* parent process */ + contract_abandon_latest(rfork); + + ++qp->nrun; + rp->pid = rfork; + rp->que = e->etype; + if (e->etype != CRONEVENT) + (e->u)->aruncnt++; + else + (e->u)->cruncnt++; + rp->rusr = (e->u); + logit(BCHAR, rp, 0); + free(at_cmdfile); + + return (0); + } + + child_sigreset(); + contract_clear_template(); + + if (e->etype != CRONEVENT) { + /* open jobfile as stdin to shell */ + if (stat(at_cmdfile, &buf)) { + if (errno == ENAMETOOLONG) { + if (chdir(ATDIR) == 0) + cron_unlink(e->cmd); + } else + cron_unlink(at_cmdfile); + mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON); + exit(1); + } + if (!(buf.st_mode&ISUID)) { + /* + * if setuid bit off, original owner has + * given this file to someone else + */ + cron_unlink(at_cmdfile); + exit(1); + } + if ((fd = open(at_cmdfile, O_RDONLY)) == -1) { + mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON); + cron_unlink(at_cmdfile); + exit(1); + } + if (fd != 0) { + (void) dup2(fd, 0); + (void) close(fd); + } + /* + * 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) { + if ((pproj = getprojbyid(projid, &proj, + (void *)&mybuf, sizeof (mybuf))) == NULL || + !inproj(e->u->name, pproj->pj_name, + mybuf2, sizeof (mybuf2))) { + cron_unlink(at_cmdfile); + mail((e->u)->name, BADPROJID, ERR_CANTEXECAT); + 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)" + " for user %s", errno, e->cmd, e->u->name); + if (e->etype != CRONEVENT) + cron_unlink(at_cmdfile); + exit(1); + } + + /* + * set correct user identification and check his account + */ + r = set_user_cred(e->u, pproj); + if (r == VUC_EXPIRED) { + msg("user (%s) account is expired", e->u->name); + audit_cron_user_acct_expired(e->u->name); + clean_out_user(e->u); + exit(1); + } + if (r == VUC_NEW_AUTH) { + msg("user (%s) password has expired", e->u->name); + audit_cron_user_acct_expired(e->u->name); + clean_out_user(e->u); + exit(1); + } + if (r != VUC_OK) { + msg("bad user (%s)", e->u->name); + 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 || + setgid(e->u->gid) == -1 || + initgroups(e->u->name, e->u->gid) == -1) { + msg("bad user (%s) or setgid failed (%s)", + e->u->name, e->u->name); + audit_cron_bad_user(e->u->name); + clean_out_user(e->u); + exit(1); + } + + if (e->etype != CRONEVENT) { + r = audit_cron_session(e->u->name, NULL, + e->u->uid, e->u->gid, + at_cmdfile); + cron_unlink(at_cmdfile); + } else { + r = audit_cron_session(e->u->name, CRONDIR, + e->u->uid, e->u->gid, + NULL); + } + if (r != 0) { + msg("cron audit problem. job failed (%s) for user %s", + e->cmd, e->u->name); + exit(1); + } + + audit_cron_new_job(e->cmd, e->etype, (void *)e); + + if (setuid(e->u->uid) == -1) { + msg("setuid failed (%s)", e->u->name); + clean_out_user(e->u); + exit(1); + } + + if (e->etype == CRONEVENT) { + /* check for standard input to command */ + if (e->of.ct.input != NULL) { + if ((tmpfile = strdup(TMPINFILE)) == NULL) { + mail((e->u)->name, MALLOCERR, + ERR_CANTEXECCRON); + exit(1); + } + if ((fd = mkstemp(tmpfile)) == -1 || + (fptr = fdopen(fd, "w")) == NULL) { + mail((e->u)->name, NOSTDIN, + ERR_CANTEXECCRON); + cron_unlink(tmpfile); + free(tmpfile); + exit(1); + } + if ((fwrite(e->of.ct.input, sizeof (char), + strlen(e->of.ct.input), fptr)) != + strlen(e->of.ct.input)) { + mail((e->u)->name, NOSTDIN, ERR_CANTEXECCRON); + cron_unlink(tmpfile); + free(tmpfile); + (void) close(fd); + (void) fclose(fptr); + exit(1); + } + if (fseek(fptr, (off_t)0, SEEK_SET) != -1) { + if (fd != 0) { + (void) dup2(fd, 0); + (void) close(fd); + } + } + cron_unlink(tmpfile); + free(tmpfile); + (void) fclose(fptr); + } else if ((fd = open("/dev/null", O_RDONLY)) > 0) { + (void) dup2(fd, 0); + (void) close(fd); + } + } + + /* redirect stdout and stderr for the shell */ + if ((fd = open(rp->outfile, O_WRONLY|O_CREAT|O_EXCL, OUTMODE)) == 1) + fd = open("/dev/null", O_WRONLY); + + if (fd >= 0 && fd != 1) + (void) dup2(fd, 1); + + if (fd >= 0 && fd != 2) { + (void) dup2(fd, 2); + if (fd != 1) + (void) close(fd); + } + + (void) strlcat(homedir, (e->u)->home, sizeof (homedir)); + (void) strlcat(logname, (e->u)->name, sizeof (logname)); + environ = envinit; + if (chdir((e->u)->home) == -1) { + mail((e->u)->name, CANTCDHOME, + e->etype == CRONEVENT ? ERR_CANTEXECCRON : + ERR_CANTEXECAT); + 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) + (void) nice(qp->nice); + if (e->etype == CRONEVENT) + (void) execl(SHELL, "sh", "-c", e->cmd, 0); + else /* type == ATEVENT */ + (void) execl(SHELL, "sh", 0); + mail((e->u)->name, CANTEXECSH, + e->etype == CRONEVENT ? ERR_CANTEXECCRON : ERR_CANTEXECAT); + exit(1); + /*NOTREACHED*/ +} + +static int +idle(long t) +{ + time_t now; + + while (t > 0L) { + + if (msg_wait(t) != 0) { + /* we need to run next job immediately */ + return (0); + } + + reap_child(); + + now = time(NULL); + if (last_time > now) { + /* clock has been reset */ + return (1); + } + + if (next_event == NULL && !el_empty()) { + next_event = (struct event *)el_first(); + } + if (next_event == NULL) + t = INFINITY; + else + t = (long)next_event->time - now; + } + 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 +reap_child() +{ + pid_t pid; + int prc; + struct runinfo *rp; + + for (;;) { + pid = waitpid((pid_t)-1, &prc, WNOHANG); + if (pid <= 0) + break; +#ifdef DEBUG + fprintf(stderr, + "wait returned %x for process %d\n", prc, pid); +#endif + if ((rp = rinfo_get(pid)) == NULL) { + if (miscpid_delete(pid) == 0) { + /* not found in anywhere */ + msg(PIDERR, pid); + } + } else if (rp->que == ZOMB) { + (void) unlink(rp->outfile); + rinfo_free(rp); + } else { + cleanup(rp, prc); + } + } +} + +static void +cleanup(struct runinfo *pr, int rc) +{ + int nextfork = 1; + struct usr *p; + struct stat buf; + + logit(ECHAR, pr, rc); + --qt[pr->que].nrun; + p = pr->rusr; + if (pr->que != CRONEVENT) + --p->aruncnt; + else + --p->cruncnt; + + if (!lstat(pr->outfile, &buf)) { + if ((buf.st_mode != S_IFLNK) && + (buf.st_size > 0 || pr->mailwhendone)) { + /* mail user stdout and stderr */ + for (;;) { + if ((pr->pid = fork()) < 0) { + /* + * if fork fails try forever in doubling + * retry times, up to 16 seconds + */ + (void) sleep(nextfork); + if (nextfork < 16) + nextfork += nextfork; + continue; + } else if (pr->pid == 0) { + child_sigreset(); + contract_clear_template(); + + mail_result(p, pr, buf.st_size); + /* NOTREACHED */ + } else { + contract_abandon_latest(pr->pid); + pr->que = ZOMB; + break; + } + } + } else { + (void) unlink(pr->outfile); + 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 +mail_result(struct usr *p, struct runinfo *pr, size_t filesize) +{ + struct passwd *ruser_ids; + FILE *mailpipe; + FILE *st; + struct utsname name; + int nbytes; + char iobuf[BUFSIZ]; + char *cmd; + + (void) uname(&name); + if ((ruser_ids = getpwnam(p->name)) == NULL) + exit(0); + (void) setuid(ruser_ids->pw_uid); + + cmd = xmalloc(strlen(MAIL)+strlen(p->name)+2); + (void) sprintf(cmd, "%s %s", MAIL, p->name); + mailpipe = popen(cmd, "w"); + contract_abandon_latest(0); + free(cmd); + if (mailpipe == NULL) + exit(127); + (void) fprintf(mailpipe, "To: %s\n", p->name); + if (pr->jobtype == CRONEVENT) { + (void) fprintf(mailpipe, CRONOUT); + (void) fprintf(mailpipe, "Your \"cron\" job on %s\n", + name.nodename); + if (pr->jobname != NULL) { + (void) fprintf(mailpipe, "%s\n\n", pr->jobname); + } + } else { + (void) fprintf(mailpipe, "Subject: Output from \"at\" job\n\n"); + (void) fprintf(mailpipe, "Your \"at\" job on %s\n", + name.nodename); + if (pr->jobname != NULL) { + (void) fprintf(mailpipe, "\"%s\"\n\n", pr->jobname); + } + } + /* Tmp. file is fopen'ed w/ "r", secure open */ + if (filesize > 0 && + (st = fopen(pr->outfile, "r")) != NULL) { + (void) fprintf(mailpipe, + "produced the following output:\n\n"); + while ((nbytes = fread(iobuf, sizeof (char), BUFSIZ, st)) != 0) + (void) fwrite(iobuf, sizeof (char), nbytes, mailpipe); + (void) fclose(st); + } else { + (void) fprintf(mailpipe, "completed.\n"); + } + (void) pclose(mailpipe); + exit(0); +} + +static int +msg_wait(long tim) +{ + struct message msg; + int cnt; + time_t reftime; + struct pollfd pfd[2]; + int64_t tl; + int timeout; + static int pending_msg; + static time_t pending_reftime; + + if (pending_msg) { + process_msg(&msgbuf, pending_reftime); + pending_msg = 0; + return (0); + } + + /* + * We are opening the signal mask to receive SIGCLD. The notifypipe + * is used to avoid race condition between SIGCLD and poll system + * call. + * If SIGCLD is delivered in poll(), poll will be interrupted, and + * we will return to idle() to reap the dead children. + * If SIGCLD is delivered between sigprocmask() below and poll(), + * there is no way we can detect the SIGCLD because poll() won't + * be interrupted. In such case, the dead children can't be wait'ed + * until poll returns by timeout or a new job. To avoid this race + * condition, child_handler write to the notifypipe, so that + * poll() will be able to return with POLLIN which indicates that + * we have received SIGCLD. + * + * Since the notifypipe is used to just let poll return from + * system call, the data in the pipe won't be read. Therefore, + * any data in the pipe needs to be flushed before opening signal + * mask. + * + * Note that we can probably re-write this code with pselect() + * which can handle this situation easily. + */ + (void) ioctl(notifypipe[0], I_FLUSH, FLUSHW); + + pfd[0].fd = msgfd; + pfd[0].events = POLLIN; + pfd[1].fd = notifypipe[1]; + pfd[1].events = POLLIN; + +#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 accordingly. + */ + tim = (tim > CRON_MAXSLEEP) ? CRON_MAXSLEEP : tim; +#endif + tl = (tim == INFINITY) ? -1ll : (int64_t)tim * 1000; + + accept_sigcld = 1; + (void) sigprocmask(SIG_UNBLOCK, &childmask, NULL); + do { + timeout = (tl > INT_MAX ? INT_MAX : (int)tl); + tl -= timeout; + cnt = poll(pfd, 2, timeout); + if (cnt == -1 && errno != EINTR) { + perror("! poll"); + } + } while (tl > 0 && cnt == 0); + (void) sigprocmask(SIG_BLOCK, &childmask, NULL); + accept_sigcld = 0; + + /* + * poll timeout or interrupted. + */ + if (cnt <= 0) + return (0); + + /* + * Not the timeout or new job, but a SIGCLD has been delivered. + */ + if ((pfd[0].revents & POLLIN) == 0) + return (0); + + errno = 0; + if ((cnt = read(msgfd, &msg, sizeof (msg))) != sizeof (msg)) { + if (cnt != -1 || errno != EAGAIN) + perror("! read"); + return (0); + } + reftime = time(NULL); + if (next_event != NULL && reftime >= next_event->time) { + /* + * we need to run the job before reloading crontab. + */ + (void) memcpy(&msgbuf, &msg, sizeof (msg)); + pending_msg = 1; + pending_reftime = reftime; + return (1); + } + process_msg(&msg, reftime); + 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 +process_msg(struct message *pmsg, time_t reftime) +{ + if (pmsg->etype == NULL) + return; + + switch (pmsg->etype) { + case AT: + if (pmsg->action == DELETE) + del_atjob(pmsg->fname, pmsg->logname); + else + mod_atjob(pmsg->fname, (time_t)0); + break; + case CRON: + if (pmsg->action == DELETE) + del_ctab(pmsg->fname); + else + mod_ctab(pmsg->fname, reftime); + break; + default: + msg("message received - bad format"); + break; + } + if (next_event != NULL) { + if (next_event->etype == CRONEVENT) + el_add(next_event, next_event->time, + (next_event->u)->ctid); + else /* etype == ATEVENT */ + el_add(next_event, next_event->time, + next_event->of.at.eventid); + next_event = NULL; + } + (void) fflush(stdout); + pmsg->etype = NULL; +} + +static struct runinfo * +rinfo_get(pid_t pid) +{ + struct runinfo *rp; + + for (rp = rt; rp < rt+MAXRUN; rp++) { + if (rp->pid == pid) + break; + } + if (rp >= rt+MAXRUN) + return (NULL); + else + return (rp); +} + +/* + * Free memory used for output file name and job name in runinfo structure + */ +static void +rinfo_free(struct runinfo *rp) +{ + free(rp->outfile); + free(rp->jobname); + rp->outfile = rp->jobname = NULL; + rp->pid = 0; +} + +/* ARGSUSED */ +static void +thaw_handler(int sig) +{ + ; +} + + +/* ARGSUSED */ +static void +cronend(int sig) +{ + crabort("SIGTERM", REMOVE_FIFO); +} + +/*ARGSUSED*/ +static void +child_handler(int sig) +{ + /* + * Just in case someone changes the signal mask. + * we don't want to notify the SIGCLD. + */ + if (accept_sigcld) { + (void) write(notifypipe[0], &sig, 1); + } +} + +static void +child_sigreset(void) +{ + (void) signal(SIGCLD, SIG_DFL); + (void) sigprocmask(SIG_SETMASK, &defmask, NULL); +} + +/* + * crabort() - handle exits out of cron + */ +static void +crabort(char *mssg, int action) +{ + int c; + + if (action & REMOVE_FIFO) { + /* FIFO vanishes when cron finishes */ + if (unlink(FIFO) < 0) + perror("cron could not unlink FIFO"); + } + + if (action & CONSOLE_MSG) { + /* write error msg to console */ + if ((c = open(CONSOLE, O_WRONLY)) >= 0) { + (void) write(c, "cron aborted: ", 14); + (void) write(c, mssg, strlen(mssg)); + (void) write(c, "\n", 1); + (void) close(c); + } + } + + /* always log the message */ + msg(mssg); + msg("******* CRON ABORTED ********"); + exit(1); +} + +/* + * msg() - time-stamped error reporting function + */ +/*PRINTFLIKE1*/ +static void +msg(char *fmt, ...) +{ + va_list args; + time_t t; + + t = time(NULL); + + (void) fflush(stdout); + + (void) fprintf(stderr, "! "); + + va_start(args, fmt); + (void) vfprintf(stderr, fmt, args); + va_end(args); + + (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t)); + (void) fprintf(stderr, " %s\n", timebuf); + + (void) fflush(stderr); +} + +static void +logit(int cc, struct runinfo *rp, int rc) +{ + time_t t; + int ret; + + if (!log) + return; + + t = time(NULL); + if (cc == BCHAR) + (void) printf("%c CMD: %s\n", cc, next_event->cmd); + (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t)); + (void) printf("%c %.8s %u %c %s", + cc, (rp->rusr)->name, rp->pid, QUE(rp->que), timebuf); + if ((ret = TSTAT(rc)) != 0) + (void) printf(" ts=%d", ret); + if ((ret = RCODE(rc)) != 0) + (void) printf(" rc=%d", ret); + (void) putchar('\n'); + (void) fflush(stdout); +} + +static void +resched(int delay) +{ + time_t nt; + + /* run job at a later time */ + nt = next_event->time + delay; + if (next_event->etype == CRONEVENT) { + next_event->time = next_time(next_event, (time_t)0); + if (nt < next_event->time) + next_event->time = nt; + el_add(next_event, next_event->time, (next_event->u)->ctid); + delayed = 1; + msg("rescheduling a cron job"); + return; + } + add_atevent(next_event->u, next_event->cmd, nt, next_event->etype); + msg("rescheduling at job"); +} + +static void +quedefs(int action) +{ + int i; + int j; + char qbuf[QBUFSIZ]; + FILE *fd; + + /* set up default queue definitions */ + for (i = 0; i < NQUEUE; i++) { + qt[i].njob = qd.njob; + qt[i].nice = qd.nice; + qt[i].nwait = qd.nwait; + } + if (action == DEFAULT) + return; + if ((fd = fopen(QUEDEFS, "r")) == NULL) { + msg("cannot open quedefs file"); + msg("using default queue definitions"); + return; + } + while (fgets(qbuf, QBUFSIZ, fd) != NULL) { + if ((j = qbuf[0]-'a') < 0 || j >= NQUEUE || qbuf[1] != '.') + continue; + parsqdef(&qbuf[2]); + qt[j].njob = qq.njob; + qt[j].nice = qq.nice; + qt[j].nwait = qq.nwait; + } + (void) fclose(fd); +} + +static void +parsqdef(char *name) +{ + int i; + + qq = qd; + while (*name) { + i = 0; + while (isdigit(*name)) { + i *= 10; + i += *name++ - '0'; + } + switch (*name++) { + case JOBF: + qq.njob = i; + break; + case NICEF: + qq.nice = i; + break; + case WAITF: + qq.nwait = i; + break; + } + } +} + +/* + * defaults - read defaults from /etc/default/cron + */ +static void +defaults() +{ + int flags; + char *deflog; + char *hz, *tz; + + /* + * get HZ value for environment + */ + if ((hz = getenv("HZ")) == (char *)NULL) + (void) sprintf(hzname, "HZ=%d", HZ); + else + (void) snprintf(hzname, sizeof (hzname), "HZ=%s", hz); + /* + * get TZ value for environment + */ + (void) snprintf(tzone, sizeof (tzone), "TZ=%s", + ((tz = getenv("TZ")) != NULL) ? tz : DEFTZ); + + if (defopen(DEFFILE) == 0) { + /* ignore case */ + flags = defcntl(DC_GETFLAGS, 0); + TURNOFF(flags, DC_CASE); + (void) defcntl(DC_SETFLAGS, flags); + + if (((deflog = defread("CRONLOG=")) == NULL) || + (*deflog == 'N') || (*deflog == 'n')) + log = 0; + else + log = 1; + /* fix for 1087611 - allow paths to be set in defaults file */ + if ((Def_path = defread("PATH=")) != NULL) { + (void) strlcat(path, Def_path, LINE_MAX); + } else { + (void) strlcpy(path, NONROOTPATH, LINE_MAX); + } + if ((Def_supath = defread("SUPATH=")) != NULL) { + (void) strlcat(supath, Def_supath, LINE_MAX); + } else { + (void) strlcpy(supath, ROOTPATH, LINE_MAX); + } + (void) defopen(NULL); + } +} + +/* + * 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(const struct usr *u) +{ + struct passwd *pw; + 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. + */ + + static size_t nGroupsMax = (size_t)-1; + + /* + * Arrays for cron user's group list, constructed at startup to + * be nGroupsMax elements long, used for verifying user + * credentials prior to execution. + */ + + static gid_t *UsrGrps; + static gid_t *OrigGrps; + + if (((pw = getpwnam(u->name)) == NULL) || + (pw->pw_uid != u->uid)) { + return (VUC_BADUSER); + } + + /* + * Create the group id lists needed for job credential + * verification. + */ + + if (nGroupsMax == (size_t)-1) { + if ((nGroupsMax = sysconf(_SC_NGROUPS_MAX)) > 0) { + UsrGrps = xcalloc(nGroupsMax, sizeof (gid_t)); + OrigGrps = xcalloc(nGroupsMax, sizeof (gid_t)); + } + +#ifdef DEBUG + (void) fprintf(stderr, "nGroupsMax = %ld\n", nGroupsMax); +#endif + } + +#ifdef DEBUG + (void) fprintf(stderr, "verify_user_cred (%s-%d)\n", pw->pw_name, + pw->pw_uid); + (void) fprintf(stderr, "verify_user_cred: pw->pw_gid = %d, " + "u->gid = %d\n", pw->pw_gid, u->gid); +#endif + + retval = (u->gid == pw->pw_gid) ? VUC_OK : VUC_NOTINGROUP; + + if (nGroupsMax > 0) { + numOrigGrps = getgroups(nGroupsMax, OrigGrps); + + (void) initgroups(pw->pw_name, pw->pw_gid); + numUsrGrps = getgroups(nGroupsMax, UsrGrps); + + for (i = 0; i < numUsrGrps; i++) { + if (UsrGrps[i] == u->gid) { + retval = VUC_OK; + break; + } + } + + if (OrigGrps) { + (void) setgroups(numOrigGrps, OrigGrps); + } + } + +#ifdef DEBUG + (void) fprintf(stderr, "verify_user_cred: VUC = %d\n", retval); +#endif + + return (retval); +} + +static int +set_user_cred(const struct usr *u, struct project *pproj) +{ + static char *progname = "cron"; + int r = 0, rval = 0; + + if ((r = pam_start(progname, u->name, &pam_conv, &pamh)) + != 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 (pproj != NULL) { + size_t sz = sizeof (PROJECT) + strlen(pproj->pj_name); + char *buf = alloca(sz); + + (void) snprintf(buf, sz, PROJECT "%s", pproj->pj_name); + (void) pam_set_item(pamh, PAM_RESOURCE, buf); + } + + r = pam_setcred(pamh, PAM_ESTABLISH_CRED); + if (r != PAM_SUCCESS) + rval = VUC_BADUSER; + +set_eser_cred_exit: + (void) pam_end(pamh, r); + 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) +{ + struct event *ev, *pv; + + for (pv = NULL, ev = u->atevents; + ev != NULL; + pv = ev, ev = ev->link, free(pv)) { + el_remove(ev->of.at.eventid, 1); + if (cwd == AT) + cron_unlink(ev->cmd); + else { + char buf[PATH_MAX]; + if (strlen(ATDIR) + strlen(ev->cmd) + 2 + < PATH_MAX) { + (void) sprintf(buf, "%s/%s", ATDIR, ev->cmd); + cron_unlink(buf); + } + } + free(ev->cmd); + } + + u->atevents = NULL; +} + +static void +clean_out_ctab(struct usr *u) +{ + rm_ctevents(u); + el_remove(u->ctid, 0); + u->ctid = 0; + u->ctexists = 0; +} + +static void +cron_unlink(char *name) +{ + int r; + + r = unlink(name); + if (r == 0 || (r == -1 && errno == ENOENT)) { + (void) audit_cron_delete_anc_file(name, NULL); + } +} + +static void +create_anc_ctab(struct event *e) +{ + if (audit_cron_create_anc_file(e->u->name, + (cwd == CRON) ? NULL:CRONDIR, + e->u->name, + e->u->uid) == -1) { + process_anc_files(CRON_ANC_DELETE); + crabort("cannot create ancillary files for crontabs", + REMOVE_FIFO|CONSOLE_MSG); + } +} + +static void +delete_anc_ctab(struct event *e) +{ + (void) audit_cron_delete_anc_file(e->u->name, + (cwd == CRON) ? NULL:CRONDIR); +} + +static void +create_anc_atjob(struct event *e) +{ + if (!e->of.at.exists) + return; + + if (audit_cron_create_anc_file(e->cmd, + (cwd == AT) ? NULL:ATDIR, + e->u->name, + e->u->uid) == -1) { + process_anc_files(CRON_ANC_DELETE); + crabort("cannot create ancillary files for atjobs", + REMOVE_FIFO|CONSOLE_MSG); + } +} + +static void +delete_anc_atjob(struct event *e) +{ + if (!e->of.at.exists) + return; + + (void) audit_cron_delete_anc_file(e->cmd, + (cwd == AT) ? NULL:ATDIR); +} + + +static void +process_anc_files(int del) +{ + struct usr *u = uhead; + struct event *e; + + if (!audit_cron_mode()) + return; + + for (;;) { + if (u->ctexists && u->ctevents != NULL) { + e = u->ctevents; + for (;;) { + if (del) + delete_anc_ctab(e); + else + create_anc_ctab(e); + if ((e = e->link) == NULL) + break; + } + } + + if (u->atevents != NULL) { + e = u->atevents; + for (;;) { + if (del) + delete_anc_atjob(e); + else + create_anc_atjob(e); + if ((e = e->link) == NULL) + break; + } + } + + if ((u = u->nextusr) == NULL) + break; + } +} + +/*ARGSUSED*/ +static int +cron_conv(int num_msg, struct pam_message **msgs, + struct pam_response **response, void *appdata_ptr) +{ + 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: + if (m[i]->msg != NULL) { + (void) msg("%s\n", m[i]->msg); + } + 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_insert(pid_t pid) +{ + struct miscpid *mp; + + mp = xmalloc(sizeof (*mp)); + mp->pid = pid; + mp->next = miscpid_head; + miscpid_head = mp; +} + +static int +miscpid_delete(pid_t pid) +{ + struct miscpid *mp, *omp; + int found = 0; + + omp = NULL; + for (mp = miscpid_head; mp != NULL; mp = mp->next) { + if (mp->pid == pid) { + found = 1; + break; + } + omp = mp; + } + if (found) { + if (omp != NULL) + omp->next = mp->next; + else + miscpid_head = NULL; + free(mp); + } + return (found); +} + +/* + * Establish contract terms such that all children are in abandoned + * process contracts. + */ +static int +contract_set_template() +{ + int fd; + + if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0) + crabort("cannot open process contract template", + REMOVE_FIFO | CONSOLE_MSG); + + if (ct_pr_tmpl_set_param(fd, 0) || + ct_tmpl_set_informative(fd, 0) || + ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR)) + crabort("cannot establish contract template terms", + REMOVE_FIFO | CONSOLE_MSG); + + if (ct_tmpl_activate(fd)) + crabort("cannot activate contract template", + REMOVE_FIFO | CONSOLE_MSG); + + (void) close(fd); +} + +/* + * Clear active process contract template. + */ +static int +contract_clear_template() +{ + int fd; + + if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0) + crabort("cannot open process contract template", + REMOVE_FIFO | CONSOLE_MSG); + + if (ct_tmpl_clear(fd)) + crabort("cannot clear contract template", + REMOVE_FIFO | CONSOLE_MSG); + + (void) close(fd); +} + +/* + * Abandon latest process contract unconditionally. If we have leaked [some + * critical amount], exit such that the kernel reaps our contracts. + */ +static void +contract_abandon_latest(pid_t pid) +{ + int r; + ctid_t id; + static uint_t cts_lost; + + if (cts_lost > MAX_LOST_CONTRACTS) + crabort("repeated failure to abandon contracts", + REMOVE_FIFO | CONSOLE_MSG); + + if (r = contract_latest(&id)) { + if (pid == 0) + msg("could not obtain latest contract from " + "popen(3C): %s", strerror(r)); + else + msg("could not obtain latest contract for " + "PID %ld: %s", pid, strerror(r)); + cts_lost++; + return; + } + + if (r = contract_abandon_id(id)) { + msg("could not abandon latest contract %ld: %s", id, + strerror(r)); + cts_lost++; + return; + } +} diff --git a/usr/src/cmd/cron/cron.dfl b/usr/src/cmd/cron/cron.dfl new file mode 100644 index 0000000000..ebcbc89ad7 --- /dev/null +++ b/usr/src/cmd/cron/cron.dfl @@ -0,0 +1,7 @@ +# +# Copyright 1991 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#pragma ident "%Z%%M% %I% %E% SMI" + +CRONLOG=YES diff --git a/usr/src/cmd/cron/cron.h b/usr/src/cmd/cron/cron.h new file mode 100644 index 0000000000..fbd4f666ee --- /dev/null +++ b/usr/src/cmd/cron/cron.h @@ -0,0 +1,108 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 + * or http://www.opensolaris.org/os/licensing. + * 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 1999-2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _CRON_H +#define _CRON_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +#include <dirent.h> +#include <auth_attr.h> +#include <auth_list.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define FALSE 0 +#define TRUE 1 +#define MINUTE 60L +#define HOUR 60L*60L +#define DAY 24L*60L*60L +#define NQUEUE 26 /* number of queues available */ +#define ATEVENT 0 +#define BATCHEVENT 1 +#define CRONEVENT 2 + +#define ADD 'a' +#define DELETE 'd' +#define AT 'a' +#define CRON 'c' + +#define QUE(x) ('a'+(x)) +#define RCODE(x) (((x)>>8)&0377) +#define TSTAT(x) ((x)&0377) + +#define FLEN 15 +#define LLEN 9 + +/* + * structure used for passing messages from the at and crontab commands to cron + */ +struct message { + char etype; + char action; + char fname[FLEN]; + char logname[LLEN]; +} msgbuf; + +/* anything below here can be changed */ + +#define CRONDIR "/var/spool/cron/crontabs" +#define ATDIR "/var/spool/cron/atjobs" +#define ACCTFILE "/var/cron/log" +#define CRONALLOW "/etc/cron.d/cron.allow" +#define CRONDENY "/etc/cron.d/cron.deny" +#define ATALLOW "/etc/cron.d/at.allow" +#define ATDENY "/etc/cron.d/at.deny" +#define PROTO "/etc/cron.d/.proto" +#define QUEDEFS "/etc/cron.d/queuedefs" +#define FIFO "/etc/cron.d/FIFO" +#define DEFFILE "/etc/default/cron" + +#define SHELL "/usr/bin/sh" /* shell to execute */ + +#define CTLINESIZE 1000 /* max chars in a crontab line */ +#define UNAMESIZE 20 /* max chars in a user name */ + +int allowed(char *, char *, char *); +int days_in_mon(int, int); +char *errmsg(int); +char *getuser(uid_t); +void cron_sendmsg(char, char *, char *, char); +time_t num(char **); +void *xmalloc(size_t); +void *xcalloc(size_t, size_t); + +#ifdef __cplusplus +} +#endif + +#endif /* _CRON_H */ diff --git a/usr/src/cmd/cron/cron.xcl b/usr/src/cmd/cron/cron.xcl new file mode 100644 index 0000000000..facb8a8f0f --- /dev/null +++ b/usr/src/cmd/cron/cron.xcl @@ -0,0 +1,83 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (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 +# or http://www.opensolaris.org/os/licensing. +# 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 +# +msgid "" +msgid "/etc/cron.d/at.allow" +msgid "/etc/cron.d/at.deny" +msgid "cklmsrf:q:t:" +msgid " " +msgid "DATEMSK" +msgid "%-5d" +msgid "/var/spool/cron/atjobs" +msgid "_at" +msgid "/" +msgid "%s" +msgid "/etc/cron.d/.proto" +msgid "%s.%c" +msgid "r" +msgid "at: %s: %s\n" +msgid "%a %b %e %H:%M:%S %Y" +msgid "%s/%ld.%c" +msgid "at: %s\n" +msgid "at> " +msgid "/bin/csh" +msgid "/bin/ksh" +msgid "/bin/sh" +msgid "SHELL" +msgid "$SHELL" +msgid "/sh" +msgid "/ksh" +msgid ": %s job\n" +msgid "batch" +msgid "at" +msgid "stdin" +msgid "yes" +msgid "no" +msgid "export %s; %s='%s'\n" +msgid "w" +msgid "%o" +msgid "ulimit %ld\n" +msgid "<EOT>\n" +msgid ":%lu" +msgid "at: %s: " +msgid "%s\t%s\n" +msgid "" +msgid "elr" +msgid "/etc/cron.d/cron.allow" +msgid "/etc/cron.d/cron.deny" +msgid "/var/spool/cron/crontabs" +msgid "/" +msgid "r" +msgid "/tmp/crontabXXXXXX" +msgid "w" +msgid "VISUAL" +msgid "EDITOR" +msgid "ed" +msgid "%s %s" +msgid "crontab: %s: %s\n" +msgid "%-5d" +msgid "_cron" +msgid "crontab: %s\n" +msgid "/etc/cron.d/FIFO" +msgid "/usr/bin/pwd" +msgid "user = %s\t%s\t%s\n" +msgid "malloc" +msgid "calloc" diff --git a/usr/src/cmd/cron/cron.xml b/usr/src/cmd/cron/cron.xml new file mode 100644 index 0000000000..88d3472100 --- /dev/null +++ b/usr/src/cmd/cron/cron.xml @@ -0,0 +1,113 @@ +<?xml version="1.0"?> +<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> +<!-- + Copyright 2005 Sun Microsystems, Inc. All rights reserved. + Use is subject to license terms. + + CDDL HEADER START + + The contents of this file are subject to the terms of the + Common Development and Distribution License, Version 1.0 only + (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 + or http://www.opensolaris.org/os/licensing. + 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 + + ident "%Z%%M% %I% %E% SMI" + + NOTE: This service manifest is not editable; its contents will + be overwritten by package or patch operations, including + operating system upgrade. Make customizations in a different + file. +--> + +<service_bundle type='manifest' name='SUNWcsr:cron'> + +<service + name='system/cron' + type='service' + version='1'> + + <single_instance /> + + <dependency + name='usr' + type='service' + grouping='require_all' + restart_on='none'> + <service_fmri value='svc:/system/filesystem/local' /> + </dependency> + + <dependency + name='ns' + type='service' + grouping='require_all' + restart_on='none'> + <service_fmri value='svc:/milestone/name-services' /> + </dependency> + + <dependent + name='cron_multi-user' + grouping='optional_all' + restart_on='none'> + <service_fmri value='svc:/milestone/multi-user' /> + </dependent> + + <exec_method + type='method' + name='start' + exec='/lib/svc/method/svc-cron' + timeout_seconds='60'> + <method_context> + <method_credential user='root' group='root' /> + </method_context> + </exec_method> + + <exec_method + type='method' + name='stop' + exec=':kill' + timeout_seconds='60'> + </exec_method> + + <property_group name='startd' type='framework'> + <!-- sub-process core dumps shouldn't restart session --> + <propval name='ignore_error' type='astring' + value='core,signal' /> + </property_group> + + <property_group name='general' type='framework'> + <!-- to start stop cron --> + <propval name='action_authorization' type='astring' + value='solaris.smf.manage.cron' /> + </property_group> + + <instance name='default' enabled='false' /> + + <stability value='Unstable' /> + + <template> + <common_name> + <loctext xml:lang='C'> + clock daemon (cron) + </loctext> + </common_name> + <documentation> + <manpage title='cron' section='1M' manpath='/usr/share/man' /> + <manpage title='crontab' section='1' manpath='/usr/share/man' /> + </documentation> + </template> +</service> + +</service_bundle> diff --git a/usr/src/cmd/cron/crontab.c b/usr/src/cmd/cron/crontab.c new file mode 100644 index 0000000000..2ed88c4c6d --- /dev/null +++ b/usr/src/cmd/cron/crontab.c @@ -0,0 +1,547 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 + * or http://www.opensolaris.org/os/licensing. + * 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 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <ctype.h> +#include <pwd.h> +#include <unistd.h> +#include <locale.h> +#include <nl_types.h> +#include <langinfo.h> +#include <libintl.h> +#include <security/pam_appl.h> +#include "cron.h" + +#define TMPFILE "_cron" /* prefix for tmp file */ +#define CRMODE 0600 /* mode for creating crontabs */ + +#define BADCREATE \ + "can't create your crontab file in the crontab directory." +#define BADOPEN "can't open your crontab file." +#define BADSHELL \ + "because your login shell isn't /usr/bin/sh, you can't use cron." +#define WARNSHELL "warning: commands will be executed using /usr/bin/sh\n" +#define BADUSAGE \ + "proper usage is: \n crontab [file | -e | -l | -r ] [user]" +#define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)." +#define NOTALLOWED "you are not authorized to use cron. Sorry." +#define NOTROOT \ + "you must be super-user to access another user's crontab file" +#define AUDITREJECT "The audit context for your shell has not been set." +#define EOLN "unexpected end of line." +#define UNEXPECT "unexpected character found in line." +#define OUTOFBOUND "number out of bounds." +#define ERRSFND "errors detected in input, no crontab file generated." +#define ED_ERROR \ + " The editor indicates that an error occurred while you were\n"\ + " editing the crontab data - usually a minor typing error.\n\n" +#define BADREAD "error reading your crontab file" +#define ED_PROMPT \ + " Edit again, to ensure crontab information is intact (%c/%c)?\n"\ + " ('%c' will discard edits.)" +#define NAMETOOLONG "login name too long" + +extern int per_errno; + +extern int audit_crontab_modify(char *, char *, int); +extern int audit_crontab_delete(char *, int); +extern int audit_crontab_not_allowed(uid_t, char *); + +int err; +int cursor; +char *cf; +char *tnam; +char edtemp[5+13+1]; +char line[CTLINESIZE]; +static char login[UNAMESIZE]; +static char yeschr; +static char nochr; + +static int yes(void); +static int next_field(int, int); +static void catch(int); +static void crabort(char *); +static void cerror(char *); +static void copycron(FILE *); + +main(argc, argv) +int argc; +char **argv; +{ + int c, r; + int rflag = 0; + int lflag = 0; + int eflag = 0; + int errflg = 0; + char *pp; + FILE *fp, *tmpfp; + struct stat stbuf; + struct passwd *pwp; + time_t omodtime; + char *editor; + char buf[BUFSIZ]; + uid_t ruid; + pid_t pid; + int stat_loc; + int ret; + char real_login[UNAMESIZE]; + int tmpfd = -1; + pam_handle_t *pamh; + int pam_error; + + (void) setlocale(LC_ALL, ""); +#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ +#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ +#endif + (void) textdomain(TEXT_DOMAIN); + yeschr = *nl_langinfo(YESSTR); + nochr = *nl_langinfo(NOSTR); + + while ((c = getopt(argc, argv, "elr")) != EOF) + switch (c) { + case 'e': + eflag++; + break; + case 'l': + lflag++; + break; + case 'r': + rflag++; + break; + case '?': + errflg++; + break; + } + + if (eflag + lflag + rflag > 1) + errflg++; + + argc -= optind; + argv += optind; + if (errflg || argc > 1) + crabort(BADUSAGE); + + ruid = getuid(); + if ((pwp = getpwuid(ruid)) == NULL) + crabort(INVALIDUSER); + + if (strlcpy(real_login, pwp->pw_name, sizeof (real_login)) + >= sizeof (real_login)) + crabort(NAMETOOLONG); + + if ((eflag || lflag || rflag) && argc == 1) { + if ((pwp = getpwnam(*argv)) == NULL) + crabort(INVALIDUSER); + + if (!chkauthattr(CRONADMIN_AUTH, real_login)) { + if (pwp->pw_uid != ruid) + crabort(NOTROOT); + else + pp = getuser(ruid); + } else + pp = *argv++; + } else { + pp = getuser(ruid); + } + + if (pp == NULL) { + if (per_errno == 2) + crabort(BADSHELL); + else + crabort(INVALIDUSER); + } + if (strlcpy(login, pp, sizeof (login)) >= sizeof (login)) + crabort(NAMETOOLONG); + if (!allowed(login, CRONALLOW, CRONDENY)) + crabort(NOTALLOWED); + + /* Do account validation check */ + pam_error = pam_start("cron", pp, NULL, &pamh); + if (pam_error != PAM_SUCCESS) { + crabort((char *)pam_strerror(pamh, pam_error)); + } + pam_error = pam_acct_mgmt(pamh, PAM_SILENT); + if (pam_error != PAM_SUCCESS) { + (void) fprintf(stderr, gettext("Warning - Invalid account: " + "'%s' not allowed to execute cronjobs\n"), pp); + } + (void) pam_end(pamh, PAM_SUCCESS); + + + /* check for unaudited shell */ + if (audit_crontab_not_allowed(ruid, pp)) + crabort(AUDITREJECT); + + cf = xmalloc(strlen(CRONDIR)+strlen(login)+2); + strcat(strcat(strcpy(cf, CRONDIR), "/"), login); + + if (rflag) { + r = unlink(cf); + cron_sendmsg(DELETE, login, login, CRON); + audit_crontab_delete(cf, r); + exit(0); + } + if (lflag) { + if ((fp = fopen(cf, "r")) == NULL) + crabort(BADOPEN); + while (fgets(line, CTLINESIZE, fp) != NULL) + fputs(line, stdout); + fclose(fp); + exit(0); + } + if (eflag) { + if ((fp = fopen(cf, "r")) == NULL) { + if (errno != ENOENT) + crabort(BADOPEN); + } + (void) strcpy(edtemp, "/tmp/crontabXXXXXX"); + tmpfd = mkstemp(edtemp); + if (fchown(tmpfd, ruid, -1) == -1) { + (void) close(tmpfd); + crabort("fchown of temporary file failed"); + } + (void) close(tmpfd); + /* + * Fork off a child with user's permissions, + * to edit the crontab file + */ + if ((pid = fork()) == (pid_t)-1) + crabort("fork failed"); + if (pid == 0) { /* child process */ + /* give up super-user privileges. */ + setuid(ruid); + if ((tmpfp = fopen(edtemp, "w")) == NULL) + crabort("can't create temporary file"); + if (fp != NULL) { + /* + * Copy user's crontab file to temporary file. + */ + while (fgets(line, CTLINESIZE, fp) != NULL) { + fputs(line, tmpfp); + if (ferror(tmpfp)) { + fclose(fp); + fclose(tmpfp); + crabort("write error on" + "temporary file"); + } + } + if (ferror(fp)) { + fclose(fp); + fclose(tmpfp); + crabort(BADREAD); + } + fclose(fp); + } + if (fclose(tmpfp) == EOF) + crabort("write error on temporary file"); + if (stat(edtemp, &stbuf) < 0) + crabort("can't stat temporary file"); + omodtime = stbuf.st_mtime; + editor = getenv("VISUAL"); + if (editor == NULL) + editor = getenv("EDITOR"); + if (editor == NULL) + editor = "ed"; + (void) snprintf(buf, sizeof (buf), + "%s %s", editor, edtemp); + sleep(1); + + while (1) { + ret = system(buf); + /* sanity checks */ + if ((tmpfp = fopen(edtemp, "r")) == NULL) + crabort("can't open temporary file"); + if (fstat(fileno(tmpfp), &stbuf) < 0) + crabort("can't stat temporary file"); + if (stbuf.st_size == 0) + crabort("temporary file empty"); + if (omodtime == stbuf.st_mtime) { + (void) unlink(edtemp); + fprintf(stderr, gettext( + "The crontab file was not changed.\n")); + exit(1); + } + if ((ret) && (errno != EINTR)) { + /* + * Some editors (like 'vi') can return + * a non-zero exit status even though + * everything is okay. Need to check. + */ + fprintf(stderr, gettext(ED_ERROR)); + fflush(stderr); + if (isatty(fileno(stdin))) { + /* Interactive */ + fprintf(stdout, gettext(ED_PROMPT), + yeschr, nochr, nochr); + fflush(stdout); + + if (yes()) { + /* Edit again */ + continue; + } else { + /* Dump changes */ + (void) unlink(edtemp); + exit(1); + } + } else { + /* Non-interactive, dump changes */ + (void) unlink(edtemp); + exit(1); + } + } + exit(0); + } /* while (1) */ + } + + /* fix for 1125555 - ignore common signals while waiting */ + (void) signal(SIGINT, SIG_IGN); + (void) signal(SIGHUP, SIG_IGN); + (void) signal(SIGQUIT, SIG_IGN); + (void) signal(SIGTERM, SIG_IGN); + wait(&stat_loc); + if ((stat_loc & 0xFF00) != 0) + exit(1); + + if ((seteuid(ruid) < 0) || + ((tmpfp = fopen(edtemp, "r")) == NULL)) { + fprintf(stderr, "crontab: %s: %s\n", + edtemp, errmsg(errno)); + (void) unlink(edtemp); + exit(1); + } else + seteuid(0); + + copycron(tmpfp); + (void) unlink(edtemp); + } else { + if (argc == 0) + copycron(stdin); + else if (seteuid(getuid()) != 0 || (fp = fopen(argv[0], "r")) + == NULL) + crabort(BADOPEN); + else { + seteuid(0); + copycron(fp); + } + } + cron_sendmsg(ADD, login, login, CRON); +/* + * if (per_errno == 2) + * fprintf(stderr, gettext(WARNSHELL)); + */ + return (0); +} + +static void +copycron(fp) +FILE *fp; +{ + FILE *tfp; + char pid[6], *tnam_end; + int t; + + sprintf(pid, "%-5d", getpid()); + tnam = xmalloc(strlen(CRONDIR)+strlen(TMPFILE)+7); + strcat(strcat(strcat(strcpy(tnam, CRONDIR), "/"), TMPFILE), pid); + /* cut trailing blanks */ + tnam_end = strchr(tnam, ' '); + if (tnam_end != NULL) + *tnam_end = 0; + /* catch SIGINT, SIGHUP, SIGQUIT signals */ + if (signal(SIGINT, catch) == SIG_IGN) + signal(SIGINT, SIG_IGN); + if (signal(SIGHUP, catch) == SIG_IGN) signal(SIGHUP, SIG_IGN); + if (signal(SIGQUIT, catch) == SIG_IGN) signal(SIGQUIT, SIG_IGN); + if (signal(SIGTERM, catch) == SIG_IGN) signal(SIGTERM, SIG_IGN); + if ((t = creat(tnam, CRMODE)) == -1) crabort(BADCREATE); + if ((tfp = fdopen(t, "w")) == NULL) { + unlink(tnam); + crabort(BADCREATE); + } + err = 0; /* if errors found, err set to 1 */ + while (fgets(line, CTLINESIZE, fp) != NULL) { + cursor = 0; + while (line[cursor] == ' ' || line[cursor] == '\t') + cursor++; + /* fix for 1039689 - treat blank line like a comment */ + if (line[cursor] == '#' || line[cursor] == '\n') + goto cont; + if (next_field(0, 59)) continue; + if (next_field(0, 23)) continue; + if (next_field(1, 31)) continue; + if (next_field(1, 12)) continue; + if (next_field(0, 06)) continue; + if (line[++cursor] == '\0') { + cerror(EOLN); + continue; + } +cont: + if (fputs(line, tfp) == EOF) { + unlink(tnam); + crabort(BADCREATE); + } + } + fclose(fp); + fclose(tfp); + + /* audit differences between old and new crontabs */ + audit_crontab_modify(cf, tnam, err); + + if (!err) { + /* make file tfp the new crontab */ + unlink(cf); + if (link(tnam, cf) == -1) { + unlink(tnam); + crabort(BADCREATE); + } + } else + fprintf(stderr, "crontab: %s\n", gettext(ERRSFND)); + unlink(tnam); +} + +static int +next_field(lower, upper) +int lower, upper; +{ + int num, num2; + + while ((line[cursor] == ' ') || (line[cursor] == '\t')) cursor++; + if (line[cursor] == '\0') { + cerror(EOLN); + return (1); + } + if (line[cursor] == '*') { + cursor++; + if ((line[cursor] != ' ') && (line[cursor] != '\t')) { + cerror(UNEXPECT); + return (1); + } + return (0); + } + while (TRUE) { + if (!isdigit(line[cursor])) { + cerror(UNEXPECT); + return (1); + } + num = 0; + do { + num = num*10 + (line[cursor]-'0'); + } while (isdigit(line[++cursor])); + if ((num < lower) || (num > upper)) { + cerror(OUTOFBOUND); + return (1); + } + if (line[cursor] == '-') { + if (!isdigit(line[++cursor])) { + cerror(UNEXPECT); + return (1); + } + num2 = 0; + do { + num2 = num2*10 + (line[cursor]-'0'); + } while (isdigit(line[++cursor])); + if ((num2 < lower) || (num2 > upper)) { + cerror(OUTOFBOUND); + return (1); + } + } + if ((line[cursor] == ' ') || (line[cursor] == '\t')) break; + if (line[cursor] == '\0') { + cerror(EOLN); + return (1); + } + if (line[cursor++] != ',') { + cerror(UNEXPECT); + return (1); + } + } + return (0); +} + +static void +cerror(msg) +char *msg; +{ + fprintf(stderr, gettext("%scrontab: error on previous line; %s\n"), + line, msg); + err = 1; +} + + +static void +catch(int x) +{ + unlink(tnam); + exit(1); +} + +static void +crabort(msg) +char *msg; +{ + int sverrno; + + if (strcmp(edtemp, "") != 0) { + sverrno = errno; + (void) unlink(edtemp); + errno = sverrno; + } + if (tnam != NULL) { + sverrno = errno; + (void) unlink(tnam); + errno = sverrno; + } + fprintf(stderr, "crontab: %s\n", gettext(msg)); + exit(1); +} + +static int +yes(void) +{ + int first_char; + int dummy_char; + + first_char = dummy_char = getchar(); + while ((dummy_char != '\n') && + (dummy_char != '\0') && + (dummy_char != EOF)) + dummy_char = getchar(); + return (first_char == yeschr); +} diff --git a/usr/src/cmd/cron/elm.c b/usr/src/cmd/cron/elm.c new file mode 100644 index 0000000000..c8b03ea7c9 --- /dev/null +++ b/usr/src/cmd/cron/elm.c @@ -0,0 +1,487 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 + * or http://www.opensolaris.org/os/licensing. + * 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +/* + * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.4 */ +/************************************************************************** + *** General-Purpose Event List Manager *** + ************************************************************************** + + description: These routines maintain a time-ordered list of events. + functions available: + init : Creates and initializes the data structure. + See the reference for parameters to init. + add(&event,time,id) : Adds an event to the list. + remove(id) : Removes events (with appropriate id). + empty : Returns true if the list is empty, false otherwise. + first : Removes the element at the head of the list. + Returns a pointer to the event. + delete : Frees up all allocated storage associated + with the event list. + reference: Franta, W. R. and Maly, K., + "An efficient data structure for the + simulation event set ", CACM Vol. 20(8), + Aug 1977, pp. 596-602. + machine dependant: the constant INFINITY + + *************************************************************************/ + + +#include <sys/types.h> +#include <stdlib.h> + +extern void *xmalloc(size_t); + +#define INFINITY 2147483647L /* upper bound on time */ +#define NULL 0 /* a null pointer */ +#define TRUE 1 +#define FALSE 0 + +/* the following parameters are set in init */ +static int DU; /* number of time intervals */ +static time_t LB; /* lower bound on time */ +static time_t DT; /* width of interval */ +static int NLIM; /* max notices per sublist */ + +/* a notice points to an event. a notice has the following fields: + time = time of the event. + id = identifier for an event or class of events that may need + to be removed (other than at the front of the list). + event = pointer to the event. + isdummy = tells whether this notice points to a real event or + is just a dummy notice (one that is used to "mark off" + the time intervals that the user specifies in init). + key = points back to the key that points to this notice, + if there is one. + left = points to the notice immediately preceding this one. + right = points to the notice immediately following this one. */ +struct notice { time_t time; + int id; + void *event; + short int isdummy; + struct key *key; + struct notice *left; + struct notice *right; }; + +/* current points to the front of the list of notices (events) */ +struct notice *current=NULL; + +/* a key points to a sublist of notices. a key has the following fields: + time = max time of notices in sublist. + numnote = number of notices in sublist. + notice = pointer to the notice with max time. + left = points to the key immediately preceding this one. + right = points to the key immediately following this one. */ +struct key { time_t time; + int numnote; + struct notice *notice; + struct key *left; + struct key *right; }; + +/* the index list breaks the keys into time intervals as specified in init. + the index is "shifted" one time interval whenever el_first returns an + event with a time greater than the max time of the first interval + (eg. with intervals of a day which span one week (MTWTFSS), + if el_first finds the next event is on tuesday, then + the intervals of the event list get shifted (TWTFSSM). */ +struct index { struct key *key; + struct index *right; }; + +static struct index *index=NULL; /* index pts to the front of the index list */ + +/***********************/ +void +el_init(du,lb,dt,nlim) +/***********************/ +int du, nlim; +time_t lb, dt; +{ + int i; + time_t t; + struct index *indprev,*ind; + struct key *kprev, *k; + struct notice *nprev, *n; + + if ((du<1) || (dt<1) || (nlim<1)) return; + DU = du + 1; + LB = lb; + DT = dt; + NLIM = nlim; + + /* + * initialize index, keys, and notices + */ + + /* create first dummy notice */ + n= (struct notice *) xmalloc(sizeof(struct notice)); + n->time = LB; + n->isdummy = TRUE; + n->left = NULL; + nprev = n; + /* create first dummy key */ + k= (struct key *) xmalloc(sizeof(struct key)); + k->time = LB; + k->numnote = 1; + k->notice = n; + k->left = NULL; + kprev = k; + /* make notice point to key */ + n->key = k; + /* no index element to allocate this time */ + indprev = NULL; + /* create dummy notices, dummy keys, and index elements */ + t = LB; + for (i=1; i<DU; i++) { + t = t + DT; + n = (struct notice *) xmalloc(sizeof(struct notice)); + n->time = t; + n->isdummy = TRUE; + n->left = nprev; + nprev->right = n; + nprev = n; + k = (struct key *) xmalloc(sizeof(struct key)); + k->time = t; + k->numnote = 1; + k->notice = n; + k->left = kprev; + kprev->right = k; + kprev = k; + n->key = k; + ind = (struct index *) xmalloc(sizeof(struct index)); + ind->key = k; + if (indprev == NULL) index = ind; + else indprev->right = ind; + indprev = ind; } + /* create last dummy notice */ + n = (struct notice *) xmalloc(sizeof(struct notice)); + n->time = INFINITY; + n->isdummy = TRUE; + n->left = nprev; + n->right = NULL; + nprev->right = n; + /* create last dummy key */ + k = (struct key *) xmalloc(sizeof(struct key)); + k->time = INFINITY; + k->numnote = 1; + k->notice = n; + k->left = kprev; + k->right = NULL; + kprev->right = k; + n->key = k; + /* create last index element */ + ind = (struct index *) xmalloc(sizeof(struct index)); + ind->key = k; + ind->right = NULL; + indprev->right = ind; + + current = NULL; +} + + +/**************************/ +void +el_add(event,time,id) +/**************************/ +void *event; +int id; +time_t time; +{ +/* add works slightly differently than in the reference. if the + sublist to be inserted into is full (numnote = NLIM), + the sublist is split in half. thus the size of the sublists + in this implementation normally ranges from NLIM/2 to NLIM. */ + + struct index *ind; + struct key *k,*k2; + struct notice *n,*n2; + int i; + + if ((index==NULL) || (time<LB)) return; + + /* allocate new notice */ + n = (struct notice *) xmalloc(sizeof(struct notice)); + n->time = time; + n->id = id; + n->event = event; + n->isdummy = FALSE; + n->key = NULL; + + /* find the right interval */ + ind = index; + while ((ind->key)->time <= time) ind = ind->right; + + /* find the right key */ + k = (ind->key)->left; + while (k->time > time) k = k->left; + k = k->right; + + /* (k->time>time) and ((k->left)->time<=time) */ + if (k->numnote == NLIM) { + /* k's sublist is full, so split it */ + k->numnote = NLIM / 2; + n2 = k->notice; + for (i=1; i<=NLIM/2; i++) n2 = n2->left; + /* create a key which will point to notice n2 */ + k2 = (struct key *) xmalloc(sizeof(struct key)); + k2->time = n2->time; + k2->numnote = NLIM - NLIM/2; + k2->notice = n2; + k2->right = k; + k2->left = k->left; + k->left = k2; + (k2->left)->right = k2; + n2->key = k2; /* have n2 point back to k2 */ + /* which of the new sublists will hold the new notice? */ + if (k2->time > time) k = k2; } + + /* the new notice n is ready to be inserted + k points to the appropriate sublist */ + k->numnote = k->numnote + 1; + n2 = k->notice; + while (n2->time > time) n2 = n2->left; + n->right = n2->right; + n->left = n2; + (n2->right)->left = n; + n2->right = n; + + if ( (current == NULL) || (current->time > time) ) current = n; +} + + +/************************/ +void +el_remove(id,flag) +/************************/ +int id,flag; +{ +/* remove finds notices n that need to be removed by traversing thru + the notice list. if n is the sole element of a sublist, the + sublist is deleted. if not, an adjacent sublist is merged with + n's sublist, if that is possible. after these checks, n is removed. */ + + struct notice *n,*n2; + struct key *k,*kl,*kr; + + if ((index==NULL) || (current==NULL)) return; + + n = current; + while ( n != NULL) { + while ( (n!=NULL) && ((n->isdummy)||(n->id!=id)) ) n = n->right; + if (n != NULL) { + /* n should be deleted */ + if ( (n->key!=NULL) && ((n->key)->numnote==1) ) { + /* n = sole element of a sublist */ + k = n->key; + (k->left)->right = k->right; + (k->right)->left = k->left; + free(k); } + else { if (n->key != NULL) { + /* n has a key pointing to it */ + (n->left)->key = n->key; + (n->key)->time = (n->left)->time; + (n->key)->notice = n->left; } + /* find the key that points to this sublist */ + n2 = n; + while (n2->key == NULL) n2 = n2->right; + k = n2->key; + k->numnote = k->numnote - 1; + /* check if two adjacent sublists can be merged + first check left, then check right */ + kl = k->left; + kr = k->right; + if ( (!(kl->notice)->isdummy) && + ((kl->numnote+k->numnote)<=NLIM) ) { + /* delete the key to the left */ + (kl->notice)->key = NULL; + k->numnote += kl->numnote; + (kl->left)->right = k; + k->left = kl->left; + free(kl); } + else if ( (!(k->notice)->isdummy) && + ((kr->numnote+k->numnote)<=NLIM) ) { + /* delete this key */ + (k->notice)->key = NULL; + kr->numnote += k->numnote; + (k->left)->right = kr; + kr->left = k->left; + free(k); } + } + /* delete n, then advance n down the list */ + (n->left)->right = n->right; + (n->right)->left = n->left; + n2 = n->right; + free(n); + n = n2; + } + if (flag) break; + } + /* now reset current */ + k = (index->key)->left; + while (k->left != NULL) k = k->left; + n = (k->notice)->right; + while ((n!=NULL) && (n->isdummy)) n = n->right; + current = n; +} + + +/*************************/ +el_empty() +/*************************/ +{ + if (current == NULL) return(1); + else return(0); +} + + +/*************************/ +void * +el_first() +/*************************/ +{ + struct notice *n,*fn; + struct key *k,*fk; + struct index *ind,*fi; + int ctr,*val; + time_t next_int; + + if ((index==NULL) || (current==NULL)) return(NULL); + + while ((index->key)->time < current->time) { + if (DU == 2) { + /* only two intervals, so relabel first one */ + k = index->key; + k->time += DT; + (k->notice)->time += DT; + continue; } + /* remove the notice, key, and index corresponding + to the first time interval. Then split the + overflow interval into a normal interval + plus an overflow interval. */ + fi = index; + fk = fi->key; + fn = fk->notice; + (fn->left)->right = fn->right; + (fn->right)->left = fn->left; + (fk->left)->right = fk->right; + (fk->right)->left = fk->left; + index = index->right; + /* find where to split */ + ind = index; + while ((ind->right)->right != NULL) ind = ind->right; + /* ind points to the next to last index interval */ + k = ind->key; + next_int = k->time + DT; /* upper bound on new inter. */ + while (k->time < next_int) k = k->right; + /* k points to the appropriate sublist of notices */ + n = (k->notice)->left; + ctr = 1; + while (n->time >= next_int) { + ctr++; + n = n->left; } + n = n->right; + /* n points to first notice of the new overflow interval + ctr tells how many notices are in the first sublist + of the new overflow interval + insert the new index element */ + fi->right = ind->right; + ind->right = fi; + /* insert the new dummy key */ + fk->time = next_int; + fk->numnote = k->numnote - ctr + 1; + fk->left = k->left; + fk->right = k; + (k->left)->right = fk; + k->left = fk; + k->numnote = ctr; + /* insert the new dummy notice */ + fn->time = next_int; + fn->left = n->left; + fn->right = n; + (n->left)->right = fn; + n->left = fn; } + + /* remove the first element of the list */ + (current->left)->right = current->right; + (current->right)->left = current->left; + /* now update the numnote field in the appropriate key */ + n = current; + while ( n->key == NULL ) n = n->right; + k = n->key; + k->numnote = k->numnote - 1; + /* if numnote = 0 then this key must be removed */ + if (k->numnote == 0) { + (k->left)->right = k->right; + (k->right)->left = k->left; + free(k); } + + /* now set current to be the head of the list */ + fn = current->right; + while ( (fn != NULL) && (fn->isdummy) ) + fn = fn->right; + val = current->event; + free(current); + current = fn; + + return(val); +} + + +/******************/ +void +el_delete() +/******************/ +{ + /* el_delete frees up all the space associated with the event list */ + + struct index *ind,*ind2; + struct key *k,*k2; + struct notice *n,*n2; + + if (index==NULL) return; + ind = index; + k = ind->key; + while (k->left != NULL) k = k->left; + n = k->notice; + while (n!=NULL) { + n2 = n->right; + free(n); + n = n2; } + while (k!=NULL) { + k2 = k->right; + free(k); + k = k2; } + while (ind!=NULL) { + ind2 = ind->right; + free(ind); + ind = ind2; } + + index = NULL; + current = NULL; +} diff --git a/usr/src/cmd/cron/funcs.c b/usr/src/cmd/cron/funcs.c new file mode 100644 index 0000000000..32dc93733b --- /dev/null +++ b/usr/src/cmd/cron/funcs.c @@ -0,0 +1,286 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 + * or http://www.opensolaris.org/os/licensing. + * 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +/* + * Copyright 1988-2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.4 */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <fcntl.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdio.h> +#include <dirent.h> +#include <libintl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <tzfile.h> +#include "cron.h" + +#define CANTCD "can't change directory to the at directory" +#define NOREADDIR "can't read the at directory" +#define YEAR 1900 +extern int audit_cron_is_anc_name(char *); + +time_t +num(char **ptr) +{ + time_t n = 0; + while (isdigit(**ptr)) { + n = n*10 + (**ptr - '0'); + *ptr += 1; } + return (n); +} + + +static int dom[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +days_btwn(int m1, int d1, int y1, int m2, int d2, int y2) +{ + /* + * calculate the number of "full" days in between + * m1/d1/y1 and m2/d2/y2. + * NOTE: there should not be more than a year separation in the + * dates. also, m should be in 0 to 11, and d should be in 1 to 31. + */ + + int days; + int m; + + if ((m1 == m2) && (d1 == d2) && (y1 == y2)) + return (0); + if ((m1 == m2) && (d1 < d2)) { + /* + * In case of d2==29 ,d1==28 and m1==m2==Feb and year is not + * a leap year, this function should return the days till the + * the next Feb 29.See Bug 4257355. + */ + if (d2 > days_in_mon(m2, y2)) { + int p; + for (p = 1; ! isleap(y2+YEAR+p); p++); + return (p*365 + d2-d1-1); + } + return (d2-d1-1); + } + /* the remaining dates are on different months */ + days = (days_in_mon(m1, y1)-d1) + (d2-1); + m = (m1 + 1) % 12; + while (m != m2) { + if (m == 0) + y1++; + days += days_in_mon(m, y1); + m = (m + 1) % 12; + } + return (days); +} + +int +days_in_mon(int m, int y) +{ + /* + * returns the number of days in month m of year y + * NOTE: m should be in the range 0 to 11 + */ + return (dom[m] + (((m == 1) && isleap(y + YEAR)) ? 1 : 0)); +} + +void * +xmalloc(size_t size) +{ + char *p; + + if ((p = malloc(size)) == NULL) { + perror("malloc"); + exit(55); + } + return (p); +} + +void +cron_sendmsg(char action, char *login, char *fname, char etype) +{ + static int msgfd = -2; + struct message *pmsg; + int i; + + pmsg = &msgbuf; + if (msgfd == -2) { + if ((msgfd = open(FIFO, O_WRONLY|O_NDELAY)) < 0) { + if (errno == ENXIO || errno == ENOENT) + (void) fprintf(stderr, gettext("cron may not" + " be running - call your system" + " administrator\n")); + else + (void) fprintf(stderr, gettext( + "error in message queue open\n")); + return; + } + } + pmsg->etype = etype; + pmsg->action = action; + (void) strncpy(pmsg->fname, fname, FLEN); + (void) strncpy(pmsg->logname, login, LLEN); + if ((i = write(msgfd, pmsg, sizeof (struct message))) < 0) + (void) fprintf(stderr, gettext("error in message send\n")); + else if (i != sizeof (struct message)) + (void) fprintf(stderr, gettext( + "error in message send: Premature EOF\n")); +} + +char +*errmsg(int errnum) +{ + char *msg; + static char msgbuf[32]; + + msg = strerror(errnum); + + if (msg == NULL) { + (void) snprintf(msgbuf, sizeof (msgbuf), + gettext("Error %d"), errnum); + return (msgbuf); + } else + return (msg); +} + + + +int +filewanted(struct dirent *direntry) +{ + char *p; + register char c; + + p = direntry->d_name; + (void) num(&p); + if (p == direntry->d_name) + return (0); /* didn't start with a number */ + if (*p++ != '.') + return (0); /* followed by a period */ + c = *p++; + if (c < 'a' || c > 'z') + return (0); /* followed by a queue name */ + if (audit_cron_is_anc_name(direntry->d_name)) + return (0); + return (1); +} + +/* + * Scan the directory dirname calling select to make a list of selected + * directory entries then sort using qsort and compare routine dcomp. + * Returns the number of entries and a pointer to a list of pointers to + * struct direct (through namelist). Returns -1 if there were any errors. + */ + + +#ifdef DIRSIZ +#undef DIRSIZ + +#endif +#define DIRSIZ(dp) \ + (dp)->d_reclen + +int +ascandir(dirname, namelist, select, dcomp) + char *dirname; + struct dirent *(*namelist[]); + int (*select)(); + int (*dcomp)(); +{ + register struct dirent *d, *p, **names; + register int nitems; + register char *cp1, *cp2; + struct stat stb; + long arraysz; + DIR *dirp; + + if ((dirp = opendir(dirname)) == NULL) + return (-1); + if (fstat(dirp->dd_fd, &stb) < 0) + return (-1); + + /* + * estimate the array size by taking the size of the directory file + * and dividing it by a multiple of the minimum size entry. + */ + arraysz = (stb.st_size / 24); + names = (struct dirent **)malloc(arraysz * sizeof (struct dirent *)); + if (names == NULL) + return (-1); + + nitems = 0; + while ((d = readdir(dirp)) != NULL) { + if (select != NULL && !(*select)(d)) + continue; /* just selected names */ + + /* + * Make a minimum size copy of the data + */ + p = (struct dirent *)malloc(DIRSIZ(d)); + if (p == NULL) + return (-1); + p->d_ino = d->d_ino; + p->d_reclen = d->d_reclen; + /* p->d_namlen = d->d_namlen; */ + for (cp1 = p->d_name, cp2 = d->d_name; *cp1++ = *cp2++; ); + /* + * Check to make sure the array has space left and + * realloc the maximum size. + */ + if (++nitems >= arraysz) { + if (fstat(dirp->dd_fd, &stb) < 0) + return (-1); /* just might have grown */ + arraysz = stb.st_size / 12; + names = (struct dirent **)realloc((char *)names, + arraysz * sizeof (struct dirent *)); + if (names == NULL) + return (-1); + } + names[nitems-1] = p; + } + (void) closedir(dirp); + if (nitems && dcomp != NULL) + qsort(names, nitems, sizeof (struct dirent *), dcomp); + *namelist = names; + return (nitems); +} + +void * +xcalloc(size_t nElements, size_t size) +{ + void *p; + + if ((p = calloc(nElements, size)) == NULL) { + perror("calloc"); + exit(55); + } + return (p); +} diff --git a/usr/src/cmd/cron/inc.flg b/usr/src/cmd/cron/inc.flg new file mode 100644 index 0000000000..712c9e4f8f --- /dev/null +++ b/usr/src/cmd/cron/inc.flg @@ -0,0 +1,30 @@ +#!/bin/sh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (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 +# or http://www.opensolaris.org/os/licensing. +# 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 2005 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +echo_file usr/src/lib/common/common/values-xpg4.c diff --git a/usr/src/cmd/cron/permit.c b/usr/src/cmd/cron/permit.c new file mode 100644 index 0000000000..bea91e0fee --- /dev/null +++ b/usr/src/cmd/cron/permit.c @@ -0,0 +1,118 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 + * or http://www.opensolaris.org/os/licensing. + * 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +/* + * Copyright (c) 2000 by Sun Microsystems, Inc. + * All rights reserved. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.6 */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <pwd.h> +#include "cron.h" + +struct stat globstat; +#define exists(file) (stat(file, &globstat) == 0) +#define ROOT "root" + +int per_errno; /* status info from getuser */ +static int within(char *, char *); + + +char * +getuser(uid) +uid_t uid; +{ + struct passwd *nptr; + + if ((nptr = getpwuid(uid)) == NULL) { + per_errno = 1; + return (NULL); + } + if ((strcmp(nptr->pw_shell, SHELL) != 0) && + (strcmp(nptr->pw_shell, "") != 0)) { + per_errno = 2; + /* + * return NULL if you want crontab and at to abort + * when the users login shell is not /usr/bin/sh otherwise + * return pw_name + */ + return (nptr->pw_name); + } + return (nptr->pw_name); +} + +int +allowed(user, allow, deny) +char *user, *allow, *deny; +{ + if (exists(allow)) { + if (within(user, allow)) { + return (1); + } else { + return (0); + } + } else if (exists(deny)) { + if (within(user, deny)) { + return (0); + } else { + return (1); + } + } else if (chkauthattr(CRONUSER_AUTH, user)) { + return (1); + } else { + return (0); + } +} + +static int +within(username, filename) +char *username, *filename; +{ + char line[UNAMESIZE]; + FILE *cap; + int i; + + if ((cap = fopen(filename, "r")) == NULL) + return (0); + while (fgets(line, UNAMESIZE, cap) != NULL) { + for (i = 0; line[i] != '\0'; i++) { + if (isspace(line[i])) { + line[i] = '\0'; + break; } + } + if (strcmp(line, username) == 0) { + fclose(cap); + return (1); + } + } + fclose(cap); + return (0); +} diff --git a/usr/src/cmd/cron/svc-cron b/usr/src/cmd/cron/svc-cron new file mode 100644 index 0000000000..6dfa01b806 --- /dev/null +++ b/usr/src/cmd/cron/svc-cron @@ -0,0 +1,47 @@ +#!/sbin/sh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License, Version 1.0 only +# (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 +# or http://www.opensolaris.org/os/licensing. +# 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 2004 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# +# Start method script for the cron service. +# + +. /lib/svc/share/smf_include.sh + +if [ -p /etc/cron.d/FIFO ]; then + if /usr/bin/pgrep -x -u 0 -z `/sbin/zonename` cron >/dev/null 2>&1; then + echo "$0: cron is already running" + exit $SMF_EXIT_ERR_NOSMF + fi +fi + +if [ -x /usr/sbin/cron ]; then + /usr/bin/rm -f /etc/cron.d/FIFO + /usr/sbin/cron & +else + exit 1 +fi +exit $SMF_EXIT_OK |
