118334Speter/*
218334Speter *  at.c : Put file into atrun queue
318334Speter *  Copyright (C) 1993, 1994 Thomas Koenig
418334Speter *
518334Speter *  Atrun & Atq modifications
618334Speter *  Copyright (C) 1993  David Parsons
718334Speter *
818334Speter * Redistribution and use in source and binary forms, with or without
918334Speter * modification, are permitted provided that the following conditions
1018334Speter * are met:
1118334Speter * 1. Redistributions of source code must retain the above copyright
1218334Speter *    notice, this list of conditions and the following disclaimer.
1318334Speter * 2. The name of the author(s) may not be used to endorse or promote
1418334Speter *    products derived from this software without specific prior written
1518334Speter *    permission.
1618334Speter *
1718334Speter * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
1818334Speter * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1918334Speter * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2018334Speter * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
2118334Speter * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2218334Speter * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2318334Speter * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2418334Speter * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2518334Speter * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2618334Speter * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2718334Speter */
2818334Speter
2918334Speter#include <sys/cdefs.h>
3018334Speter__FBSDID("$FreeBSD$");
3118334Speter
3218334Speter#define _USE_BSD 1
3318334Speter
3418334Speter/* System Headers */
3518334Speter
3618334Speter#include <sys/param.h>
3718334Speter#include <sys/stat.h>
3818334Speter#include <sys/time.h>
3918334Speter#include <sys/wait.h>
4018334Speter#include <ctype.h>
4118334Speter#include <dirent.h>
4218334Speter#include <err.h>
4318334Speter#include <errno.h>
4418334Speter#include <fcntl.h>
4518334Speter#ifndef __FreeBSD__
4618334Speter#include <getopt.h>
4718334Speter#endif
4818334Speter#ifdef __FreeBSD__
4918334Speter#include <locale.h>
5018334Speter#endif
5118334Speter#include <pwd.h>
5218334Speter#include <signal.h>
5318334Speter#include <stddef.h>
5418334Speter#include <stdio.h>
5518334Speter#include <stdlib.h>
5618334Speter#include <string.h>
5718334Speter#include <time.h>
5818334Speter#include <unistd.h>
5918334Speter
6018334Speter/* Local headers */
6118334Speter
6218334Speter#include "at.h"
6318334Speter#include "panic.h"
6418334Speter#include "parsetime.h"
6518334Speter#include "perm.h"
6618334Speter
6718334Speter#define MAIN
6818334Speter#include "privs.h"
6918334Speter
7018334Speter/* Macros */
7118334Speter
7218334Speter#ifndef ATJOB_DIR
7318334Speter#define ATJOB_DIR "/usr/spool/atjobs/"
7418334Speter#endif
7518334Speter
7618334Speter#ifndef LFILE
7718334Speter#define LFILE ATJOB_DIR ".lockfile"
7818334Speter#endif
7918334Speter
8018334Speter#ifndef ATJOB_MX
8118334Speter#define ATJOB_MX 255
8218334Speter#endif
8318334Speter
8418334Speter#define ALARMC 10 /* Number of seconds to wait for timeout */
8518334Speter
8618334Speter#define SIZE 255
8718334Speter#define TIMESIZE 50
8818334Speter
8918334Speterenum { ATQ, ATRM, AT, BATCH, CAT };	/* what program we want to run */
9018334Speter
9118334Speter/* File scope variables */
9218334Speter
9318334Speterstatic const char *no_export[] = {
9418334Speter    "TERM", "TERMCAP", "DISPLAY", "_"
9518334Speter};
9618334Speterstatic int send_mail = 0;
9718334Speterstatic char *atinput = NULL;	/* where to get input from */
9818334Speterstatic char atqueue = 0;	/* which queue to examine for jobs (atq) */
9918334Speter
10018334Speter/* External variables */
10118334Speter
10218334Speterextern char **environ;
10318334Speterint fcreated;
10418334Speterchar atfile[] = ATJOB_DIR "12345678901234";
10518334Speterchar atverify = 0;		/* verify time instead of queuing job */
10618334Speterchar *namep;
10718334Speter
10818334Speter/* Function declarations */
10918334Speter
11018334Speterstatic void sigc(int signo);
11118334Speterstatic void alarmc(int signo);
11218334Speterstatic char *cwdname(void);
11318334Speterstatic void writefile(time_t runtimer, char queue);
11418334Speterstatic void list_jobs(long *, int);
11518334Speterstatic long nextjob(void);
11618334Speterstatic time_t ttime(const char *arg);
11718334Speterstatic int in_job_list(long, long *, int);
11818334Speterstatic long *get_job_list(int, char *[], int *);
11918334Speter
12018334Speter/* Signal catching functions */
12118334Speter
12218334Speterstatic void sigc(int signo __unused)
12318334Speter{
12418334Speter/* If the user presses ^C, remove the spool file and exit
12518334Speter */
12618334Speter    if (fcreated)
12718334Speter    {
12818334Speter	PRIV_START
12918334Speter	    unlink(atfile);
13018334Speter	PRIV_END
13118334Speter    }
13218334Speter
13318334Speter    _exit(EXIT_FAILURE);
13418334Speter}
13518334Speter
13618334Speterstatic void alarmc(int signo __unused)
13718334Speter{
13818334Speter    char buf[1024];
13918334Speter
14018334Speter    /* Time out after some seconds. */
14118334Speter    strlcpy(buf, namep, sizeof(buf));
14218334Speter    strlcat(buf, ": file locking timed out\n", sizeof(buf));
14318334Speter    write(STDERR_FILENO, buf, strlen(buf));
14418334Speter    sigc(0);
14518334Speter}
14618334Speter
14718334Speter/* Local functions */
14818334Speter
14918334Speterstatic char *cwdname(void)
15018334Speter{
15118334Speter/* Read in the current directory; the name will be overwritten on
15218334Speter * subsequent calls.
15318334Speter */
15418334Speter    static char *ptr = NULL;
15518334Speter    static size_t size = SIZE;
15618334Speter
15718334Speter    if (ptr == NULL)
15818334Speter	if ((ptr = malloc(size)) == NULL)
15918334Speter	    errx(EXIT_FAILURE, "virtual memory exhausted");
16018334Speter
16118334Speter    while (1)
16218334Speter    {
16318334Speter	if (ptr == NULL)
16418334Speter	    panic("out of memory");
16518334Speter
16618334Speter	if (getcwd(ptr, size-1) != NULL)
16718334Speter	    return ptr;
16818334Speter
16918334Speter	if (errno != ERANGE)
17018334Speter	    perr("cannot get directory");
17118334Speter
17218334Speter	free (ptr);
17318334Speter	size += SIZE;
17418334Speter	if ((ptr = malloc(size)) == NULL)
17518334Speter	    errx(EXIT_FAILURE, "virtual memory exhausted");
17618334Speter    }
17718334Speter}
17818334Speter
17918334Speterstatic long
18018334Speternextjob(void)
18118334Speter{
18218334Speter    long jobno;
18318334Speter    FILE *fid;
18418334Speter
18518334Speter    if ((fid = fopen(ATJOB_DIR ".SEQ", "r+")) != NULL) {
18618334Speter	if (fscanf(fid, "%5lx", &jobno) == 1) {
18718334Speter	    rewind(fid);
18818334Speter	    jobno = (1+jobno) % 0xfffff;	/* 2^20 jobs enough? */
18918334Speter	    fprintf(fid, "%05lx\n", jobno);
19018334Speter	}
19118334Speter	else
19218334Speter	    jobno = EOF;
19318334Speter	fclose(fid);
19418334Speter	return jobno;
19518334Speter    }
19618334Speter    else if ((fid = fopen(ATJOB_DIR ".SEQ", "w")) != NULL) {
19718334Speter	fprintf(fid, "%05lx\n", jobno = 1);
19818334Speter	fclose(fid);
19918334Speter	return 1;
20018334Speter    }
20118334Speter    return EOF;
20218334Speter}
20318334Speter
20418334Speterstatic void
20518334Speterwritefile(time_t runtimer, char queue)
20618334Speter{
20718334Speter/* This does most of the work if at or batch are invoked for writing a job.
20818334Speter */
20918334Speter    long jobno;
21018334Speter    char *ap, *ppos, *mailname;
21118334Speter    struct passwd *pass_entry;
21218334Speter    struct stat statbuf;
21318334Speter    int fdes, lockdes, fd2;
21418334Speter    FILE *fp, *fpin;
21518334Speter    struct sigaction act;
21618334Speter    char **atenv;
21718334Speter    int ch;
21818334Speter    mode_t cmask;
21918334Speter    struct flock lock;
22018334Speter
22118334Speter#ifdef __FreeBSD__
22218334Speter    (void) setlocale(LC_TIME, "");
22318334Speter#endif
22418334Speter
22518334Speter/* Install the signal handler for SIGINT; terminate after removing the
22618334Speter * spool file if necessary
22718334Speter */
22818334Speter    act.sa_handler = sigc;
22918334Speter    sigemptyset(&(act.sa_mask));
23018334Speter    act.sa_flags = 0;
23118334Speter
23218334Speter    sigaction(SIGINT, &act, NULL);
23318334Speter
23418334Speter    ppos = atfile + strlen(ATJOB_DIR);
23518334Speter
23618334Speter    /* Loop over all possible file names for running something at this
23718334Speter     * particular time, see if a file is there; the first empty slot at any
23818334Speter     * particular time is used.  Lock the file LFILE first to make sure
23918334Speter     * we're alone when doing this.
24018334Speter     */
24118334Speter
24218334Speter    PRIV_START
24318334Speter
24418334Speter    if ((lockdes = open(LFILE, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR)) < 0)
24518334Speter	perr("cannot open lockfile " LFILE);
24618334Speter
24718334Speter    lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0;
24818334Speter    lock.l_len = 0;
24918334Speter
25018334Speter    act.sa_handler = alarmc;
25118334Speter    sigemptyset(&(act.sa_mask));
25218334Speter    act.sa_flags = 0;
25318334Speter
25418334Speter    /* Set an alarm so a timeout occurs after ALARMC seconds, in case
25518334Speter     * something is seriously broken.
25618334Speter     */
25718334Speter    sigaction(SIGALRM, &act, NULL);
25818334Speter    alarm(ALARMC);
25918334Speter    fcntl(lockdes, F_SETLKW, &lock);
26018334Speter    alarm(0);
26118334Speter
26218334Speter    if ((jobno = nextjob()) == EOF)
26318334Speter	perr("cannot generate job number");
26418334Speter
26518334Speter    sprintf(ppos, "%c%5lx%8lx", queue,
26618334Speter	    jobno, (unsigned long) (runtimer/60));
26718334Speter
26818334Speter    for(ap=ppos; *ap != '\0'; ap ++)
26918334Speter	if (*ap == ' ')
27018334Speter	    *ap = '0';
27118334Speter
27218334Speter    if (stat(atfile, &statbuf) != 0)
27318334Speter	if (errno != ENOENT)
27418334Speter	    perr("cannot access " ATJOB_DIR);
27518334Speter
27618334Speter    /* Create the file. The x bit is only going to be set after it has
27718334Speter     * been completely written out, to make sure it is not executed in the
27818334Speter     * meantime.  To make sure they do not get deleted, turn off their r
27918334Speter     * bit.  Yes, this is a kluge.
28018334Speter     */
28118334Speter    cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR);
28218334Speter    if ((fdes = creat(atfile, O_WRONLY)) == -1)
28318334Speter	perr("cannot create atjob file");
28418334Speter
28518334Speter    if ((fd2 = dup(fdes)) <0)
28618334Speter	perr("error in dup() of job file");
28718334Speter
28818334Speter    if(fchown(fd2, real_uid, real_gid) != 0)
28918334Speter	perr("cannot give away file");
29018334Speter
29118334Speter    PRIV_END
29218334Speter
29318334Speter    /* We no longer need suid root; now we just need to be able to write
29418334Speter     * to the directory, if necessary.
29518334Speter     */
29618334Speter
29718334Speter    REDUCE_PRIV(DAEMON_UID, DAEMON_GID)
29818334Speter
29918334Speter    /* We've successfully created the file; let's set the flag so it
30018334Speter     * gets removed in case of an interrupt or error.
30118334Speter     */
30218334Speter    fcreated = 1;
30318334Speter
30418334Speter    /* Now we can release the lock, so other people can access it
30518334Speter     */
30618334Speter    lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = 0;
30718334Speter    lock.l_len = 0;
30818334Speter    fcntl(lockdes, F_SETLKW, &lock);
30918334Speter    close(lockdes);
31018334Speter
31118334Speter    if((fp = fdopen(fdes, "w")) == NULL)
31218334Speter	panic("cannot reopen atjob file");
31318334Speter
31418334Speter    /* Get the userid to mail to, first by trying getlogin(),
31518334Speter     * then from LOGNAME, finally from getpwuid().
31618334Speter     */
31718334Speter    mailname = getlogin();
31818334Speter    if (mailname == NULL)
31918334Speter	mailname = getenv("LOGNAME");
32018334Speter
32118334Speter    if ((mailname == NULL) || (mailname[0] == '\0')
32218334Speter	|| (strlen(mailname) >= MAXLOGNAME) || (getpwnam(mailname)==NULL))
32318334Speter    {
32418334Speter	pass_entry = getpwuid(real_uid);
32518334Speter	if (pass_entry != NULL)
32618334Speter	    mailname = pass_entry->pw_name;
32718334Speter    }
32818334Speter
32918334Speter    if (atinput != (char *) NULL)
33018334Speter    {
33118334Speter	fpin = freopen(atinput, "r", stdin);
33218334Speter	if (fpin == NULL)
33318334Speter	    perr("cannot open input file");
33418334Speter    }
33518334Speter    fprintf(fp, "#!/bin/sh\n# atrun uid=%ld gid=%ld\n# mail %*s %d\n",
33618334Speter	(long) real_uid, (long) real_gid, MAXLOGNAME - 1, mailname,
33718334Speter	send_mail);
33818334Speter
33918334Speter    /* Write out the umask at the time of invocation
34018334Speter     */
34118334Speter    fprintf(fp, "umask %lo\n", (unsigned long) cmask);
34218334Speter
34318334Speter    /* Write out the environment. Anything that may look like a
34418334Speter     * special character to the shell is quoted, except for \n, which is
34518334Speter     * done with a pair of "'s.  Don't export the no_export list (such
34618334Speter     * as TERM or DISPLAY) because we don't want these.
34718334Speter     */
34818334Speter    for (atenv= environ; *atenv != NULL; atenv++)
34918334Speter    {
35018334Speter	int export = 1;
35118334Speter	char *eqp;
35218334Speter
35318334Speter	eqp = strchr(*atenv, '=');
35418334Speter	if (ap == NULL)
35518334Speter	    eqp = *atenv;
35618334Speter	else
35718334Speter	{
35818334Speter	    size_t i;
35918334Speter	    for (i=0; i<sizeof(no_export)/sizeof(no_export[0]); i++)
36018334Speter	    {
36118334Speter		export = export
36218334Speter		    && (strncmp(*atenv, no_export[i],
36318334Speter				(size_t) (eqp-*atenv)) != 0);
36418334Speter	    }
36518334Speter	    eqp++;
36618334Speter	}
36718334Speter
36818334Speter	if (export)
36918334Speter	{
37018334Speter	    (void)fputs("export ", fp);
37118334Speter	    fwrite(*atenv, sizeof(char), eqp-*atenv, fp);
37218334Speter	    for(ap = eqp;*ap != '\0'; ap++)
37318334Speter	    {
37418334Speter		if (*ap == '\n')
37518334Speter		    fprintf(fp, "\"\n\"");
37618334Speter		else
37718334Speter		{
37818334Speter		    if (!isalnum(*ap)) {
37918334Speter			switch (*ap) {
38018334Speter			  case '%': case '/': case '{': case '[':
38118334Speter			  case ']': case '=': case '}': case '@':
38218334Speter			  case '+': case '#': case ',': case '.':
38318334Speter			  case ':': case '-': case '_':
38418334Speter			    break;
38518334Speter			  default:
38618334Speter			    fputc('\\', fp);
38718334Speter			    break;
38818334Speter			}
38918334Speter		    }
39018334Speter		    fputc(*ap, fp);
39118334Speter		}
39218334Speter	    }
39318334Speter	    fputc('\n', fp);
39418334Speter
39518334Speter	}
39618334Speter    }
39718334Speter    /* Cd to the directory at the time and write out all the
39818334Speter     * commands the user supplies from stdin.
39918334Speter     */
40018334Speter    fprintf(fp, "cd ");
40118334Speter    for (ap = cwdname(); *ap != '\0'; ap++)
40218334Speter    {
40318334Speter	if (*ap == '\n')
40418334Speter	    fprintf(fp, "\"\n\"");
40518334Speter	else
40618334Speter	{
40718334Speter	    if (*ap != '/' && !isalnum(*ap))
40818334Speter		fputc('\\', fp);
40918334Speter
41018334Speter	    fputc(*ap, fp);
41118334Speter	}
41218334Speter    }
41318334Speter    /* Test cd's exit status: die if the original directory has been
41418334Speter     * removed, become unreadable or whatever
41518334Speter     */
41618334Speter    fprintf(fp, " || {\n\t echo 'Execution directory "
41718334Speter	        "inaccessible' >&2\n\t exit 1\n}\n");
41818334Speter
41918334Speter    while((ch = getchar()) != EOF)
42018334Speter	fputc(ch, fp);
42118334Speter
42218334Speter    fprintf(fp, "\n");
42318334Speter    if (ferror(fp))
42418334Speter	panic("output error");
42518334Speter
42618334Speter    if (ferror(stdin))
42718334Speter	panic("input error");
42818334Speter
42918334Speter    fclose(fp);
43018334Speter
43118334Speter    /* Set the x bit so that we're ready to start executing
43218334Speter     */
43318334Speter
43418334Speter    if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
43518334Speter	perr("cannot give away file");
43618334Speter
43718334Speter    close(fd2);
43818334Speter    fprintf(stderr, "Job %ld will be executed using /bin/sh\n", jobno);
43918334Speter}
44018334Speter
44118334Speterstatic int
44218334Speterin_job_list(long job, long *joblist, int len)
44318334Speter{
44418334Speter    int i;
44518334Speter
44618334Speter    for (i = 0; i < len; i++)
44718334Speter	if (job == joblist[i])
44818334Speter	    return 1;
44918334Speter
45018334Speter    return 0;
45118334Speter}
45218334Speter
45318334Speterstatic void
45418334Speterlist_jobs(long *joblist, int len)
45518334Speter{
45618334Speter    /* List all a user's jobs in the queue, by looping through ATJOB_DIR,
45718334Speter     * or everybody's if we are root
45818334Speter     */
45918334Speter    struct passwd *pw;
46018334Speter    DIR *spool;
46118334Speter    struct dirent *dirent;
46218334Speter    struct stat buf;
46318334Speter    struct tm runtime;
46418334Speter    unsigned long ctm;
46518334Speter    char queue;
46618334Speter    long jobno;
46718334Speter    time_t runtimer;
46818334Speter    char timestr[TIMESIZE];
46918334Speter    int first=1;
47018334Speter
47118334Speter#ifdef __FreeBSD__
47218334Speter    (void) setlocale(LC_TIME, "");
47318334Speter#endif
47418334Speter
47518334Speter    PRIV_START
47618334Speter
47718334Speter    if (chdir(ATJOB_DIR) != 0)
47818334Speter	perr("cannot change to " ATJOB_DIR);
47918334Speter
48018334Speter    if ((spool = opendir(".")) == NULL)
48118334Speter	perr("cannot open " ATJOB_DIR);
48218334Speter
48318334Speter    /*	Loop over every file in the directory
48418334Speter     */
48518334Speter    while((dirent = readdir(spool)) != NULL) {
48618334Speter	if (stat(dirent->d_name, &buf) != 0)
48718334Speter	    perr("cannot stat in " ATJOB_DIR);
48818334Speter
48918334Speter	/* See it's a regular file and has its x bit turned on and
49018334Speter         * is the user's
49118334Speter         */
49218334Speter	if (!S_ISREG(buf.st_mode)
49318334Speter	    || ((buf.st_uid != real_uid) && ! (real_uid == 0))
49418334Speter	    || !(S_IXUSR & buf.st_mode || atverify))
49518334Speter	    continue;
49618334Speter
49718334Speter	if(sscanf(dirent->d_name, "%c%5lx%8lx", &queue, &jobno, &ctm)!=3)
49818334Speter	    continue;
49918334Speter
50018334Speter	/* If jobs are given, only list those jobs */
50118334Speter	if (joblist && !in_job_list(jobno, joblist, len))
50218334Speter	    continue;
50318334Speter
50418334Speter	if (atqueue && (queue != atqueue))
50518334Speter	    continue;
50618334Speter
50718334Speter	runtimer = 60*(time_t) ctm;
50818334Speter	runtime = *localtime(&runtimer);
50918334Speter	strftime(timestr, TIMESIZE, "%+", &runtime);
51018334Speter	if (first) {
51118334Speter	    printf("Date\t\t\t\tOwner\t\tQueue\tJob#\n");
51218334Speter	    first=0;
51318334Speter	}
51418334Speter	pw = getpwuid(buf.st_uid);
51518334Speter
51618334Speter	printf("%s\t%-16s%c%s\t%ld\n",
51718334Speter	       timestr,
51818334Speter	       pw ? pw->pw_name : "???",
51918334Speter	       queue,
52018334Speter	       (S_IXUSR & buf.st_mode) ? "":"(done)",
52118334Speter	       jobno);
52218334Speter    }
52318334Speter    PRIV_END
52418334Speter    closedir(spool);
52518334Speter}
52618334Speter
52718334Speterstatic void
52818334Speterprocess_jobs(int argc, char **argv, int what)
52918334Speter{
53018334Speter    /* Delete every argument (job - ID) given
53118334Speter     */
53218334Speter    int i;
53318334Speter    int rc;
53418334Speter    int nofJobs;
53518334Speter    int nofDone;
53618334Speter    int statErrno;
53718334Speter    struct stat buf;
53818334Speter    DIR *spool;
53918334Speter    struct dirent *dirent;
54018334Speter    unsigned long ctm;
54118334Speter    char queue;
54218334Speter    long jobno;
54318334Speter
54418334Speter    nofJobs = argc - optind;
54518334Speter    nofDone = 0;
54618334Speter
54718334Speter    PRIV_START
54818334Speter
54918334Speter    if (chdir(ATJOB_DIR) != 0)
55018334Speter	perr("cannot change to " ATJOB_DIR);
55118334Speter
55218334Speter    if ((spool = opendir(".")) == NULL)
55318334Speter	perr("cannot open " ATJOB_DIR);
55418334Speter
55518334Speter    PRIV_END
55618334Speter
55718334Speter    /*	Loop over every file in the directory
55818334Speter     */
55918334Speter    while((dirent = readdir(spool)) != NULL) {
56018334Speter
56118334Speter	PRIV_START
56218334Speter	rc = stat(dirent->d_name, &buf);
56318334Speter	statErrno = errno;
56418334Speter	PRIV_END
56518334Speter	/* There's a race condition between readdir above and stat here:
56618334Speter	 * another atrm process could have removed the file from the spool
56718334Speter	 * directory under our nose. If this happens, stat will set errno to
56818334Speter	 * ENOENT, which we shouldn't treat as fatal.
56918334Speter	 */
57018334Speter	if (rc != 0) {
57118334Speter	    if (statErrno == ENOENT)
57218334Speter		continue;
57318334Speter	    else
57418334Speter		perr("cannot stat in " ATJOB_DIR);
57518334Speter	}
57618334Speter
57718334Speter	if(sscanf(dirent->d_name, "%c%5lx%8lx", &queue, &jobno, &ctm)!=3)
57818334Speter	    continue;
57918334Speter
58018334Speter	for (i=optind; i < argc; i++) {
58118334Speter	    if (atoi(argv[i]) == jobno) {
58218334Speter		if ((buf.st_uid != real_uid) && !(real_uid == 0))
58318334Speter		    errx(EXIT_FAILURE, "%s: not owner", argv[i]);
58418334Speter		switch (what) {
58518334Speter		  case ATRM:
58618334Speter
58718334Speter		    PRIV_START
58818334Speter
58918334Speter		    if (unlink(dirent->d_name) != 0)
59018334Speter		        perr(dirent->d_name);
59118334Speter
59218334Speter		    PRIV_END
59318334Speter
59418334Speter		    break;
59518334Speter
59618334Speter		  case CAT:
59718334Speter		    {
59818334Speter			FILE *fp;
59918334Speter			int ch;
60018334Speter
60118334Speter			PRIV_START
60218334Speter
60318334Speter			fp = fopen(dirent->d_name,"r");
60418334Speter
60518334Speter			PRIV_END
60618334Speter
60718334Speter			if (!fp) {
60818334Speter			    perr("cannot open file");
60918334Speter			}
61018334Speter			while((ch = getc(fp)) != EOF) {
61118334Speter			    putchar(ch);
61218334Speter			}
61318334Speter			fclose(fp);
61418334Speter		    }
61518334Speter		    break;
61618334Speter
61718334Speter		  default:
61818334Speter		    errx(EXIT_FAILURE, "internal error, process_jobs = %d",
61918334Speter			what);
62018334Speter	        }
62118334Speter
62218334Speter		/* All arguments have been processed
62318334Speter		 */
62418334Speter		if (++nofDone == nofJobs)
62518334Speter		    goto end;
62618334Speter	    }
62718334Speter	}
62818334Speter    }
62918334Speterend:
63018334Speter    closedir(spool);
63118334Speter} /* delete_jobs */
63218334Speter
63318334Speter#define	ATOI2(ar)	((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
63418334Speter
63518334Speterstatic time_t
63618334Speterttime(const char *arg)
63718334Speter{
63818334Speter    /*
63918334Speter     * This is pretty much a copy of stime_arg1() from touch.c.  I changed
64018334Speter     * the return value and the argument list because it's more convenient
64118334Speter     * (IMO) to do everything in one place. - Joe Halpin
64218334Speter     */
64318334Speter    struct timeval tv[2];
64418334Speter    time_t now;
64518334Speter    struct tm *t;
64618334Speter    int yearset;
64718334Speter    char *p;
64818334Speter
64918334Speter    if (gettimeofday(&tv[0], NULL))
65018334Speter	panic("Cannot get current time");
65118334Speter
65218334Speter    /* Start with the current time. */
65318334Speter    now = tv[0].tv_sec;
65418334Speter    if ((t = localtime(&now)) == NULL)
65518334Speter	panic("localtime");
65618334Speter    /* [[CC]YY]MMDDhhmm[.SS] */
65718334Speter    if ((p = strchr(arg, '.')) == NULL)
65818334Speter	t->tm_sec = 0;		/* Seconds defaults to 0. */
65918334Speter    else {
66018334Speter	if (strlen(p + 1) != 2)
66118334Speter	    goto terr;
66218334Speter	*p++ = '\0';
66318334Speter	t->tm_sec = ATOI2(p);
66418334Speter    }
66518334Speter
66618334Speter    yearset = 0;
66718334Speter    switch(strlen(arg)) {
66818334Speter    case 12:			/* CCYYMMDDhhmm */
66918334Speter	t->tm_year = ATOI2(arg);
67018334Speter	t->tm_year *= 100;
67118334Speter	yearset = 1;
67218334Speter	/* FALLTHROUGH */
67318334Speter    case 10:			/* YYMMDDhhmm */
67418334Speter	if (yearset) {
67518334Speter	    yearset = ATOI2(arg);
67618334Speter	    t->tm_year += yearset;
67718334Speter	} else {
67818334Speter	    yearset = ATOI2(arg);
67918334Speter	    t->tm_year = yearset + 2000;
68018334Speter	}
68118334Speter	t->tm_year -= 1900;	/* Convert to UNIX time. */
68218334Speter	/* FALLTHROUGH */
68318334Speter    case 8:				/* MMDDhhmm */
68418334Speter	t->tm_mon = ATOI2(arg);
68518334Speter	--t->tm_mon;		/* Convert from 01-12 to 00-11 */
68618334Speter	t->tm_mday = ATOI2(arg);
68718334Speter	t->tm_hour = ATOI2(arg);
68818334Speter	t->tm_min = ATOI2(arg);
68918334Speter	break;
69018334Speter    default:
69118334Speter	goto terr;
69218334Speter    }
69318334Speter
69418334Speter    t->tm_isdst = -1;		/* Figure out DST. */
69518334Speter    tv[0].tv_sec = tv[1].tv_sec = mktime(t);
69618334Speter    if (tv[0].tv_sec != -1)
69718334Speter	return tv[0].tv_sec;
69818334Speter    else
69918334Speterterr:
70018334Speter	panic(
70118334Speter	   "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
70218334Speter}
70318334Speter
70418334Speterstatic long *
70518334Speterget_job_list(int argc, char *argv[], int *joblen)
70618334Speter{
70718334Speter    int i, len;
70818334Speter    long *joblist;
70918334Speter    char *ep;
71018334Speter
71118334Speter    joblist = NULL;
71218334Speter    len = argc;
71318334Speter    if (len > 0) {
71418334Speter	if ((joblist = malloc(len * sizeof(*joblist))) == NULL)
71518334Speter	    panic("out of memory");
71618334Speter
71718334Speter	for (i = 0; i < argc; i++) {
71818334Speter	    errno = 0;
71918334Speter	    if ((joblist[i] = strtol(argv[i], &ep, 10)) < 0 ||
72018334Speter		ep == argv[i] || *ep != '\0' || errno)
72118334Speter		panic("invalid job number");
72218334Speter	}
72318334Speter    }
72418334Speter
72518334Speter    *joblen = len;
72618334Speter    return joblist;
72718334Speter}
72818334Speter
72918334Speterint
73018334Spetermain(int argc, char **argv)
73118334Speter{
73218334Speter    int c;
73318334Speter    char queue = DEFAULT_AT_QUEUE;
73418334Speter    char queue_set = 0;
73518334Speter    char *pgm;
73618334Speter
73718334Speter    int program = AT;			/* our default program */
73818334Speter    const char *options = "q:f:t:rmvldbc"; /* default options for at */
73918334Speter    time_t timer;
74018334Speter    long *joblist;
74118334Speter    int joblen;
74218334Speter
74318334Speter    joblist = NULL;
74418334Speter    joblen = 0;
74518334Speter    timer = -1;
74618334Speter    RELINQUISH_PRIVS
74718334Speter
74818334Speter    /* Eat any leading paths
74918334Speter     */
75018334Speter    if ((pgm = strrchr(argv[0], '/')) == NULL)
75118334Speter	pgm = argv[0];
75218334Speter    else
75318334Speter        pgm++;
75418334Speter
75518334Speter    namep = pgm;
75618334Speter
75718334Speter    /* find out what this program is supposed to do
75818334Speter     */
75918334Speter    if (strcmp(pgm, "atq") == 0) {
76018334Speter	program = ATQ;
76118334Speter	options = "q:v";
76218334Speter    }
76318334Speter    else if (strcmp(pgm, "atrm") == 0) {
76418334Speter	program = ATRM;
76518334Speter	options = "";
76618334Speter    }
76718334Speter    else if (strcmp(pgm, "batch") == 0) {
76818334Speter	program = BATCH;
76918334Speter	options = "f:q:mv";
77018334Speter    }
77118334Speter
77218334Speter    /* process whatever options we can process
77318334Speter     */
77418334Speter    opterr=1;
77518334Speter    while ((c=getopt(argc, argv, options)) != -1)
77618334Speter	switch (c) {
77718334Speter	case 'v':   /* verify time settings */
77818334Speter	    atverify = 1;
77918334Speter	    break;
78018334Speter
78118334Speter	case 'm':   /* send mail when job is complete */
78218334Speter	    send_mail = 1;
78318334Speter	    break;
78418334Speter
78518334Speter	case 'f':
78618334Speter	    atinput = optarg;
78718334Speter	    break;
78818334Speter
78918334Speter	case 'q':    /* specify queue */
79018334Speter	    if (strlen(optarg) > 1)
79118334Speter		usage();
79218334Speter
79318334Speter	    atqueue = queue = *optarg;
79418334Speter	    if (!(islower(queue)||isupper(queue)))
79518334Speter		usage();
79618334Speter
79718334Speter	    queue_set = 1;
79818334Speter	    break;
79918334Speter
80018334Speter	case 'd':
80118334Speter	    warnx("-d is deprecated; use -r instead");
80218334Speter	    /* fall through to 'r' */
80318334Speter
80418334Speter	case 'r':
80518334Speter	    if (program != AT)
80618334Speter		usage();
80718334Speter
80818334Speter	    program = ATRM;
80918334Speter	    options = "";
81018334Speter	    break;
81118334Speter
81218334Speter	case 't':
81318334Speter	    if (program != AT)
81418334Speter		usage();
81518334Speter	    timer = ttime(optarg);
81618334Speter	    break;
81718334Speter
81818334Speter	case 'l':
81918334Speter	    if (program != AT)
82018334Speter		usage();
82118334Speter
82218334Speter	    program = ATQ;
82318334Speter	    options = "q:";
82418334Speter	    break;
82518334Speter
82618334Speter	case 'b':
82718334Speter	    if (program != AT)
82818334Speter		usage();
82918334Speter
83018334Speter	    program = BATCH;
83118334Speter	    options = "f:q:mv";
83218334Speter	    break;
83318334Speter
83418334Speter	case 'c':
83518334Speter	    program = CAT;
83618334Speter	    options = "";
83718334Speter	    break;
83818334Speter
83918334Speter	default:
84018334Speter	    usage();
84118334Speter	    break;
84218334Speter	}
84318334Speter    /* end of options eating
84418334Speter     */
84518334Speter
84618334Speter    /* select our program
84718334Speter     */
84818334Speter    if(!check_permission())
84918334Speter	errx(EXIT_FAILURE, "you do not have permission to use this program");
85018334Speter    switch (program) {
85118334Speter    case ATQ:
85218334Speter
85318334Speter	REDUCE_PRIV(DAEMON_UID, DAEMON_GID)
85418334Speter
85518334Speter	if (queue_set == 0)
85618334Speter	    joblist = get_job_list(argc - optind, argv + optind, &joblen);
85718334Speter	list_jobs(joblist, joblen);
85818334Speter	break;
85918334Speter
86018334Speter    case ATRM:
86118334Speter
86218334Speter	REDUCE_PRIV(DAEMON_UID, DAEMON_GID)
86318334Speter
86418334Speter	process_jobs(argc, argv, ATRM);
86518334Speter	break;
86618334Speter
86718334Speter    case CAT:
86818334Speter
86918334Speter	process_jobs(argc, argv, CAT);
87018334Speter	break;
87118334Speter
87218334Speter    case AT:
87318334Speter	/*
87418334Speter	 * If timer is > -1, then the user gave the time with -t.  In that
87518334Speter	 * case, it's already been set. If not, set it now.
87618334Speter	 */
87718334Speter	if (timer == -1)
87818334Speter	    timer = parsetime(argc, argv);
87918334Speter
88018334Speter	if (atverify)
88118334Speter	{
88218334Speter	    struct tm *tm = localtime(&timer);
88318334Speter	    fprintf(stderr, "%s\n", asctime(tm));
88418334Speter	}
88518334Speter	writefile(timer, queue);
88618334Speter	break;
88718334Speter
88818334Speter    case BATCH:
88918334Speter	if (queue_set)
89018334Speter	    queue = toupper(queue);
89118334Speter	else
89218334Speter	    queue = DEFAULT_BATCH_QUEUE;
89318334Speter
89418334Speter	if (argc > optind)
89518334Speter	    timer = parsetime(argc, argv);
89618334Speter	else
89718334Speter	    timer = time(NULL);
89818334Speter
89918334Speter	if (atverify)
90018334Speter	{
90118334Speter	    struct tm *tm = localtime(&timer);
90218334Speter	    fprintf(stderr, "%s\n", asctime(tm));
90318334Speter	}
90418334Speter
90518334Speter        writefile(timer, queue);
90618334Speter	break;
90718334Speter
90818334Speter    default:
90918334Speter	panic("internal error");
91018334Speter	break;
91118334Speter    }
91218334Speter    exit(EXIT_SUCCESS);
91318334Speter}
91418334Speter