summaryrefslogtreecommitdiff
path: root/usr/src/cmd/cron
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/cron
downloadillumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/cron')
-rw-r--r--usr/src/cmd/cron/Makefile208
-rw-r--r--usr/src/cmd/cron/at.c1012
-rw-r--r--usr/src/cmd/cron/atq.c562
-rw-r--r--usr/src/cmd/cron/atrm.c417
-rw-r--r--usr/src/cmd/cron/att1.y227
-rw-r--r--usr/src/cmd/cron/att2.ed6
-rw-r--r--usr/src/cmd/cron/att2.l90
-rw-r--r--usr/src/cmd/cron/batch.sh40
-rw-r--r--usr/src/cmd/cron/batch.xpg4.sh40
-rw-r--r--usr/src/cmd/cron/cron.c3312
-rw-r--r--usr/src/cmd/cron/cron.dfl7
-rw-r--r--usr/src/cmd/cron/cron.h108
-rw-r--r--usr/src/cmd/cron/cron.xcl83
-rw-r--r--usr/src/cmd/cron/cron.xml113
-rw-r--r--usr/src/cmd/cron/crontab.c547
-rw-r--r--usr/src/cmd/cron/elm.c487
-rw-r--r--usr/src/cmd/cron/funcs.c286
-rw-r--r--usr/src/cmd/cron/inc.flg30
-rw-r--r--usr/src/cmd/cron/permit.c118
-rw-r--r--usr/src/cmd/cron/svc-cron47
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