# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # 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 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2020 Joyent, Inc. # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. DEFAULTFILES = cron.dfl include ../Makefile.cmd MANIFEST = cron.xml ROOTMANIFESTDIR = $(ROOTSVCSYSTEM) ROOTMETHOD = $(ROOTLIBSVCMETHOD)/svc-cron CPPFLAGS += -D_FILE_OFFSET_BITS=64 -I $(SRC)/common/util CERRWARN += -Wno-parentheses CERRWARN += $(CNOWARN_UNINIT) CERRWARN += -Wno-unused-variable CERRWARN += -Wno-unused-value SMOFF += signed_integer_overflow_check,all_func_returns ROOTVAR = $(ROOT)/var ROOTSPCRON = $(ROOTVAR)/spool/cron ROOTCROND = $(ROOTETC)/cron.d ROOTCRONTABS = $(ROOTSPCRON)/crontabs ROOTATJOBS = $(ROOTSPCRON)/atjobs ROOTLIBCRON = $(ROOTLIB)/cron PROG1 = cron PROG2 = at atq atrm crontab XPG6PROG = crontab XPG4PROG = at crontab PROG = $(PROG1) $(PROG2) PARSETEST = parsetest SCRIPT = batch XPG4SCRIPT = batch.xpg4 POFILE= $(PROG1)_cmd.po POFILES1= at.po crontab.po funcs.po batch.po POFILES= $(POFILES1) atrm.po $(POFILES1) : XGETFLAGS= -a -x $(PROG1).xcl ROOTDIRS = $(ROOTSPCRON) $(ROOTCROND) \ $(ROOTCRONTABS) $(ROOTATJOBS) ROOTPROG = $(PROG1:%=$(ROOTUSRSBIN)/%) $(PROG2:%=$(ROOTBIN)/%) \ $(SCRIPT:%=$(ROOTBIN)/%) \ $(XPG6PROG:%=$(ROOTXPG6BIN)/%) \ $(XPG4PROG:%=$(ROOTXPG4BIN)/%) \ $(XPG4SCRIPT:%.xpg4=$(ROOTXPG4BIN)/%) ROOTSYMLINK = $(ROOTLIBCRON) $(ROOTETC)/cron GETRESPSRC= $(SRC)/common/util/getresponse.c GETRESPOBJ= getresponse.o COMMONOBJ1= permit.o COMMONOBJ2= funcs.o COMMONOBJS= $(COMMONOBJ1) $(COMMONOBJ2) CRONOBJS= cron.o elm.o parse.o ATOBJS= at.o att1.o att2.o # Hammerhead: GCC 14 automatically links values-xpg4.o via CRT startup. # Explicitly adding it causes "multiple definition of __xpg4". XPG4OBJS= ATRMOBJS1= atrm.o ATRMOBJS= $(ATRMOBJS1) $(GETRESPOBJ) ATQOBJS= atq.o CRONTABOBJS1= crontab.o parse.o CRONTABOBJS= $(CRONTABOBJS1) $(GETRESPOBJ) # Hammerhead: GNU Make fix - parse.o is shared with crontab and compiled # without -DPARSETEST. Use a separate object for parsetest. PARSETESTOBJS= parsetest_parse.o # /usr/xpg*/bin/crontab isn't linked with values-xpg*.o since it isn't # required by any specific behavior differences; this makes these # setuid variants less likely to accidently trip over differences that # could unintentionally open up a security hole. XPG4COMMONOBJS= $(COMMONOBJS:%=objs.xpg4/%) XPG4CTOBJS= $(CRONTABOBJS:%=objs.xpg4/%) XPG4ATOBJS= $(ATOBJS:%=objs.xpg4/%) $(XPG4OBJS:%=objs.xpg4/%) XPG6COMMONOBJS= $(COMMONOBJS:%=objs.xpg6/%) XPG6CTOBJS= $(CRONTABOBJS:%=objs.xpg6/%) cron : POBJS = $(CRONOBJS) $(COMMONOBJ2) at : POBJS = $(ATOBJS) $(COMMONOBJS) at.xpg4 : POBJS = $(XPG4ATOBJS) $(XPG4COMMONOBJS) atrm : POBJS = $(ATRMOBJS) $(COMMONOBJS) atq : POBJS = $(ATQOBJS) $(COMMONOBJS) crontab : POBJS = $(CRONTABOBJS) $(COMMONOBJS) crontab.xpg4 : POBJS = $(XPG4CTOBJS) $(XPG4COMMONOBJS) crontab.xpg6 : POBJS = $(XPG6CTOBJS) $(XPG6COMMONOBJS) parsetest : POBJS = $(PARSETESTOBJS) CFLAGS += $(CCVERBOSE) NOBJS= $(CRONOBJS) $(ATOBJS) $(ATRMOBJS1) $(ATQOBJS) $(CRONTABOBJS1) \ $(COMMONOBJS) OBJS = $(NOBJS) $(XPG4COMMONOBJS) $(XPG4ATOBJS) $(XPG4CTOBJS) \ $(XPG6COMMONOBJS) $(XPG6CTOBJS) $(GETRESPOBJ) SRCS = $(NOBJS:%.o=%.c) $(GETRESPSRC) CLOBBERFILES += $(SCRIPT) $(XPG4SCRIPT) $(ROOTLIBCRON) : SYMLNKDEST = ../../etc/cron.d $(ROOTETC)/cron : SYMLNKDEST = ../usr/sbin/cron $(ROOTBIN)/at : FILEMODE = 04755 $(ROOTXPG4BIN)/at : FILEMODE = 04755 $(ROOTBIN)/atrm : FILEMODE = 04755 $(ROOTBIN)/atq : FILEMODE = 04755 $(ROOTBIN)/crontab : FILEMODE = 04555 $(ROOTXPG6BIN)/crontab : FILEMODE = 04555 $(ROOTXPG4BIN)/crontab : FILEMODE = 04555 $(ROOTUSRSBIN)/cron : FILEMODE = 0555 at : LDLIBS += -lbsm -lproject -lsecdb at.xpg4 : LDLIBS += -lbsm -lproject -lsecdb atq : LDLIBS += -lbsm -lsecdb atrm : LDLIBS += -lbsm -lsecdb cron : LDLIBS += -lbsm -lpam -lproject -lcontract -lzoneinfo -lcustr crontab : LDLIBS += -lbsm -lsecdb -lpam -lzoneinfo -lcustr crontab.xpg6 : LDLIBS += -lbsm -lsecdb -lpam -lzoneinfo -lcustr crontab.xpg4 : LDLIBS += -lbsm -lsecdb -lpam -lzoneinfo -lcustr parsetest : LDLIBS += -lcustr $(XPG4) : CFLAGS += -DXPG4 $(XPG6) : CFLAGS += -DXPG6 # parsetest CFLAGS applied via explicit compile rule for parsetest_parse.o $(ROOTSVCSYSTEM)/cron.xml : FILEMODE = 0444 $(ROOTLIBSVCMETHOD)/svc-cron : FILEMODE = 0555 # because of labels from yacc att1.o : CERRWARN += -Wno-unused-label att2.o : CERRWARN += -Wno-unused-label objs.xpg4/att1.o : CERRWARN += -Wno-unused-label objs.xpg4/att2.o : CERRWARN += -Wno-unused-label objs.xpg6/att1.o : CERRWARN += -Wno-unused-label objs.xpg6/att2.o : CERRWARN += -Wno-unused-label .KEEP_STATE: all : $(PROG) $(XPG4) $(XPG6) $(SCRIPT) $(XPG4SCRIPT) $(FILES) \ $(PARSETEST) install : all $(ROOTPROG) $(ROOTETCDEFAULTFILES) $(ROOTSYMLINK) \ $(ROOTMANIFEST) $(ROOTMETHOD) $(PROG) $(PARSETEST): $$(POBJS) $(LINK.c) $(POBJS) -o $@ $(LDLIBS) $(POST_PROCESS) $(XPG4) : objs.xpg4 $$(POBJS) $(LINK.c) $(POBJS) -o $@ $(LDLIBS) $(POST_PROCESS) $(XPG6) : objs.xpg6 $$(POBJS) $(LINK.c) $(POBJS) -o $@ $(LDLIBS) $(POST_PROCESS) # Hammerhead: Separate object for parsetest with -DPARSETEST parsetest_parse.o: parse.c $(COMPILE.c) -DPARSETEST -o $@ $< objs.xpg6/%.o: %.c $(COMPILE.c) -o $@ $< objs.xpg6: -@mkdir -p $@ objs.xpg4/%.o: %.c $(COMPILE.c) -o $@ $< objs.xpg4: -@mkdir -p $@ objs.xpg4/values-xpg4.o: ../../lib/crt/common/values-xpg4.c $(COMPILE.c) -o $@ ../../lib/crt/common/values-xpg4.c %.o: $(SRC)/common/util/%.c $(COMPILE.c) $(OUTPUT_OPTION) $< $(POST_PROCESS_O) objs.xpg4/%.o: $(SRC)/common/util/%.c $(COMPILE.c) -o $@ $< $(POST_PROCESS_O) objs.xpg6/%.o: $(SRC)/common/util/%.c $(COMPILE.c) -o $@ $< $(POST_PROCESS_O) # Hammerhead: GNU Make doesn't have dmake's built-in %: %.sh implicit rule batch: batch.sh $(RM) $@; $(CP) $< $@ batch.xpg4: batch.xpg4.sh $(RM) $@; $(CP) $< $@ 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 directories installed by Targetdirs #$(ROOTDIRS): # $(INS.dir) $(ROOTSYMLINK) : $(RM) $@; $(SYMLINK) $(SYMLNKDEST) $@ check: $(CHKMANIFEST) $(POFILE): $(POFILES) $(RM) $@; cat $(POFILES) > $@ clean : $(RM) $(OBJS) att1.h att1.c att2.c strip : $(STRIP) $(PROG) $(XPG4) $(XPG6) include ../Makefile.targ Copyright (c) 1983 Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the University of California, Berkeley and its contributors. 4. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PORTIONS OF CRON COMMAND FUNCTIONALITY /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * 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) 2011 Gary Mills * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 CANTCHUID "can't change user identifier" #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" 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 *); static void escapestr(const char *); void atabort(const char *) __NORETURN; int yyerror(const char *); 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(3C) */ 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; int main(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(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"); /* NOTREACHED */ } static void catch(int x) { unlink(tfname); exit(1); } void atabort(const char *msg) { fprintf(stderr, "at: %s\n", gettext(msg)); exit(1); } int yywrap(void) { return (1); } int yyerror(const char *s) { atabort(s); return (0); } /* * 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(struct tm *tptr) { int 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); } /* * Escape a string to be used inside the job shell script. */ static void escapestr(const char *str) { char c; (void) putchar('\''); while ((c = *str++) != '\0') { if (c != '\'') (void) putchar(c); else (void) fputs("'\\''", stdout); /* ' -> '\'' */ } (void) putchar('\''); } /* * make job file from proto + stdin */ static void copy(char *jobfile, FILE *inputfile, int when) { int c; FILE *pfp; char *shell; char dirbuf[PATH_MAX + 1]; char line[LINE_MAX]; char **ep; mode_t um; char *val; extern char **environ; uid_t realusr, effeusr; 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 their 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 */ Shell = shell = _PATH_BSHELL; 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: \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 ((val = strchr(*ep, '=')) == NULL) continue; *val++ = '\0'; (void) printf("export %s; %s=", *ep, *ep); escapestr(val); (void) putchar('\n'); *--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': /* * Must obtain current working directory as the user */ dirbuf[0] = '\0'; realusr = getuid(); effeusr = geteuid(); /* change euid for getcwd */ if (seteuid(realusr) < 0) { atabort(CANTCHUID); } if (getcwd(dirbuf, sizeof (dirbuf)) == NULL) { atabort( "can't obtain current working directory"); } /* change back afterwards */ if (seteuid(effeusr) < 0) { atabort(CANTCHUID); } escapestr(dirbuf); 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("\n", stderr); break; case 't': printf(":%lu", when); break; default: putchar(c); } } out: fclose(pfp); fflush(NULL); } /* remove jobs that are specified */ static int remove_jobs(int argc, char **argv, char *login) { 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) && (!cron_admin(pw->pw_name))) { fprintf(stderr, "at: you don't own %s\n", argv[i]); error = 1; } else { if (cron_admin(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 ((!cron_admin(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 ((cron_admin(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) && (!cron_admin(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; /* FALLTHROUGH */ 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; /* FALLTHROUGH */ 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); } /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. * Copyright 2021 Joyent, Inc. */ /* 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. */ /* * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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." static void atabortperror(char *msg); static void atabort(char *msg); static void aterror(char *msg); static void atperror(char *msg); static void usage(void); static void printjobname(char *file); static void printdate(char *filename); static void printrank(int n); static void printqueue(uid_t *uidlist, int nuids); int main(int argc, char **argv) { struct passwd *pp; /* password file entry pointer */ struct passwd pr; int i; int cflag = 0; /* print in order of creation time */ int nflag = 0; /* just print the number of jobs in */ /* queue */ 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 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()); if (pp == NULL) atabort(INVALIDUSER); if (!allowed(pp->pw_name, ATALLOW, ATDENY)) atabort(NOTALLOWED); pr.pw_uid = pp->pw_uid; pr.pw_name = pp->pw_name; /* * 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 (cron_admin(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 (!cron_admin(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 = scandir(".", &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); return (0); } /* * Count the number of jobs in the spooling area owned by a certain person(s). */ int countfiles(uid_t *uidlist, int nuids) { 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) */ 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 that user. */ 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). */ static void printqueue(uid_t *uidlist, int nuids) { int i, j; /* for loop indices */ int rank; /* rank of a job */ int entryfound; /* found file owned by users */ char *getname(); 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 of their files 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 their login name. Return -1 if no * such account name exists. */ uid_t getid(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 their user id. */ char * getname(uid_t uid) { 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 void printrank(int 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. */ static void printdate(char *filename) { time_t jobdate; struct tm *unpackeddate; char date[19]; /* 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. */ snprintf(date, sizeof (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 "???". */ static void printjobname(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 "scandir") */ int creation(struct dirent **d1, struct dirent **d2) { char *p1, *p2; int i; struct stat stbuf1, stbuf2; 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 "scandir") */ int execution(struct dirent **d1, struct dirent **d2) { char *p1, *p2; int i; char *name1, *name2; time_t time1, time2; int seq1, seq2; 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. */ static void usage(void) { fprintf(stderr, "usage: atq [-c] [-n] [name ...]\n"); exit(1); } static void aterror(char *msg) { fprintf(stderr, "atq: %s\n", msg); } static void atperror(char *msg) { fprintf(stderr, "atq: %s: %s\n", msg, errmsg(errno)); } static void atabort(char *msg) { aterror(msg); exit(1); } static void atabortperror(char *msg) { atperror(msg); exit(1); } /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* 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. */ /* * synopsis: atrm [-f] [-i] [-a] [[job #] [user] ...] * * * Remove "at" jobs. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cron.h" #include "getresponse.h" extern time_t num(); extern char *errmsg(); 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" static void usage(void); static void atabortperror(char *msg); static void atabort(char *msg); static void atperror(char *msg); static void atperror2(char *msg, char *name); static void aterror(char *msg); static void powner(char *file); int getjoblist(struct dirent ***, struct stat ***, int (*)()); int removentry(char *, struct stat *, uid_t); int main(int argc, char **argv) { int i; /* for loop index */ int numjobs; /* # of jobs in spooling area */ int allflag = 0; /* remove all jobs belonging to user? */ int jobexists; /* does a requested job exist? */ char *pp; 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, ""); (void) textdomain(TEXT_DOMAIN); 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 (cron_admin(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 their 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; } return (0); } /* * Print usage info and exit. */ static void usage(void) { 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 them who owns each file before * it is removed. Return TRUE if file removed, else FALSE. */ int removentry(char *filename, struct stat *statptr, uid_t user) { struct passwd *pwd; char *pp; int r; if (init_yes() < 0) { (void) fprintf(stderr, gettext(ERR_MSG_INIT_YES), strerror(errno)); exit(1); } if (!fflag) printf("%s: ", filename); if (user != statptr->st_uid && !cron_admin(login_authchk)) { if (!fflag) { printf("permission denied\n"); } return (0); } else { if (iflag) { if (cron_admin(login_authchk)) { printf("\t(owned by "); powner(filename); printf(") "); } printf(gettext("remove it? ")); if (yes() == 0) return (0); } if (cron_admin(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 "???". */ static void powner(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; } printf("%s", getname(statb.st_uid)); } int getjoblist(struct dirent ***namelistp, struct stat ***statlistp, int (*sortfunc)()) { int numjobs; struct dirent **namelist; int i; struct stat *statptr; /* pointer to file stat structure */ struct stat **statlist; 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 = scandir(".", 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) { atperror2("Can't stat", namelist[i]->d_name); continue; } statlist[i] = statptr; } *statlistp = statlist; return (numjobs); } /* * Get the full login name of a person using their user id. */ char * getname(uid_t uid) { struct passwd *pwdinfo; /* password info structure */ if ((pwdinfo = getpwuid(uid)) == 0) return ("???"); return (pwdinfo->pw_name); } static void aterror(char *msg) { fprintf(stderr, "atrm: %s\n", msg); } static void atperror(char *msg) { fprintf(stderr, "atrm: %s: %s\n", msg, errmsg(errno)); } static void atperror2(char *msg, char *name) { fprintf(stderr, "atrm: %s %s: %s\n", msg, name, errmsg(errno)); } static void atabort(char *msg) { aterror(msg); exit(1); } static void atabortperror(char *msg) { atperror(msg); exit(1); } %{ /* * 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 */ %{ #include "stdio.h" #include "ctype.h" #include "time.h" extern int yylex(void); extern int yyerror(const char *); 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 { (void) yyerror("bad time specification"); } ; 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_mintm_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; } ; %% /feof(yyin)/p s?/\*?? s?\*/?? p w q %{ /* * 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 #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); } %% #!/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 $* #!/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 $* /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2013 Joshua M. Clulow * * Copyright (c) 2014 Gary Mills * Copyright (c) 2016 by Delphix. All rights reserved. * Copyright 2022 Sebastian Wiedenroth * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* Copyright (c) 1987, 1988 Microsoft Corporation */ /* All Rights Reserved */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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 or at-job file. Resubmit it." #define BADPROJID "can't set project id for your job." #define CANTCDHOME "can't change directory to %s.\ \nYour commands will not be executed." #define CANTEXECSH "unable to exec the shell, %s, for one of your \ commands." #define CANT_STR_LEN (sizeof (CANTEXECSH) > sizeof (CANTCDHOME) ? \ sizeof (CANTEXECSH) : sizeof (CANTCDHOME)) #define NOREAD "can't read your crontab file. Resubmit it." #define BADTYPE "crontab or at-job file is not a regular file.\n" #define NOSTDIN "unable to create a standard input file for \ one of your crontab commands. \ \nThat command was not executed." #define NOTALLOWED "you are not authorized to use cron. Sorry." #define STDERRMSG "\n\n********************************************\ *****\nCron: The previous message is the \ standard output and standard error \ \nof one of your cron commands.\n" #define STDOUTERR "one of your commands generated output or errors, \ but cron was unable to mail you this output.\ \nRemember to redirect standard output and standard \ error for each of your commands." #define CLOCK_DRIFT "clock time drifted backwards after event!\n" #define PIDERR "unexpected pid returned %d (ignored)" #define CRONTABERR "Subject: Your crontab file has an error in it\n\n" #define MALLOCERR "out of space, cannot create new string\n" #define 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 ERR_NOTREG 4 /* error not a regular file */ #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]; static struct message msgbuf; struct shared { int count; /* usage count */ void (*free)(void *obj); /* routine that will free obj */ void *obj; /* object */ }; 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 */ struct shared *tz; /* timezone of this event */ struct shared *home; /* directory for this event */ struct shared *shell; /* shell for this event */ uint32_t max_random_delay; /* max. random delay */ } 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; static 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 */ struct runinfo *next; } *rthead; 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] = ENV_HOME; static char logname[LINE_MAX] = "LOGNAME="; static char tzone[LINE_MAX] = ENV_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 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 ignore_msg(char *, char *, struct event *); 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(int); static void mail(char *, char *, 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 void contract_set_template(void); static void contract_clear_template(void); static void contract_abandon_latest(pid_t); static void cte_init(void); static void cte_add(int, char *); static void cte_valid(void); static int cte_istoomany(void); static void cte_sendmail(char *); static int set_user_cred(const struct usr *, struct project *); static struct shared *create_shared_str(char *str); static struct shared *dup_shared(struct shared *obj); static void rel_shared(struct shared *obj); static void *get_obj(struct shared *obj); /* * 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 reset_needed; /* set to 1 when cron(8) needs to re-initialize */ static int refresh; static sigset_t defmask, sigmask; /* * 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, const 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(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 int 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); static int valid_entry(char *, int); static struct usr *create_ulist(char *, int); static void init_cronevent(char *, int); static void init_atevent(char *, time_t, int, int); static void update_atevent(struct usr *, char *, time_t, int); 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_needed is set to 1 whenever el_add() finds out that a cron * job is scheduled to be run before the time when cron(8) daemon * initialized. * Other cases where a reset is needed is when ex() finds that the * event to be executed is being run at the wrong time, or when idle() * determines that time was reset. * We immediately return to the top of the while (TRUE) loop in * main() where the event list is cleared and rebuilt, and reset_needed * is set back to 0. */ reset_needed = 0; /* * Only the privileged user can run this command. */ if (getuid() != 0) crabort(NOTALLOWED, 0); begin: (void) setlocale(LC_ALL, ""); /* fork unless 'nofork' is specified */ if ((argc <= 1) || (strcmp(argv[1], "nofork"))) { if ((rfork = fork()) != 0) { 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); /* setup THAW handler */ act.sa_handler = thaw_handler; act.sa_flags = 0; (void) sigemptyset(&act.sa_mask); (void) sigaction(SIGTHAW, &act, NULL); /* setup CHLD handler */ 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(&defmask); (void) sigemptyset(&sigmask); (void) sigaddset(&sigmask, SIGCLD); (void) sigaddset(&sigmask, SIGTHAW); (void) sigprocmask(SIG_BLOCK, &sigmask, NULL); t_old = init_time; last_time = t_old; for (;;) { /* MAIN LOOP */ t = time(NULL); if ((t_old > t) || (t-last_time > CUSHION) || reset_needed) { reset_needed = 0; /* * the time was set backwards or forward or * refresh is requested. */ if (refresh) msg("re-scheduling jobs"); else msg("time was reset, re-initializing"); el_delete(); u = uhead; while (u != NULL) { rm_ctevents(u); e = u->atevents; while (e != NULL) { free(e->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; /* * reset_needed might have been set in the functions * call path from initialize() */ if (reset_needed) { continue; } } 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, "%+", &next_event->time); (void) fprintf(stderr, "next_time=%ld %s\n", next_event->time, timebuf); #endif } if (ne_time > 0) { /* * reset_needed may be set in the functions call path * from idle() */ if (idle(ne_time) || reset_needed) { reset_needed = 1; 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 */ /* * reset_needed may be set in the functions call path * from ex() */ if (ex(next_event) || reset_needed) { reset_needed = 1; continue; } switch (next_event->etype) { case CRONEVENT: /* add cronevent back into the main event list */ if (delayed) { delayed = 0; break; } /* * check if time(0)< last_time. if so, then the * system clock has gone backwards. to prevent this * job from being started twice, we reschedule this * job for the >>next time after last_time<<, and * then set next_event->time to this. note that * crontab's resolution is 1 minute. */ 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, "%+", &next_event->time); (void) fprintf(stderr, "pushing back cron event %s at %ld (%s)\n", next_event->cmd, next_event->time, timebuf); #endif switch (el_add(next_event, next_event->time, (next_event->u)->ctid)) { case -1: ignore_msg("main", "cron", next_event); break; case -2: /* event time lower than init time */ reset_needed = 1; break; } break; default: /* remove at or batch job from system */ if (delayed) { delayed = 0; break; } 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 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(8) is waiting */ (void) sleep(60); } perror("FIFO"); crabort("cannot access fifo queue", REMOVE_FIFO|CONSOLE_MSG); } } else { if (NOFORK) { /* didn't fork... init(8) 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); } init_time = time(NULL); el_init(8, init_time, (time_t)(60*60*24), 10); init_time = time(NULL); el_init(8, init_time, (time_t)(60*60*24), 10); /* * 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(firstpass); 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(int first) { DIR *dir; struct dirent *dp; char *ptr; int jobtype; time_t tim; if (chdir(CRONDIR) == -1) crabort(BADCD, REMOVE_FIFO|CONSOLE_MSG); cwd = CRON; if ((dir = opendir(".")) == NULL) crabort(NOREADDIR, REMOVE_FIFO|CONSOLE_MSG); while ((dp = readdir(dir)) != NULL) { if (!valid_entry(dp->d_name, CRONEVENT)) continue; init_cronevent(dp->d_name, first); } (void) closedir(dir); if (chdir(ATDIR) == -1) { msg("cannot chdir to at directory"); return; } if ((dir = opendir(".")) == NULL) { msg("cannot read at at directory"); return; } cwd = AT; while ((dp = readdir(dir)) != NULL) { if (!valid_entry(dp->d_name, ATEVENT)) continue; ptr = dp->d_name; if (((tim = num(&ptr)) == 0) || (*ptr != '.')) continue; ptr++; if (!isalpha(*ptr)) continue; jobtype = *ptr - 'a'; if (jobtype >= NQUEUE) { cron_unlink(dp->d_name); continue; } init_atevent(dp->d_name, tim, jobtype, first); } (void) closedir(dir); } static int valid_entry(char *name, int type) { struct stat buf; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) return (0); /* skip over ancillary file names */ if (audit_cron_is_anc_name(name)) return (0); if (stat(name, &buf)) { mail(name, BADSTAT, ERR_UNIXERR); cron_unlink(name); return (0); } if (!S_ISREG(buf.st_mode)) { mail(name, BADTYPE, ERR_NOTREG); cron_unlink(name); return (0); } if (type == ATEVENT) { if (!(buf.st_mode & ISUID)) { cron_unlink(name); return (0); } } return (1); } struct usr * create_ulist(char *name, int type) { struct usr *u; u = xcalloc(1, sizeof (struct usr)); u->name = xstrdup(name); if (type == CRONEVENT) { u->ctexists = TRUE; u->ctid = ecid++; } else { u->ctexists = FALSE; u->ctid = 0; } u->uid = (uid_t)-1; u->gid = (uid_t)-1; u->nextusr = uhead; uhead = u; return (u); } void init_cronevent(char *name, int first) { struct usr *u; if (first) { u = create_ulist(name, CRONEVENT); readcron(u, 0); } else { if ((u = find_usr(name)) == NULL) { u = create_ulist(name, CRONEVENT); readcron(u, 0); } else { u->ctexists = TRUE; rm_ctevents(u); el_remove(u->ctid, 0); readcron(u, 0); } } } void init_atevent(char *name, time_t tim, int jobtype, int first) { struct usr *u; if (first) { u = create_ulist(name, ATEVENT); add_atevent(u, name, tim, jobtype); } else { if ((u = find_usr(name)) == NULL) { u = create_ulist(name, ATEVENT); add_atevent(u, name, tim, jobtype); } else { update_atevent(u, name, tim, jobtype); } } } static void mod_ctab(char *name, time_t reftime) { struct passwd *pw; struct stat buf; struct usr *u; char namebuf[LINE_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 = create_ulist(name, CRONEVENT); 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; readcron(u, reftime); } else { u->uid = pw->pw_uid; u->gid = pw->pw_gid; if (u->home != NULL) { 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); } } else { 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 their crontab\n", u->name); #endif rm_ctevents(u); el_remove(u->ctid, 0); readcron(u, reftime); } } static void mod_atjob(char *name, time_t reftime __unused) { char *ptr; time_t tim; struct passwd *pw; struct stat buf; struct usr *u; 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 = create_ulist(pw->pw_name, ATEVENT); u->home = xstrdup(pw->pw_dir); u->uid = pw->pw_uid; u->gid = pw->pw_gid; add_atevent(u, name, tim, jobtype); } else { u->uid = pw->pw_uid; u->gid = pw->pw_gid; free(u->home); u->home = xstrdup(pw->pw_dir); update_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 if (el_add(e, e->time, e->of.at.eventid) < 0) { ignore_msg("add_atevent", "at", e); } } void update_atevent(struct usr *u, char *name, time_t tim, int jobtype) { struct event *e; 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 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; struct shared *tz = NULL; struct shared *home = NULL; struct shared *shell = NULL; uint32_t max_random_delay = 0; int lineno = 0; const char *errstr; /* 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) { char *tmp; /* 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; if (strncmp(&line[cursor], ENV_TZ, strlen(ENV_TZ)) == 0) { if ((tmp = strchr(&line[cursor], '\n')) != NULL) { *tmp = '\0'; } if (!isvalid_tz(&line[cursor + strlen(ENV_TZ)], NULL, _VTZ_ALL)) { cte_add(lineno, line); break; } if (tz == NULL || strcmp(&line[cursor], get_obj(tz))) { rel_shared(tz); tz = create_shared_str(&line[cursor]); } continue; } if (strncmp(&line[cursor], ENV_HOME, strlen(ENV_HOME)) == 0) { if ((tmp = strchr(&line[cursor], '\n')) != NULL) { *tmp = '\0'; } if (home == NULL || strcmp(&line[cursor], get_obj(home))) { rel_shared(home); home = create_shared_str( &line[cursor + strlen(ENV_HOME)]); } continue; } if (strncmp(&line[cursor], ENV_SHELL, strlen(ENV_SHELL)) == 0) { if ((tmp = strchr(&line[cursor], '\n')) != NULL) { *tmp = '\0'; } if (shell == NULL || strcmp(&line[cursor], get_obj(shell))) { rel_shared(shell); shell = create_shared_str(&line[cursor]); } continue; } if (strncmp(&line[cursor], ENV_RANDOM_DELAY, strlen(ENV_RANDOM_DELAY)) == 0) { if ((tmp = strchr(&line[cursor], '\n')) != NULL) { *tmp = '\0'; } max_random_delay = strtonum( &line[cursor + strlen(ENV_RANDOM_DELAY)], 0, UINT32_MAX / 60, &errstr); if (errstr != NULL) { cte_add(lineno, line); break; } continue; } e = xmalloc(sizeof (struct event)); e->etype = CRONEVENT; if (next_field(0, 59, line, &cursor, &e->of.ct.minute) != CFOK || next_field(0, 23, line, &cursor, &e->of.ct.hour) != CFOK || next_field(1, 31, line, &cursor, &e->of.ct.daymon) != CFOK || next_field(1, 12, line, &cursor, &e->of.ct.month) != CFOK || next_field(0, 6, line, &cursor, &e->of.ct.dayweek) != CFOK) { #ifdef DEBUG (void) fprintf(stderr, "Error: %d %s", lineno, line); #endif 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; } /* set the timezone of this entry */ e->of.ct.tz = dup_shared(tz); /* set the shell of this entry */ e->of.ct.shell = dup_shared(shell); /* set the home of this entry */ e->of.ct.home = dup_shared(home); /* set the maximum random delay */ e->of.ct.max_random_delay = max_random_delay; /* 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 */ switch (el_add(e, e->time, u->ctid)) { case -1: ignore_msg("readcron", "cron", e); break; case -2: /* event time lower than init time */ reset_needed = 1; break; } cte_valid(); #ifdef DEBUG cftime(timebuf, "%+", &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); rel_shared(tz); rel_shared(shell); rel_shared(home); } /* * 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); } } #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 tz_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; int d1, day1, carry1, d2, day2, carry2, daysahead, mon, yr, db, wd; int 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*/ } static time_t next_time(struct event *e, time_t tflag) { time_t ret; if (e->of.ct.tz != NULL) { (void) putenv((char *)get_obj(e->of.ct.tz)); tzset(); ret = tz_next_time(e, tflag); (void) putenv(tzone); tzset(); } else { ret = tz_next_time(e, tflag); } if (e->of.ct.max_random_delay > 0) { ret += arc4random_uniform(e->of.ct.max_random_delay * 60 - 1); } return (ret); } /* * 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); rel_shared(e2->of.ct.tz); rel_shared(e2->of.ct.shell); rel_shared(e2->of.ct.home); 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; union { struct { char buf[PROJECT_BUFSZ]; char buf2[PROJECT_BUFSZ]; } p; char error[CANT_STR_LEN + PATH_MAX]; } bufs; char *tmpfile; FILE *fptr; time_t dhltime; projid_t projid; int projflag = 0; char *home; char *sh; 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); } rp = rinfo_get(0); /* allocating a new runinfo struct */ /* * 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); switch (el_add(next_event, next_event->time, (next_event->u)->ctid)) { case -1: ignore_msg("ex", "cron", next_event); break; case -2: /* event time lower than init time */ reset_needed = 1; break; } } else { /* etype == ATEVENT */ msg("correcting batch event\n"); if (el_add(next_event, next_event->time, next_event->of.at.eventid) < 0) { ignore_msg("ex", "at", next_event); } } 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 *)&bufs.p.buf, sizeof (bufs.p.buf))) == NULL || !inproj(e->u->name, pproj->pj_name, bufs.p.buf2, sizeof (bufs.p.buf2))) { 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 their 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->u)->uid == 0) { /* set default path */ /* path settable in defaults file */ envinit[2] = supath; } else { envinit[2] = path; } 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); } if (e->etype == CRONEVENT && e->of.ct.home != NULL) { home = (char *)get_obj(e->of.ct.home); } else { home = (e->u)->home; } (void) strlcat(homedir, home, sizeof (homedir)); (void) strlcat(logname, (e->u)->name, sizeof (logname)); environ = envinit; if (chdir(home) == -1) { snprintf(bufs.error, sizeof (bufs.error), CANTCDHOME, home); mail((e->u)->name, bufs.error, 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) { if (e->of.ct.tz) { (void) putenv((char *)get_obj(e->of.ct.tz)); } if (e->of.ct.shell) { char *name; sh = (char *)get_obj(e->of.ct.shell); name = strrchr(sh, '/'); if (name == NULL) name = sh; else name++; (void) putenv(sh); sh += strlen(ENV_SHELL); (void) execl(sh, name, "-c", e->cmd, 0); } else { (void) execl(SHELL, "sh", "-c", e->cmd, 0); sh = SHELL; } } else { /* type == ATEVENT */ (void) execl(SHELL, "sh", 0); sh = SHELL; } snprintf(bufs.error, sizeof (bufs.error), CANTEXECSH, sh); mail((e->u)->name, bufs.error, e->etype == CRONEVENT ? ERR_CANTEXECCRON : ERR_CANTEXECAT); exit(1); /*NOTREACHED*/ } /* * Main idle loop. * When timed out to run the job, return 0. * If for some reasons we need to reschedule jobs, return 1. */ static int idle(long t) { time_t now; refresh = 0; while (t > 0L) { if (msg_wait(t) != 0) { /* we need to run next job immediately */ return (0); } reap_child(); if (refresh) { /* We got THAW or REFRESH message */ return (1); } now = time(NULL); if (last_time > now) { /* clock has been reset to backward */ 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) == 0) { if (!S_ISLNK(buf.st_mode) && (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; char *lowname = (pr->jobtype == CRONEVENT ? "cron" : "at"); (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"); free(cmd); if (mailpipe == NULL) exit(127); (void) fprintf(mailpipe, "To: %s\n", p->name); (void) fprintf(mailpipe, "Subject: %s <%s@%s> %s\n", (pr->jobtype == CRONEVENT ? "Cron" : "At"), p->name, name.nodename, pr->jobname); /* * RFC3834 (Section 5) defines the Auto-Submitted header to prevent * vacation replies, et al, from being sent in response to * machine-generated mail. */ (void) fprintf(mailpipe, "Auto-Submitted: auto-generated\n"); /* * Additional headers for mail filtering and diagnostics: */ (void) fprintf(mailpipe, "X-Mailer: cron (%s %s)\n", name.sysname, name.release); (void) fprintf(mailpipe, "X-Cron-User: %s\n", p->name); (void) fprintf(mailpipe, "X-Cron-Host: %s\n", name.nodename); (void) fprintf(mailpipe, "X-Cron-Job-Name: %s\n", pr->jobname); (void) fprintf(mailpipe, "X-Cron-Job-Type: %s\n", lowname); /* * Message Body: * * (Temporary file is fopen'ed with "r", secure open.) */ (void) fprintf(mailpipe, "\n"); if (filesize > 0 && (st = fopen(pr->outfile, "r")) != NULL) { while ((nbytes = fread(iobuf, sizeof (char), BUFSIZ, st)) != 0) (void) fwrite(iobuf, sizeof (char), nbytes, mailpipe); (void) fclose(st); } else { (void) fprintf(mailpipe, "Job completed with no output.\n"); } (void) pclose(mailpipe); exit(0); } static int msg_wait(long tim) { struct message msg; int cnt; time_t reftime; fd_set fds; struct timespec tout, *toutp; static int pending_msg; static time_t pending_reftime; if (pending_msg) { process_msg(&msgbuf, pending_reftime); pending_msg = 0; return (0); } FD_ZERO(&fds); FD_SET(msgfd, &fds); toutp = NULL; if (tim != INFINITY) { #ifdef CRON_MAXSLEEP /* * CRON_MAXSLEEP can be defined to have cron periodically wake * up, so that cron can detect a change of TOD and adjust the * sleep time more frequently. */ tim = (tim > CRON_MAXSLEEP) ? CRON_MAXSLEEP : tim; #endif tout.tv_nsec = 0; tout.tv_sec = tim; toutp = &tout; } cnt = pselect(msgfd + 1, &fds, NULL, NULL, toutp, &defmask); if (cnt == -1 && errno != EINTR) perror("! pselect"); /* pselect timeout or interrupted */ if (cnt <= 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 == 0) 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; case REFRESH: refresh = 1; pmsg->etype = 0; return; default: msg("message received - bad format"); break; } if (next_event != NULL) { if (next_event->etype == CRONEVENT) { switch (el_add(next_event, next_event->time, (next_event->u)->ctid)) { case -1: ignore_msg("process_msg", "cron", next_event); break; case -2: /* event time lower than init time */ reset_needed = 1; break; } } else { /* etype == ATEVENT */ if (el_add(next_event, next_event->time, next_event->of.at.eventid) < 0) { ignore_msg("process_msg", "at", next_event); } } next_event = NULL; } (void) fflush(stdout); pmsg->etype = 0; } /* * Allocate a new or find an existing runinfo structure */ static struct runinfo * rinfo_get(pid_t pid) { struct runinfo *rp; if (pid == 0) { /* allocate a new entry */ rp = xcalloc(1, sizeof (struct runinfo)); rp->next = rthead; /* link the entry into the list */ rthead = rp; return (rp); } /* search the list for an existing entry */ for (rp = rthead; rp != NULL; rp = rp->next) { if (rp->pid == pid) break; } return (rp); } /* * Free a runinfo structure and its associated memory */ static void rinfo_free(struct runinfo *entry) { struct runinfo **rpp; struct runinfo *rp; #ifdef DEBUG (void) fprintf(stderr, "freeing job %s\n", entry->jobname); #endif for (rpp = &rthead; (rp = *rpp) != NULL; rpp = &rp->next) { if (rp == entry) { *rpp = rp->next; /* unlink the entry */ free(rp->outfile); free(rp->jobname); free(rp); break; } } } static void thaw_handler(int sig __unused) { refresh = 1; } static void cronend(int sig __unused) { crabort("SIGTERM", REMOVE_FIFO); } static void child_handler(int sig __unused) { ; } 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 ignore_msg(char *func_name, char *job_type, struct event *event) { msg("%s: ignoring %s job (user: %s, cmd: %s, time: %ld)", func_name, job_type, event->u->name ? event->u->name : "unknown", event->cmd ? event->cmd : "unknown", event->time); } 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 %s %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; switch (el_add(next_event, next_event->time, (next_event->u)->ctid)) { case -1: ignore_msg("resched", "cron", next_event); break; case -2: /* event time lower than init time */ reset_needed = 1; break; } 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(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) return (VUC_BADUSER); if (u->home != NULL) { 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); } } else { u->home = xmalloc(strlen(pw->pw_dir) + 1); (void) strcpy(u->home, pw->pw_dir); } if (u->uid != pw->pw_uid) u->uid = pw->pw_uid; if (u->gid != pw->pw_gid) u->gid = pw->pw_gid; /* * 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; } } static int cron_conv(int num_msg, const struct pam_message **msgs, struct pam_response **response __unused, void *appdata_ptr __unused) { const 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 void contract_set_template(void) { 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 void contract_clear_template(void) { 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)) != 0) { msg("could not obtain latest contract for " "PID %ld: %s", pid, strerror(r)); cts_lost++; return; } if ((r = contract_abandon_id(id)) != 0) { msg("could not abandon latest contract %ld: %s", id, strerror(r)); cts_lost++; return; } } static struct shared * create_shared(void *obj, void * (*obj_alloc)(void *obj), void (*obj_free)(void *)) { struct shared *out; if ((out = xmalloc(sizeof (struct shared))) == NULL) { return (NULL); } if ((out->obj = obj_alloc(obj)) == NULL) { free(out); return (NULL); } out->count = 1; out->free = obj_free; return (out); } static struct shared * create_shared_str(char *str) { return (create_shared(str, (void *(*)(void *))strdup, free)); } static struct shared * dup_shared(struct shared *obj) { if (obj != NULL) { obj->count++; } return (obj); } static void rel_shared(struct shared *obj) { if (obj && (--obj->count) == 0) { obj->free(obj->obj); free(obj); } } static void * get_obj(struct shared *obj) { return (obj->obj); } # # Copyright 1991 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # CRONLOG=YES /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2019 OmniOS Community Edition (OmniOSce) Association. * Copyright 2022 Sebastian Wiedenroth */ #ifndef _CRON_H #define _CRON_H #include /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #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 REFRESH 'r' #define QUE(x) ('a'+(x)) #define RCODE(x) (((x)>>8)&0377) #define TSTAT(x) ((x)&0377) /* This constant must be at least sysconf(_SC_LOGIN_NAME_MAX) in size */ #define UNAMESIZE 32 /* max chars in a user name */ #define FLEN UNAMESIZE #define LLEN UNAMESIZE /* * 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]; }; /* * Errors from the crontab field parser. */ typedef enum { CFOK = 0, CFEOLN, CFUNEXPECT, CFOUTOFBOUND, CFEOVERFLOW, CFENOMEM } cferror_t; #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 ENV_SHELL "SHELL=" #define ENV_TZ "TZ=" #define ENV_HOME "HOME=" #define ENV_RANDOM_DELAY "RANDOM_DELAY=" #define CTLINESIZE 1000 /* max chars in a crontab line */ extern int allowed(char *, char *, char *); extern int days_in_mon(int, int); extern char *errmsg(int); extern char *getuser(uid_t); extern void cron_sendmsg(char, char *, char *, char); extern time_t num(char **); extern void *xmalloc(size_t); extern void *xcalloc(size_t, size_t); extern char *xstrdup(const char *); extern int isvalid_shell(const char *shell); extern int isvalid_dir(const char *dir); extern int cron_admin(const char *); extern cferror_t next_field(uint_t, uint_t, char *, int *, char **); #ifdef __cplusplus } #endif #endif /* _CRON_H */ # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" msgid "" msgid "/etc/cron.d/at.allow" msgid "/etc/cron.d/at.deny" msgid "cklmsrf:p: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 "\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 "/bin/pwd" msgid "user = %s\t%s\t%s\n" msgid "malloc" msgid "calloc" msgid "%s/%s%d" msgid "%1024s" msgid "%*[^\n]\n" msgid "cron" msgid "vi" msgid "solaris.jobs.admin" /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * 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 2009 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 2020 OmniOS Community Edition (OmniOSce) Association. * Copyright 2022 Sebastian Wiedenroth */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cron.h" #include "getresponse.h" #if defined(XPG4) #define VIPATH "/usr/xpg4/bin/vi" #elif defined(XPG6) #define VIPATH "/usr/xpg6/bin/vi" #else #define _XPG_NOTDEFINED #define VIPATH "vi" #endif #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 \ "usage:\n" \ "\tcrontab [-u username] [file]\n" \ "\tcrontab [-u username] { -e | -l | -r }\n" \ "\tcrontab { -e | -l | -r } [username]" #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 OVERFLOW "too many elements." #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 (%s/%s)?\n"\ " ('%s' will discard edits.)" #define NAMETOOLONG "login name too long" #define BAD_TZ "Timezone unrecognized in: %s" #define BAD_SHELL "Invalid shell specified: %s" #define BAD_HOME "Unable to access directory: %s\t%s\n" #define BAD_RAND_DELAY "Invalid delay: %s" 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 *); static int err; int cursor; char *cf; char *tnam; char edtemp[5+13+1]; char line[CTLINESIZE]; static char login[UNAMESIZE]; static void catch(int); static void crabort(char *); static void cerror(char *); static void copycron(FILE *); int main(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; uid_t ruid; pid_t pid; int stat_loc; int ret; char real_login[UNAMESIZE]; char *user = NULL; int tmpfd = -1; pam_handle_t *pamh; int pam_error; char *buf; size_t buflen; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it wasn't */ #endif (void) textdomain(TEXT_DOMAIN); if (init_yes() < 0) { (void) fprintf(stderr, gettext(ERR_MSG_INIT_YES), strerror(errno)); exit(1); } while ((c = getopt(argc, argv, "elru:")) != EOF) { switch (c) { case 'e': eflag++; break; case 'l': lflag++; break; case 'r': rflag++; break; case 'u': user = optarg; break; case '?': errflg++; break; } } argc -= optind; argv += optind; if (eflag + lflag + rflag > 1) errflg++; if ((eflag || lflag || rflag) && argc > 0) { if (user != NULL) errflg++; else user = *argv; } 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 (user != NULL) { if ((pwp = getpwnam(user)) == NULL) crabort(INVALIDUSER); if (!cron_admin(real_login)) { if (pwp->pw_uid != ruid) crabort(NOTROOT); else pp = getuser(ruid); } else { pp = user; } } 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; #ifdef _XPG_NOTDEFINED editor = getenv("VISUAL"); if (editor == NULL) { #endif editor = getenv("EDITOR"); if (editor == NULL) editor = VIPATH; #ifdef _XPG_NOTDEFINED } #endif buflen = strlen(editor) + strlen(edtemp) + 2; buf = xmalloc(buflen); (void) snprintf(buf, buflen, "%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), yesstr, nostr, nostr); 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); /* * unlink edtemp as 'ruid'. The file contents will be held * since we open the file descriptor 'tmpfp' before calling * unlink. */ if (((ret = seteuid(ruid)) < 0) || ((tmpfp = fopen(edtemp, "r")) == NULL) || (unlink(edtemp) == -1)) { fprintf(stderr, "crontab: %s: %s\n", edtemp, errmsg(errno)); if ((ret < 0) || (tmpfp == NULL)) (void) unlink(edtemp); exit(1); } else seteuid(0); copycron(tmpfp); } 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(FILE *fp) { FILE *tfp; char pid[6], *tnam_end; int t; char buf[LINE_MAX]; const char *errstr; cferror_t cferr; 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 (strncmp(&line[cursor], ENV_TZ, strlen(ENV_TZ)) == 0) { char *x; (void) strncpy(buf, &line[cursor + strlen(ENV_TZ)], sizeof (buf)); if ((x = strchr(buf, '\n')) != NULL) *x = '\0'; if (isvalid_tz(buf, NULL, _VTZ_ALL)) { goto cont; } else { err = 1; fprintf(stderr, BAD_TZ, &line[cursor]); continue; } } else if (strncmp(&line[cursor], ENV_SHELL, strlen(ENV_SHELL)) == 0) { char *x; (void) strncpy(buf, &line[cursor + strlen(ENV_SHELL)], sizeof (buf)); if ((x = strchr(buf, '\n')) != NULL) *x = '\0'; if (isvalid_shell(buf)) { goto cont; } else { err = 1; fprintf(stderr, BAD_SHELL, &line[cursor]); continue; } } else if (strncmp(&line[cursor], ENV_HOME, strlen(ENV_HOME)) == 0) { char *x; (void) strncpy(buf, &line[cursor + strlen(ENV_HOME)], sizeof (buf)); if ((x = strchr(buf, '\n')) != NULL) *x = '\0'; if (chdir(buf) == 0) { goto cont; } else { err = 1; fprintf(stderr, BAD_HOME, &line[cursor], strerror(errno)); continue; } } else if (strncmp(&line[cursor], ENV_RANDOM_DELAY, strlen(ENV_RANDOM_DELAY)) == 0) { char *x; (void) strncpy(buf, &line[cursor + strlen(ENV_RANDOM_DELAY)], sizeof (buf)); if ((x = strchr(buf, '\n')) != NULL) *x = '\0'; (void) strtonum(buf, 0, UINT32_MAX / 60, &errstr); if (errstr == NULL) { goto cont; } else { err = 1; fprintf(stderr, BAD_RAND_DELAY, &line[cursor], strerror(errno)); continue; } } if ((cferr = next_field(0, 59, line, &cursor, NULL)) != CFOK || (cferr = next_field(0, 23, line, &cursor, NULL)) != CFOK || (cferr = next_field(1, 31, line, &cursor, NULL)) != CFOK || (cferr = next_field(1, 12, line, &cursor, NULL)) != CFOK || (cferr = next_field(0, 6, line, &cursor, NULL)) != CFOK) { switch (cferr) { case CFEOLN: cerror(EOLN); break; case CFUNEXPECT: cerror(UNEXPECT); break; case CFOUTOFBOUND: cerror(OUTOFBOUND); break; case CFEOVERFLOW: cerror(OVERFLOW); break; case CFENOMEM: (void) fprintf(stderr, "Out of memory\n"); exit(55); break; default: break; } 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 { crabort(ERRSFND); } unlink(tnam); } static void cerror(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(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); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* ********************************************************************** */ /* * 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. * Returns: 0 if success, * -2 if event time is lower * than Lower Bound time LB * -1 else * 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 #include extern void *xmalloc(size_t); #define INFINITY 2147483647L /* upper bound on time */ #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; }; /* index pts to the front of the index list */ static struct index *index = NULL; void el_init(int du, time_t lb, time_t dt, int nlim) { 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; } int el_add(void *event, time_t time, int id) { /* * 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; /* * time may be 0 when set by next_time() on error or an * invalid time specification of job */ if ((index == NULL) || (time <= 0)) { return (-1); } if (time < LB) { return (-2); } /* 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; return (0); } void el_remove(int id, int 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; } int el_empty(void) { if (current == NULL) return (1); else return (0); } void * el_first(void) { 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(void) { /* 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; } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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}; int 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 * xcalloc(size_t nElements, size_t size) { void *p; if ((p = calloc(nElements, size)) == NULL) { perror("calloc"); exit(55); } return (p); } char * xstrdup(const char *str) { int len; char *p; len = strlen(str); p = xmalloc(len + 1); (void) memcpy(p, str, len); p[len] = '\0'; return (p); } void cron_sendmsg(char action, char *login, char *fname, char etype) { static int msgfd = -2; struct message *pmsg, msgbuf; int i; (void) memset(&msgbuf, 0, sizeof (msgbuf)); 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) strlcpy(pmsg->fname, fname, FLEN); (void) strlcpy(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 msg_buf[32]; msg = strerror(errnum); if (msg == NULL) { (void) snprintf(msg_buf, sizeof (msg_buf), gettext("Error %d"), errnum); return (msg_buf); } else return (msg); } int filewanted(struct dirent *direntry) { char *p; 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); } int isvalid_shell(const char *shell) { char *t; int ret = 0; while ((t = getusershell()) != NULL) { if (strcmp(t, shell) == 0) { ret = 1; break; } } endusershell(); return (ret); } int isvalid_dir(const char *dir) { char *cwd = getcwd(NULL, 0); if (dir[0] != '/' || chdir(dir) == -1) { return (0); } if (cwd != NULL) { (void) chdir(cwd); } return (1); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * 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 2009 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 2019 OmniOS Community Edition (OmniOSce) Association. */ #include #include #include #include #include #include #include #include #include "cron.h" #ifdef PARSETEST #define xstrdup(x) strdup((x)) #endif #define MAX_ELEMENTS 60 #define READNUMBER(x) \ do { \ (x) = (x) * 10 + (line[cursor] - '0'); \ if ((x) > MAX_ELEMENTS) { \ err = CFOUTOFBOUND; \ goto out; \ } \ } while (isdigit(line[++cursor])) #define ADDELEMENT(x) \ do { \ if (eindex >= MAX_ELEMENTS) { \ err = CFEOVERFLOW; \ goto out; \ } \ elements[eindex++] = (x); \ } while (0) /* A more restrictive version of isspace(3C) that only looks for space/tab */ #define ISSPACE(x) \ ((x) == ' ' || (x) == '\t') cferror_t next_field(uint_t lower, uint_t upper, char *line, int *cursorp, char **ret) { uint_t elements[MAX_ELEMENTS]; uint_t eindex = 0, i; int cursor = *cursorp; cferror_t err = CFOK; assert(upper - lower <= MAX_ELEMENTS); if (ret != NULL) *ret = NULL; while (ISSPACE(line[cursor])) cursor++; if (line[cursor] == '\0') { err = CFEOLN; goto out; } for (;;) { uint_t num = 0, num2 = 0, step = 0; if (line[cursor] == '*') { cursor++; /* Short circuit for plain '*' */ if (ISSPACE(line[cursor])) { if (ret != NULL) *ret = xstrdup("*"); goto out; } /* * '*' is only permitted alongside other elements if * it has an associated step. */ if (line[cursor] != '/') { err = CFUNEXPECT; goto out; } /* Treat it as a range covering all values */ num = lower; num2 = upper; } else { if (!isdigit(line[cursor])) { err = CFUNEXPECT; goto out; } READNUMBER(num); if (num < lower || num > upper) { err = CFOUTOFBOUND; goto out; } if (line[cursor] == '-') { cursor++; if (!isdigit(line[cursor])) { err = CFUNEXPECT; goto out; } READNUMBER(num2); if (num2 < lower || num2 > upper) { err = CFOUTOFBOUND; goto out; } } else { ADDELEMENT(num); goto next; } } /* Look for a step definition */ if (line[cursor] == '/') { cursor++; if (!isdigit(line[cursor])) { err = CFUNEXPECT; goto out; } READNUMBER(step); if (step == 0) { err = CFOUTOFBOUND; goto out; } } else { step = 1; } if (num <= num2) { for (i = num; i <= num2; i += step) { ADDELEMENT(i); } } else { /* Wrap-around range */ for (i = num; i <= upper; i += step) { ADDELEMENT(i); } i -= (upper - lower + 1); for (; i <= num2; i += step) { ADDELEMENT(i); } } next: if (line[cursor] != ',') break; cursor++; } if (line[cursor] == '\0') { err = CFEOLN; goto out; } if (!ISSPACE(line[cursor])) { err = CFUNEXPECT; goto out; } if (ret != NULL) { custr_t *cs = NULL; if (custr_alloc(&cs) != 0) { err = CFENOMEM; goto out; } for (i = 0; i < eindex; i++) { if (custr_len(cs) > 0) { if (custr_appendc(cs, ',') != 0) { custr_free(cs); err = CFENOMEM; goto out; } } if (custr_append_printf(cs, "%u", elements[i]) != 0) { custr_free(cs); err = CFENOMEM; goto out; } } if (custr_len(cs) != 0) *ret = xstrdup(custr_cstr(cs)); custr_free(cs); } out: *cursorp = cursor; return (err); } #ifdef PARSETEST int main(int argc, char **argv) { int lower, upper, cursor = 0; char *ret; if (argc != 4) errx(1, " "); lower = atoi(argv[1]); upper = atoi(argv[2]); switch (next_field(lower, upper, argv[3], &cursor, &ret)) { case CFOK: (void) printf("%s\n", ret); break; case CFEOLN: (void) printf("UnexpectedEOL\n"); break; case CFUNEXPECT: (void) printf("UnexpectedChar\n"); break; case CFOUTOFBOUND: (void) printf("OutOfBounds\n"); break; case CFEOVERFLOW: (void) printf("Overflow\n"); break; case CFENOMEM: (void) printf("OutOfMemory\n"); break; default: (void) printf("UnknownError\n"); break; } return (0); } #endif /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #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_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(char *user, char *allow, char *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(char *username, char *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); } int cron_admin(const char *name) { return (chkauthattr(CRONADMIN_AUTH, name)); } #!/sbin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # 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 2006 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 `smf_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 /bin/rm -f /etc/cron.d/FIFO /usr/sbin/cron & else exit 1 fi exit $SMF_EXIT_OK