summaryrefslogtreecommitdiff
path: root/usr/src/cmd/cron/at.c
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/cron/at.c
downloadillumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/cron/at.c')
-rw-r--r--usr/src/cmd/cron/at.c1012
1 files changed, 1012 insertions, 0 deletions
diff --git a/usr/src/cmd/cron/at.c b/usr/src/cmd/cron/at.c
new file mode 100644
index 0000000000..02e368b3db
--- /dev/null
+++ b/usr/src/cmd/cron/at.c
@@ -0,0 +1,1012 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
+/* All Rights Reserved */
+
+
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+#include <signal.h>
+#include <errno.h>
+#include <limits.h>
+#include <ulimit.h>
+#include <unistd.h>
+#include <locale.h>
+#include <libintl.h>
+#include <tzfile.h>
+#include <project.h>
+
+#include "cron.h"
+
+#define TMPFILE "_at" /* prefix for temporary files */
+/*
+ * Mode for creating files in ATDIR.
+ * Setuid bit on so that if an owner of a file gives that file
+ * away to someone else, the setuid bit will no longer be set.
+ * If this happens, atrun will not execute the file
+ */
+#define ATMODE (S_ISUID | S_IRUSR | S_IRGRP | S_IROTH)
+#define ROOT 0 /* user-id of super-user */
+#define MAXTRYS 100 /* max trys to create at job file */
+
+#define BADTIME "bad time specification"
+#define BADQUEUE "queue name must be a single character a-z"
+#define NOTCQUEUE "queue c is reserved for cron entries"
+#define BADSHELL "because your login shell isn't /usr/bin/sh,"\
+ "you can't use at"
+#define WARNSHELL "commands will be executed using %s\n"
+#define CANTCD "can't change directory to the at directory"
+#define CANTCHOWN "can't change the owner of your job to you"
+#define CANTCREATE "can't create a job for you"
+#define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)"
+#define NOOPENDIR "can't open the at directory"
+#define NOTALLOWED "you are not authorized to use at. Sorry."
+#define USAGE\
+ "usage: at [-c|-k|-s] [-m] [-f file] [-p project] [-q queuename] "\
+ "-t time\n"\
+ " at [-c|-k|-s] [-m] [-f file] [-p project] [-q queuename] "\
+ "timespec\n"\
+ " at -l [-p project] [-q queuename] [at_job_id...]\n"\
+ " at -r at_job_id ...\n"
+
+#define FORMAT "%a %b %e %H:%M:%S %Y"
+
+/* Macros used in format for fscanf */
+#define AQ(x) #x
+#define BUFFMT(p) AQ(p)
+
+static int leap(int);
+static int atoi_for2(char *);
+static int check_queue(char *, int);
+static int list_jobs(int, char **, int, int);
+static int remove_jobs(int, char **, char *);
+static void usage(void);
+static void catch(int);
+static void copy(char *, FILE *, int);
+static void atime(struct tm *, struct tm *);
+static int not_this_project(char *);
+static char *mkjobname(time_t);
+static time_t parse_time(char *);
+static time_t gtime(struct tm *);
+void atabort(char *);
+void yyerror(void);
+extern int yyparse(void);
+
+extern void audit_at_delete(char *, char *, int);
+extern int audit_at_create(char *, int);
+extern int audit_cron_is_anc_name(char *);
+extern int audit_cron_delete_anc_file(char *, char *);
+
+/*
+ * Error in getdate(3G)
+ */
+static char *errlist[] = {
+/* 0 */ "",
+/* 1 */ "getdate: The DATEMSK environment variable is not set",
+/* 2 */ "getdate: Error on \"open\" of the template file",
+/* 3 */ "getdate: Error on \"stat\" of the template file",
+/* 4 */ "getdate: The template file is not a regular file",
+/* 5 */ "getdate: An error is encountered while reading the template",
+/* 6 */ "getdate: Malloc(3C) failed",
+/* 7 */ "getdate: There is no line in the template that matches the input",
+/* 8 */ "getdate: Invalid input specification"
+};
+
+int gmtflag = 0;
+int mday[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+uid_t user;
+struct tm *tp, at, rt;
+static int cshflag = 0;
+static int kshflag = 0;
+static int shflag = 0;
+static int mflag = 0;
+static int pflag = 0;
+static char *Shell;
+static char *tfname;
+static char pname[80];
+static char pname1[80];
+static short jobtype = ATEVENT; /* set to 1 if batch job */
+extern char *argp;
+extern int per_errno;
+static projid_t project;
+
+main(argc, argv)
+int argc;
+char **argv;
+{
+ FILE *inputfile;
+ int i, fd;
+ int try = 0;
+ int fflag = 0;
+ int lflag = 0;
+ int qflag = 0;
+ int rflag = 0;
+ int tflag = 0;
+ int c;
+ int tflen;
+ char *file;
+ char *login;
+ char *job;
+ char *jobfile = NULL; /* file containing job to be run */
+ char argpbuf[LINE_MAX], timebuf[80];
+ time_t now;
+ time_t when = 0;
+ struct tm *ct;
+ char *proj;
+ struct project prj, *pprj;
+ char mybuf[PROJECT_BUFSZ];
+ char ipbuf[PROJECT_BUFSZ];
+
+ (void) setlocale(LC_ALL, "");
+#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
+#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
+#endif
+ (void) textdomain(TEXT_DOMAIN);
+
+ user = getuid();
+ login = getuser(user);
+ if (login == NULL) {
+ if (per_errno == 2)
+ atabort(BADSHELL);
+ else
+ atabort(INVALIDUSER);
+ }
+
+ if (!allowed(login, ATALLOW, ATDENY))
+ atabort(NOTALLOWED);
+
+ while ((c = getopt(argc, argv, "cklmsrf:p:q:t:")) != EOF)
+ switch (c) {
+ case 'c':
+ cshflag++;
+ break;
+ case 'f':
+ fflag++;
+ jobfile = optarg;
+ break;
+ case 'k':
+ kshflag++;
+ break;
+ case 'l':
+ lflag++;
+ break;
+ case 'm':
+ mflag++;
+ break;
+ case 'p':
+ proj = optarg;
+ pprj = &prj;
+ if ((pprj = getprojbyname(proj, pprj,
+ (void *)&mybuf, sizeof (mybuf))) != NULL) {
+ project = pprj->pj_projid;
+ if (inproj(login, pprj->pj_name,
+ (void *)&ipbuf, sizeof (ipbuf)))
+ pflag++;
+ else {
+ (void) fprintf(stderr,
+ gettext("at: user %s is "
+ "not a member of "
+ "project %s (%d)\n"),
+ login, pprj->pj_name,
+ project);
+ exit(2);
+ }
+ break;
+ }
+ pprj = &prj;
+ if (isdigit(proj[0]) &&
+ (pprj = getprojbyid(atoi(proj), pprj,
+ (void *)&mybuf, sizeof (mybuf))) != NULL) {
+ project = pprj->pj_projid;
+ if (inproj(login, pprj->pj_name,
+ (void *)&ipbuf, sizeof (ipbuf)))
+ pflag++;
+ else {
+ (void) fprintf(stderr,
+ gettext("at: user %s is "
+ "not a member of "
+ "project %s (%d)\n"),
+ login, pprj->pj_name,
+ project);
+ exit(2);
+ }
+ break;
+ }
+ (void) fprintf(stderr, gettext("at: project "
+ "%s not found.\n"), proj);
+ exit(2);
+ break;
+ case 'q':
+ qflag++;
+ if (optarg[1] != '\0')
+ atabort(BADQUEUE);
+ jobtype = *optarg - 'a';
+ if ((jobtype < 0) || (jobtype > 25))
+ atabort(BADQUEUE);
+ if (jobtype == 2)
+ atabort(NOTCQUEUE);
+ break;
+ case 'r':
+ rflag++;
+ break;
+ case 's':
+ shflag++;
+ break;
+ case 't':
+ tflag++;
+ when = parse_time(optarg);
+ break;
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (lflag + rflag > 1)
+ usage();
+
+ if (lflag) {
+ if (cshflag || kshflag || shflag || mflag ||
+ fflag || tflag || rflag)
+ usage();
+ return (list_jobs(argc, argv, qflag, jobtype));
+ }
+
+ if (rflag) {
+ if (cshflag || kshflag || shflag || mflag ||
+ fflag || tflag || qflag)
+ usage();
+ return (remove_jobs(argc, argv, login));
+ }
+
+ if ((argc + tflag == 0) && (jobtype != BATCHEVENT))
+ usage();
+
+ if (cshflag + kshflag + shflag > 1)
+ atabort("ambiguous shell request");
+
+ time(&now);
+
+ if (jobtype == BATCHEVENT)
+ when = now;
+
+ if (when == 0) { /* figure out what time to run the job */
+ int argplen = sizeof (argpbuf) - 1;
+
+ argpbuf[0] = '\0';
+ argp = argpbuf;
+ i = 0;
+ while (i < argc) {
+ /* guard against buffer overflow */
+ argplen -= strlen(argv[i]) + 1;
+ if (argplen < 0)
+ atabort(BADTIME);
+
+ strcat(argp, argv[i]);
+ strcat(argp, " ");
+ i++;
+ }
+ if ((file = getenv("DATEMSK")) == 0 || file[0] == '\0') {
+ tp = localtime(&now);
+ /*
+ * Fix for 1047182 - we have to let yyparse
+ * check bounds on mday[] first, then fixup
+ * the leap year case.
+ */
+ yyparse();
+
+ mday[1] = 28 + leap(at.tm_year);
+
+ if (at.tm_mday > mday[at.tm_mon])
+ atabort("bad date");
+
+ atime(&at, &rt);
+ when = gtime(&at);
+ if (!gmtflag) {
+ when += timezone;
+ if (localtime(&when)->tm_isdst)
+ when -= (timezone-altzone);
+ }
+ } else { /* DATEMSK is set */
+ if ((ct = getdate(argpbuf)) == NULL)
+ atabort(errlist[getdate_err]);
+ else
+ when = mktime(ct);
+ }
+ }
+
+ if (when < now) /* time has already past */
+ atabort("too late");
+
+ tflen = strlen(ATDIR) + 1 + strlen(TMPFILE) +
+ 10 + 1; /* 10 for an INT_MAX pid */
+ tfname = xmalloc(tflen);
+ snprintf(tfname, tflen, "%s/%s%d", ATDIR, TMPFILE, getpid());
+
+ /* catch INT, HUP, TERM and QUIT signals */
+ if (signal(SIGINT, catch) == SIG_IGN)
+ signal(SIGINT, SIG_IGN);
+ if (signal(SIGHUP, catch) == SIG_IGN)
+ signal(SIGHUP, SIG_IGN);
+ if (signal(SIGQUIT, catch) == SIG_IGN)
+ signal(SIGQUIT, SIG_IGN);
+ if (signal(SIGTERM, catch) == SIG_IGN)
+ signal(SIGTERM, SIG_IGN);
+ if ((fd = open(tfname, O_CREAT|O_EXCL|O_WRONLY, ATMODE)) < 0)
+ atabort(CANTCREATE);
+ if (chown(tfname, user, getgid()) == -1) {
+ unlink(tfname);
+ atabort(CANTCHOWN);
+ }
+ close(1);
+ dup(fd);
+ close(fd);
+ sprintf(pname, "%s", PROTO);
+ sprintf(pname1, "%s.%c", PROTO, 'a'+jobtype);
+
+ /*
+ * Open the input file with the user's permissions.
+ */
+ if (jobfile != NULL) {
+ if ((seteuid(user) < 0) ||
+ (inputfile = fopen(jobfile, "r")) == NULL) {
+ unlink(tfname);
+ fprintf(stderr, "at: %s: %s\n", jobfile, errmsg(errno));
+ exit(1);
+ }
+ else
+ seteuid(0);
+ } else
+ inputfile = stdin;
+
+ copy(jobfile, inputfile, when);
+ while (rename(tfname, job = mkjobname(when)) == -1) {
+ sleep(1);
+ if (++try > MAXTRYS / 10) {
+ unlink(tfname);
+ atabort(CANTCREATE);
+ }
+ }
+ unlink(tfname);
+ if (audit_at_create(job, 0))
+ atabort(CANTCREATE);
+
+ cron_sendmsg(ADD, login, strrchr(job, '/')+1, AT);
+ if (per_errno == 2)
+ fprintf(stderr, gettext(WARNSHELL), Shell);
+ cftime(timebuf, FORMAT, &when);
+ fprintf(stderr, gettext("job %s at %s\n"),
+ strrchr(job, '/')+1, timebuf);
+ if (when - MINUTE < HOUR)
+ fprintf(stderr, gettext(
+ "at: this job may not be executed at the proper time.\n"));
+ return (0);
+}
+
+
+static char *
+mkjobname(t)
+time_t t;
+{
+ int i, fd;
+ char *name;
+
+ name = xmalloc(200);
+ for (i = 0; i < MAXTRYS; i++) {
+ sprintf(name, "%s/%ld.%c", ATDIR, t, 'a'+jobtype);
+ /* fix for 1099183, 1116833 - create file here, avoid race */
+ if ((fd = open(name, O_CREAT | O_EXCL, ATMODE)) > 0) {
+ close(fd);
+ return (name);
+ }
+ t += 1;
+ }
+ atabort("queue full");
+}
+
+
+static void
+catch(int x)
+{
+ unlink(tfname);
+ exit(1);
+}
+
+
+void
+atabort(msg)
+char *msg;
+{
+ fprintf(stderr, "at: %s\n", gettext(msg));
+
+ exit(1);
+}
+
+yywrap(void)
+{
+ return (1);
+}
+
+void
+yyerror(void)
+{
+ atabort(BADTIME);
+}
+
+/*
+ * add time structures logically
+ */
+static void
+atime(struct tm *a, struct tm *b)
+{
+ if ((a->tm_sec += b->tm_sec) >= 60) {
+ b->tm_min += a->tm_sec / 60;
+ a->tm_sec %= 60;
+ }
+ if ((a->tm_min += b->tm_min) >= 60) {
+ b->tm_hour += a->tm_min / 60;
+ a->tm_min %= 60;
+ }
+ if ((a->tm_hour += b->tm_hour) >= 24) {
+ b->tm_mday += a->tm_hour / 24;
+ a->tm_hour %= 24;
+ }
+ a->tm_year += b->tm_year;
+ if ((a->tm_mon += b->tm_mon) >= 12) {
+ a->tm_year += a->tm_mon / 12;
+ a->tm_mon %= 12;
+ }
+ a->tm_mday += b->tm_mday;
+ mday[1] = 28 + leap(a->tm_year);
+ while (a->tm_mday > mday[a->tm_mon]) {
+ a->tm_mday -= mday[a->tm_mon++];
+ if (a->tm_mon > 11) {
+ a->tm_mon = 0;
+ mday[1] = 28 + leap(++a->tm_year);
+ }
+ }
+
+}
+
+static int
+leap(int year)
+{
+ return (isleap(year + TM_YEAR_BASE));
+}
+
+/*
+ * return time from time structure
+ */
+static time_t
+gtime(tptr)
+struct tm *tptr;
+{
+ register i;
+ long tv;
+ int dmsize[12] =
+ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+
+ tv = 0;
+ for (i = 1970; i != tptr->tm_year+TM_YEAR_BASE; i++)
+ tv += (365 + isleap(i));
+ /*
+ * We call isleap since leap() adds
+ * 1900 onto any value passed
+ */
+
+ if (!leap(tptr->tm_year) && at.tm_mday == 29 && at.tm_mon == 1)
+ atabort("bad date - not a leap year");
+
+ if ((leap(tptr->tm_year)) && tptr->tm_mon >= 2)
+ ++tv;
+
+ for (i = 0; i < tptr->tm_mon; ++i)
+ tv += dmsize[i];
+ tv += tptr->tm_mday - 1;
+ tv = 24 * tv + tptr->tm_hour;
+ tv = 60 * tv + tptr->tm_min;
+ tv = 60 * tv + tptr->tm_sec;
+ return (tv);
+}
+
+/*
+ * make job file from proto + stdin
+ */
+static void
+copy(char *jobfile, FILE *inputfile, int when)
+{
+ register c;
+ register FILE *pfp;
+ register FILE *xfp;
+ char *shell;
+ char dirbuf[PATH_MAX + 1];
+ char line[LINE_MAX];
+ register char **ep;
+ mode_t um;
+ char *val;
+ extern char **environ;
+ int pfd[2];
+ pid_t pid;
+ uid_t realusr;
+ int ttyinput;
+ int ulimit_flag = 0;
+ struct rlimit rlp;
+ struct project prj, *pprj;
+ char pbuf[PROJECT_BUFSZ];
+ char pbuf2[PROJECT_BUFSZ];
+ char *user;
+
+ /*
+ * Fix for 1099381:
+ * If the inputfile is from a tty, then turn on prompting, and
+ * put out a prompt now, instead of waiting for a lot of file
+ * activity to complete.
+ */
+ ttyinput = isatty(fileno(inputfile));
+ if (ttyinput) {
+ fputs("at> ", stderr);
+ fflush(stderr);
+ }
+
+ /*
+ * Fix for 1053807:
+ * Determine what shell we should use to run the job. If the user
+ * didn't explicitly request that his/her current shell be over-
+ * ridden (shflag or cshflag), then we use the current shell.
+ */
+ if (cshflag)
+ Shell = shell = "/bin/csh";
+ else if (kshflag) {
+ Shell = shell = "/bin/ksh";
+ ulimit_flag = 1;
+ } else if (shflag) {
+ Shell = shell = "/bin/sh";
+ ulimit_flag = 1;
+ } else if (((Shell = val = getenv("SHELL")) != NULL) &&
+ (*val != '\0')) {
+ shell = "$SHELL";
+ if ((strstr(val, "/sh") != NULL) ||
+ (strstr(val, "/ksh") != NULL))
+ ulimit_flag = 1;
+ } else {
+ /* SHELL is NULL or unset, therefore use default */
+#ifdef XPG4
+ Shell = shell = "/usr/xpg4/bin/sh";
+#else
+ Shell = shell = "/bin/sh";
+#endif /* XPG4 */
+ ulimit_flag = 1;
+ }
+
+ printf(": %s job\n", jobtype ? "batch" : "at");
+ printf(": jobname: %.127s\n", (jobfile == NULL) ? "stdin" : jobfile);
+ printf(": notify by mail: %s\n", (mflag) ? "yes" : "no");
+
+ if (pflag) {
+ (void) printf(": project: %d\n", project);
+ } else {
+ /*
+ * Check if current user is a member of current project.
+ * This check is done here to avoid setproject() failure
+ * later when the job gets executed. If current user does
+ * not belong to current project, user's default project
+ * will be used instead. This is achieved by not specifying
+ * the project (": project: <project>\n") in the job file.
+ */
+ if ((user = getuser(getuid())) == NULL)
+ atabort(INVALIDUSER);
+ project = getprojid();
+ pprj = getprojbyid(project, &prj, pbuf, sizeof (pbuf));
+ if (pprj != NULL) {
+ if (inproj(user, pprj->pj_name, pbuf2, sizeof (pbuf2)))
+ (void) printf(": project: %d\n", project);
+ }
+ }
+
+ for (ep = environ; *ep; ep++) {
+ if (strchr(*ep, '\'') != NULL)
+ continue;
+ if ((val = strchr(*ep, '=')) == NULL)
+ continue;
+ *val++ = '\0';
+ printf("export %s; %s='%s'\n", *ep, *ep, val);
+ *--val = '=';
+ }
+ if ((pfp = fopen(pname1, "r")) == NULL &&
+ (pfp = fopen(pname, "r")) == NULL)
+ atabort("no prototype");
+ /*
+ * Put in a line to run the proper shell using the rest of
+ * the file as input. Note that 'exec'ing the shell will
+ * cause sh() to leave a /tmp/sh### file around. (1053807)
+ */
+ printf("%s << '...the rest of this file is shell input'\n", shell);
+
+ um = umask(0);
+ while ((c = getc(pfp)) != EOF) {
+ if (c != '$')
+ putchar(c);
+ else switch (c = getc(pfp)) {
+ case EOF:
+ goto out;
+ case 'd':
+ /*
+ * fork off a child with submitter's permissions,
+ * otherwise, when IFS=/, /usr/bin/pwd would be parsed
+ * by the shell as file "bin". The shell would
+ * then search according to the submitter's PATH
+ * and run the file bin with root permission
+ */
+
+ (void) fflush(stdout);
+ dirbuf[0] = NULL;
+ if (pipe(pfd) != 0)
+ atabort("pipe open failed");
+ realusr = getuid(); /* get realusr before the fork */
+ if ((pid = fork()) == (pid_t)-1)
+ atabort("fork failed");
+ if (pid == 0) { /* child process */
+ (void) close(pfd[0]);
+ /* remove setuid for pwd */
+ (void) setuid(realusr);
+ if ((xfp = popen("/usr/bin/pwd", "r"))
+ != NULL) {
+ fscanf(xfp, "%" BUFFMT(PATH_MAX) "s",
+ dirbuf);
+ (void) pclose(xfp);
+ xfp = fdopen(pfd[1], "w");
+ fprintf(xfp, "%s", dirbuf);
+ (void) fclose(xfp);
+ }
+ _exit(0);
+ }
+ (void) close(pfd[1]); /* parent process */
+ xfp = fdopen(pfd[0], "r");
+ fscanf(xfp, "%" BUFFMT(PATH_MAX) "s", dirbuf);
+ printf("%s", dirbuf);
+ (void) fclose(xfp);
+ break;
+ case 'm':
+ printf("%o", um);
+ break;
+ case '<':
+ if (ulimit_flag) {
+ if (getrlimit(RLIMIT_FSIZE, &rlp) == 0) {
+ if (rlp.rlim_cur == RLIM_INFINITY)
+ printf("ulimit unlimited\n");
+ else
+ printf("ulimit %lld\n",
+ rlp.rlim_cur / 512);
+ }
+ }
+ /*
+ * fix for 1113572 - use fputs() so that a
+ * newline isn't appended to the one returned
+ * with fgets(); 1099381 - prompt for input.
+ */
+ while (fgets(line, LINE_MAX, inputfile) != NULL) {
+ fputs(line, stdout);
+ if (ttyinput)
+ fputs("at> ", stderr);
+ }
+ if (ttyinput) /* clean up the final output */
+ fputs("<EOT>\n", stderr);
+ break;
+ case 't':
+ printf(":%lu", when);
+ break;
+ default:
+ putchar(c);
+ }
+ }
+out:
+ fclose(pfp);
+ fflush(NULL);
+}
+
+static int
+remove_jobs(int argc, char **argv, char *login)
+/* remove jobs that are specified */
+{
+ int i, r;
+ int error = 0;
+ struct stat buf;
+ struct passwd *pw;
+
+ pw = getpwuid(user);
+ if (pw == NULL) {
+ atabort("Invalid user.\n");
+ }
+
+ if (argc == 0)
+ usage();
+ if (chdir(ATDIR) == -1)
+ atabort(CANTCD);
+ for (i = 0; i < argc; i++)
+ if (strchr(argv[i], '/') != NULL) {
+ fprintf(stderr, "at: %s: not a valid job-id\n",
+ argv[i]);
+ } else if (stat(argv[i], &buf)) {
+ fprintf(stderr, "at: %s: ", argv[i]);
+ perror("");
+ } else if ((user != buf.st_uid) &&
+ (!chkauthattr(CRONADMIN_AUTH, pw->pw_name))) {
+ fprintf(stderr, "at: you don't own %s\n",
+ argv[i]);
+ error = 1;
+ } else {
+ if (chkauthattr(CRONADMIN_AUTH, pw->pw_name)) {
+ login = getuser((uid_t)buf.st_uid);
+ if (login == NULL) {
+ if (per_errno == 2)
+ atabort(BADSHELL);
+ else
+ atabort(INVALIDUSER);
+ }
+ }
+ cron_sendmsg(DELETE, login, argv[i], AT);
+ r = unlink(argv[i]);
+ audit_at_delete(argv[i], ATDIR, r);
+ }
+ return (error);
+}
+
+
+
+static int
+list_jobs(int argc, char **argv, int qflag, int queue)
+{
+ DIR *dir;
+ int i;
+ int error = 0;
+ char *patdir, *atdir, *ptr;
+ char timebuf[80];
+ time_t t;
+ struct stat buf, st1, st2;
+ struct dirent *dentry;
+ struct passwd *pw;
+ unsigned int atdirlen;
+ int r;
+ struct passwd *pwd, pwds;
+ char buf_pwd[1024];
+ char job_file[PATH_MAX];
+
+ pwd = getpwuid_r(user, &pwds, buf_pwd, sizeof (buf_pwd));
+ if (pwd == NULL) {
+ atabort("Invalid user.\n");
+ }
+
+ /* list jobs for user */
+ if (chdir(ATDIR) == -1)
+ atabort(CANTCD);
+
+ atdirlen = strlen(ATDIR);
+ atdir = xmalloc(atdirlen + 1);
+ strcpy(atdir, ATDIR);
+ patdir = strrchr(atdir, '/');
+ *patdir = '\0';
+ if (argc == 0) {
+ /* list all jobs for a user */
+ if (stat(ATDIR, &st1) != 0 || stat(atdir, &st2) != 0)
+ atabort("Can not get status of spooling"
+ "directory for at");
+ if ((dir = opendir(ATDIR)) == NULL)
+ atabort(NOOPENDIR);
+ while (1) {
+ if ((dentry = readdir(dir)) == NULL)
+ break;
+ if ((dentry->d_ino == st1.st_ino) ||
+ (dentry->d_ino == st2.st_ino))
+ continue;
+ if ((r = audit_cron_is_anc_name(dentry->d_name)) == 1)
+ continue;
+ if (stat(dentry->d_name, &buf)) {
+ unlink(dentry->d_name);
+ audit_cron_delete_anc_file(dentry->d_name,
+ NULL);
+ continue;
+ }
+ if ((!chkauthattr(CRONADMIN_AUTH, pwd->pw_name)) &&
+ (buf.st_uid != user))
+ continue;
+ ptr = dentry->d_name;
+ if (((t = num(&ptr)) == 0) || (*ptr != '.'))
+ continue;
+ strcpy(job_file, patdir);
+ strcat(job_file, dentry->d_name);
+ if (pflag && not_this_project(job_file))
+ continue;
+ ascftime(timebuf, FORMAT, localtime(&t));
+ if ((chkauthattr(CRONADMIN_AUTH, pwd->pw_name)) &&
+ ((pw = getpwuid(buf.st_uid)) != NULL)) {
+ if (!qflag || (qflag &&
+ check_queue(ptr, queue)))
+ printf("user = %s\t%s\t%s\n",
+ pw->pw_name, dentry->d_name,
+ timebuf);
+ } else
+ if (!qflag || (qflag &&
+ check_queue(ptr, queue)))
+ printf("%s\t%s\n",
+ dentry->d_name, timebuf);
+ }
+ (void) closedir(dir);
+ } else /* list particular jobs for user */
+ for (i = 0; i < argc; i++) {
+ ptr = argv[i];
+ strlcpy(job_file, patdir, PATH_MAX);
+ strlcat(job_file, ptr, PATH_MAX);
+ if (((t = num(&ptr)) == 0) || (*ptr != '.')) {
+ fprintf(stderr, gettext(
+ "at: invalid job name %s\n"), argv[i]);
+ error = 1;
+ } else if (stat(argv[i], &buf)) {
+ fprintf(stderr, "at: %s: ", argv[i]);
+ perror("");
+ error = 1;
+ } else if ((user != buf.st_uid) &&
+ (!chkauthattr(CRONADMIN_AUTH, pwd->pw_name))) {
+ fprintf(stderr, gettext(
+ "at: you don't own %s\n"), argv[i]);
+ error = 1;
+ } else if (pflag && not_this_project(job_file)) {
+ continue;
+ } else {
+ if (!qflag || (qflag &&
+ check_queue(ptr, queue))) {
+ ascftime(timebuf, FORMAT,
+ localtime(&t));
+ printf("%s\t%s\n", argv[i], timebuf);
+ }
+ }
+ }
+ return (error);
+}
+
+/*
+ * open the command file and read the project id line
+ * compare to the project number provided via -p on the command line
+ * return 0 if they match, 1 if they don't match or an error occurs.
+ */
+#define SKIPCOUNT 3 /* lines to skip to get to project line in file */
+
+static int
+not_this_project(char *filename)
+{
+ FILE *fp;
+ projid_t sproj;
+ int i;
+
+ if ((fp = fopen(filename, "r")) == NULL)
+ return (1);
+
+ for (i = 0; i < SKIPCOUNT; i++)
+ fscanf(fp, "%*[^\n]\n");
+
+ fscanf(fp, ": project: %d\n", &sproj);
+ fclose(fp);
+
+ return (sproj == project ? 0 : 1);
+}
+
+static int
+check_queue(char *name, int queue)
+{
+ if ((name[strlen(name) - 1] - 'a') == queue)
+ return (1);
+ else
+ return (0);
+}
+
+static time_t
+parse_time(char *t)
+{
+ int century = 0;
+ int seconds = 0;
+ char *p;
+ time_t when = 0;
+ struct tm tm;
+
+ /*
+ * time in the following format (defined by the touch(1) spec):
+ * [[CC]YY]MMDDhhmm[.SS]
+ */
+ if ((p = strchr(t, '.')) != NULL) {
+ if (strchr(p+1, '.') != NULL)
+ atabort(BADTIME);
+ seconds = atoi_for2(p+1);
+ *p = '\0';
+ }
+
+ memset(&tm, 0, sizeof (struct tm));
+ when = time(0);
+ tm.tm_year = localtime(&when)->tm_year;
+
+ switch (strlen(t)) {
+ case 12: /* CCYYMMDDhhmm */
+ century = atoi_for2(t);
+ t += 2;
+ case 10: /* YYMMDDhhmm */
+ tm.tm_year = atoi_for2(t);
+ t += 2;
+ if (century == 0) {
+ if (tm.tm_year < 69)
+ tm.tm_year += 100;
+ } else
+ tm.tm_year += (century - 19) * 100;
+ case 8: /* MMDDhhmm */
+ tm.tm_mon = atoi_for2(t) - 1;
+ t += 2;
+ tm.tm_mday = atoi_for2(t);
+ t += 2;
+ tm.tm_hour = atoi_for2(t);
+ t += 2;
+ tm.tm_min = atoi_for2(t);
+ t += 2;
+ tm.tm_sec = seconds;
+ break;
+ default:
+ atabort(BADTIME);
+ }
+
+ if ((when = mktime(&tm)) == -1)
+ atabort(BADTIME);
+ if (tm.tm_isdst)
+ when -= (timezone-altzone);
+ return (when);
+}
+
+static int
+atoi_for2(char *p) {
+ int value;
+
+ value = (*p - '0') * 10 + *(p+1) - '0';
+ if ((value < 0) || (value > 99))
+ atabort(BADTIME);
+ return (value);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, USAGE);
+ exit(1);
+}