at.c revision b0d023d2a077dec1f7abfd95a9dd81fb8ec2cb10
2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2011 Gary Mills
2N/A *
2N/A * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
2N/A/* All Rights Reserved */
2N/A
2N/A#include <sys/resource.h>
2N/A#include <sys/stat.h>
2N/A#include <sys/types.h>
2N/A
2N/A#include <dirent.h>
2N/A#include <string.h>
2N/A#include <stdlib.h>
2N/A#include <fcntl.h>
2N/A#include <pwd.h>
2N/A#include <stdio.h>
2N/A#include <ctype.h>
2N/A#include <time.h>
2N/A#include <signal.h>
2N/A#include <errno.h>
2N/A#include <limits.h>
2N/A#include <ulimit.h>
2N/A#include <unistd.h>
2N/A#include <locale.h>
2N/A#include <libintl.h>
2N/A#include <tzfile.h>
2N/A#include <project.h>
2N/A#include <paths.h>
2N/A
2N/A#include "cron.h"
2N/A
2N/A#define TMPFILE "_at" /* prefix for temporary files */
2N/A/*
2N/A * Mode for creating files in ATDIR.
2N/A * Setuid bit on so that if an owner of a file gives that file
2N/A * away to someone else, the setuid bit will no longer be set.
2N/A * If this happens, atrun will not execute the file
2N/A */
2N/A#define ATMODE (S_ISUID | S_IRUSR | S_IRGRP | S_IROTH)
2N/A#define ROOT 0 /* user-id of super-user */
2N/A#define MAXTRYS 100 /* max trys to create at job file */
2N/A
2N/A#define BADTIME "bad time specification"
2N/A#define BADQUEUE "queue name must be a single character a-z"
2N/A#define NOTCQUEUE "queue c is reserved for cron entries"
2N/A#define BADSHELL "because your login shell isn't /usr/bin/sh,"\
2N/A "you can't use at"
2N/A#define WARNSHELL "commands will be executed using %s\n"
2N/A#define CANTCD "can't change directory to the at directory"
2N/A#define CANTCHOWN "can't change the owner of your job to you"
2N/A#define CANTCHUID "can't change user identifier"
2N/A#define CANTCREATE "can't create a job for you"
2N/A#define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)"
2N/A#define NOOPENDIR "can't open the at directory"
2N/A#define NOTALLOWED "you are not authorized to use at. Sorry."
2N/A#define USAGE\
2N/A "usage: at [-c|-k|-s] [-m] [-f file] [-p project] [-q queuename] "\
2N/A "-t time\n"\
2N/A " at [-c|-k|-s] [-m] [-f file] [-p project] [-q queuename] "\
2N/A "timespec\n"\
2N/A " at -l [-p project] [-q queuename] [at_job_id...]\n"\
2N/A " at -r at_job_id ...\n"
2N/A
2N/A#define FORMAT "%a %b %e %H:%M:%S %Y"
2N/A
2N/Astatic int leap(int);
2N/Astatic int atoi_for2(char *);
2N/Astatic int check_queue(char *, int);
2N/Astatic int list_jobs(int, char **, int, int);
2N/Astatic int remove_jobs(int, char **, char *);
2N/Astatic void usage(void);
2N/Astatic void catch(int);
2N/Astatic void copy(char *, FILE *, int);
2N/Astatic void atime(struct tm *, struct tm *);
2N/Astatic int not_this_project(char *);
2N/Astatic char *mkjobname(time_t);
2N/Astatic time_t parse_time(char *);
2N/Astatic time_t gtime(struct tm *);
2N/Avoid atabort(char *)__NORETURN;
2N/Avoid yyerror(void);
2N/Aextern int yyparse(void);
2N/A
2N/Aextern void audit_at_delete(char *, char *, int);
2N/Aextern int audit_at_create(char *, int);
2N/Aextern int audit_cron_is_anc_name(char *);
2N/Aextern int audit_cron_delete_anc_file(char *, char *);
2N/A
2N/A/*
2N/A * Error in getdate(3G)
2N/A */
2N/Astatic char *errlist[] = {
2N/A/* 0 */ "",
2N/A/* 1 */ "getdate: The DATEMSK environment variable is not set",
2N/A/* 2 */ "getdate: Error on \"open\" of the template file",
2N/A/* 3 */ "getdate: Error on \"stat\" of the template file",
2N/A/* 4 */ "getdate: The template file is not a regular file",
2N/A/* 5 */ "getdate: An error is encountered while reading the template",
2N/A/* 6 */ "getdate: Malloc(3C) failed",
2N/A/* 7 */ "getdate: There is no line in the template that matches the input",
2N/A/* 8 */ "getdate: Invalid input specification"
2N/A};
2N/A
2N/Aint gmtflag = 0;
2N/Aint mday[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
2N/Auid_t user;
2N/Astruct tm *tp, at, rt;
2N/Astatic int cshflag = 0;
2N/Astatic int kshflag = 0;
2N/Astatic int shflag = 0;
2N/Astatic int mflag = 0;
2N/Astatic int pflag = 0;
2N/Astatic char *Shell;
2N/Astatic char *tfname;
2N/Astatic char pname[80];
2N/Astatic char pname1[80];
2N/Astatic short jobtype = ATEVENT; /* set to 1 if batch job */
2N/Aextern char *argp;
2N/Aextern int per_errno;
2N/Astatic projid_t project;
2N/A
2N/Aint
2N/Amain(int argc, char **argv)
2N/A{
2N/A FILE *inputfile;
2N/A int i, fd;
2N/A int try = 0;
2N/A int fflag = 0;
2N/A int lflag = 0;
2N/A int qflag = 0;
2N/A int rflag = 0;
2N/A int tflag = 0;
2N/A int c;
2N/A int tflen;
2N/A char *file;
2N/A char *login;
2N/A char *job;
2N/A char *jobfile = NULL; /* file containing job to be run */
2N/A char argpbuf[LINE_MAX], timebuf[80];
2N/A time_t now;
2N/A time_t when = 0;
2N/A struct tm *ct;
2N/A char *proj;
2N/A struct project prj, *pprj;
2N/A char mybuf[PROJECT_BUFSZ];
2N/A char ipbuf[PROJECT_BUFSZ];
2N/A
2N/A (void) setlocale(LC_ALL, "");
2N/A#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
2N/A#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
2N/A#endif
2N/A (void) textdomain(TEXT_DOMAIN);
2N/A
2N/A user = getuid();
2N/A login = getuser(user);
2N/A if (login == NULL) {
2N/A if (per_errno == 2)
2N/A atabort(BADSHELL);
2N/A else
2N/A atabort(INVALIDUSER);
2N/A }
2N/A
2N/A if (!allowed(login, ATALLOW, ATDENY))
2N/A atabort(NOTALLOWED);
2N/A
2N/A while ((c = getopt(argc, argv, "cklmsrf:p:q:t:")) != EOF)
2N/A switch (c) {
2N/A case 'c':
2N/A cshflag++;
2N/A break;
2N/A case 'f':
2N/A fflag++;
2N/A jobfile = optarg;
2N/A break;
2N/A case 'k':
2N/A kshflag++;
2N/A break;
2N/A case 'l':
2N/A lflag++;
2N/A break;
2N/A case 'm':
2N/A mflag++;
2N/A break;
2N/A case 'p':
2N/A proj = optarg;
2N/A pprj = &prj;
2N/A if ((pprj = getprojbyname(proj, pprj,
2N/A (void *)&mybuf, sizeof (mybuf))) != NULL) {
2N/A project = pprj->pj_projid;
2N/A if (inproj(login, pprj->pj_name,
2N/A (void *)&ipbuf, sizeof (ipbuf)))
2N/A pflag++;
2N/A else {
2N/A (void) fprintf(stderr,
2N/A gettext("at: user %s is "
2N/A "not a member of "
2N/A "project %s (%d)\n"),
2N/A login, pprj->pj_name,
2N/A project);
2N/A exit(2);
2N/A }
2N/A break;
2N/A }
2N/A pprj = &prj;
2N/A if (isdigit(proj[0]) &&
2N/A (pprj = getprojbyid(atoi(proj), pprj,
2N/A (void *)&mybuf, sizeof (mybuf))) != NULL) {
2N/A project = pprj->pj_projid;
2N/A if (inproj(login, pprj->pj_name,
2N/A (void *)&ipbuf, sizeof (ipbuf)))
2N/A pflag++;
2N/A else {
2N/A (void) fprintf(stderr,
2N/A gettext("at: user %s is "
2N/A "not a member of "
2N/A "project %s (%d)\n"),
2N/A login, pprj->pj_name,
2N/A project);
2N/A exit(2);
2N/A }
2N/A break;
2N/A }
2N/A (void) fprintf(stderr, gettext("at: project "
2N/A "%s not found.\n"), proj);
2N/A exit(2);
2N/A break;
2N/A case 'q':
2N/A qflag++;
2N/A if (optarg[1] != '\0')
2N/A atabort(BADQUEUE);
2N/A jobtype = *optarg - 'a';
2N/A if ((jobtype < 0) || (jobtype > 25))
2N/A atabort(BADQUEUE);
2N/A if (jobtype == 2)
2N/A atabort(NOTCQUEUE);
2N/A break;
2N/A case 'r':
2N/A rflag++;
2N/A break;
2N/A case 's':
2N/A shflag++;
2N/A break;
2N/A case 't':
2N/A tflag++;
2N/A when = parse_time(optarg);
2N/A break;
2N/A default:
2N/A usage();
2N/A }
2N/A
2N/A argc -= optind;
2N/A argv += optind;
2N/A
2N/A if (lflag + rflag > 1)
2N/A usage();
2N/A
2N/A if (lflag) {
2N/A if (cshflag || kshflag || shflag || mflag ||
2N/A fflag || tflag || rflag)
2N/A usage();
2N/A return (list_jobs(argc, argv, qflag, jobtype));
2N/A }
2N/A
2N/A if (rflag) {
2N/A if (cshflag || kshflag || shflag || mflag ||
2N/A fflag || tflag || qflag)
2N/A usage();
2N/A return (remove_jobs(argc, argv, login));
2N/A }
2N/A
2N/A if ((argc + tflag == 0) && (jobtype != BATCHEVENT))
2N/A usage();
2N/A
2N/A if (cshflag + kshflag + shflag > 1)
2N/A atabort("ambiguous shell request");
2N/A
2N/A time(&now);
2N/A
2N/A if (jobtype == BATCHEVENT)
2N/A when = now;
2N/A
2N/A if (when == 0) { /* figure out what time to run the job */
2N/A int argplen = sizeof (argpbuf) - 1;
2N/A
2N/A argpbuf[0] = '\0';
2N/A argp = argpbuf;
2N/A i = 0;
2N/A while (i < argc) {
2N/A /* guard against buffer overflow */
2N/A argplen -= strlen(argv[i]) + 1;
2N/A if (argplen < 0)
2N/A atabort(BADTIME);
2N/A
2N/A strcat(argp, argv[i]);
2N/A strcat(argp, " ");
2N/A i++;
2N/A }
2N/A if ((file = getenv("DATEMSK")) == 0 || file[0] == '\0') {
2N/A tp = localtime(&now);
2N/A /*
2N/A * Fix for 1047182 - we have to let yyparse
2N/A * check bounds on mday[] first, then fixup
2N/A * the leap year case.
2N/A */
2N/A yyparse();
2N/A
2N/A mday[1] = 28 + leap(at.tm_year);
2N/A
2N/A if (at.tm_mday > mday[at.tm_mon])
2N/A atabort("bad date");
2N/A
2N/A atime(&at, &rt);
2N/A when = gtime(&at);
2N/A if (!gmtflag) {
2N/A when += timezone;
2N/A if (localtime(&when)->tm_isdst)
2N/A when -= (timezone-altzone);
2N/A }
2N/A } else { /* DATEMSK is set */
2N/A if ((ct = getdate(argpbuf)) == NULL)
2N/A atabort(errlist[getdate_err]);
2N/A else
2N/A when = mktime(ct);
2N/A }
2N/A }
2N/A
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");
/* NOTREACHED */
}
static void
catch(int x)
{
unlink(tfname);
exit(1);
}
void
atabort(msg)
char *msg;
{
fprintf(stderr, "at: %s\n", gettext(msg));
exit(1);
}
int
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;
{
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);
}
/*
* 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 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 */
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: <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':
/*
* 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);
}
printf("%s", 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("<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) &&
(!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;
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);
}