From 0e393d5044d1e68e17d91385b70d5cd6b27f8438 Mon Sep 17 00:00:00 2001 From: Apple Date: Wed, 19 Jan 2005 01:05:26 +0000 Subject: [PATCH] system_cmds-279.6.1.tar.gz --- at.tproj/Makefile | 6 +- at.tproj/at.1 | 3 +- at.tproj/at.c | 1230 ++++++++++++++++++++------------ at.tproj/at.h | 36 +- at.tproj/panic.c | 95 +-- at.tproj/panic.h | 43 +- at.tproj/parsetime.c | 467 ++++++------ at.tproj/parsetime.h | 36 +- at.tproj/perm.c | 59 +- at.tproj/perm.h | 27 +- at.tproj/privs.h | 85 ++- atrun.tproj/Makefile | 6 +- atrun.tproj/Makefile.postamble | 3 + atrun.tproj/atrun.c | 661 ++++++++++------- 14 files changed, 1579 insertions(+), 1178 deletions(-) create mode 100644 atrun.tproj/Makefile.postamble diff --git a/at.tproj/Makefile b/at.tproj/Makefile index c2335bf..2c36024 100644 --- a/at.tproj/Makefile +++ b/at.tproj/Makefile @@ -33,9 +33,11 @@ PROF_LIBS = $(LIBS) PROJECT_HEADERS = privs.h pathnames.h +NEXTSTEP_PB_CFLAGS += -D__FBSDID=__RCSID -DDAEMON_UID=1 -DDAEMON_GID=1 \ + -DDEFAULT_AT_QUEUE=\'a\' -DDEFAULT_BATCH_QUEUE=\'b\' \ + -DPERM_PATH=\"/var/at/\" -I/System/Library/Frameworks/System.framework/PrivateHeaders - -NEXTSTEP_BUILD_OUTPUT_DIR = /$(USER)/BUILD +NEXTSTEP_BUILD_OUTPUT_DIR = /tmp/$(NAME)/Build NEXTSTEP_OBJCPLUS_COMPILER = /usr/bin/cc WINDOWS_OBJCPLUS_COMPILER = $(DEVDIR)/gcc diff --git a/at.tproj/at.1 b/at.tproj/at.1 index 47c2591..381b7ee 100644 --- a/at.tproj/at.1 +++ b/at.tproj/at.1 @@ -27,7 +27,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $Id: at.1,v 1.3 2003/06/05 17:13:32 eseidel Exp $ +.\" $Id: at.1,v 1.3.50.2 2005/01/18 22:17:28 asigel Exp $ .\" .Dd December 5, 1993 .Dt "AT" 1 @@ -210,6 +210,7 @@ Job-creation lock file. .Xr crond 8 , .Xr nice 1 , .Xr sh 1 , +.Xr compat 5 , .Xr atrun 8 .Sh AUTHOR .Bl -tag diff --git a/at.tproj/at.c b/at.tproj/at.c index 0caf2d1..ed30706 100644 --- a/at.tproj/at.c +++ b/at.tproj/at.c @@ -1,33 +1,9 @@ -/* - * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. +/* + * at.c : Put file into atrun queue + * Copyright (C) 1993, 1994 Thomas Koenig * - * @APPLE_LICENSE_HEADER_START@ - * - * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." - * - * @APPLE_LICENSE_HEADER_END@ - */ -/* - * at.c : Put file into atrun queue - * Copyright (C) 1993 Thomas Koenig - * - * Atrun & Atq modifications - * Copyright (C) 1993 David Parsons - * All rights reserved. + * Atrun & Atq modifications + * Copyright (C) 1993 David Parsons * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -41,7 +17,7 @@ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * IN NO EVENT SHALL THE AUTHOR(S) 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 @@ -50,16 +26,29 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +__FBSDID("$FreeBSD: src/usr.bin/at/at.c,v 1.29 2002/07/22 11:32:16 robert Exp $"); + #define _USE_BSD 1 /* System Headers */ -#include + +#include #include +#include #include #include #include +#include #include #include +#ifndef __FreeBSD__ +#include +#endif +#include +#ifdef __FreeBSD__ +#include +#endif #include #include #include @@ -69,523 +58,876 @@ #include #include +#define COMPAT_MODE(a,b) (0) + /* Local headers */ + #include "at.h" #include "panic.h" #include "parsetime.h" #include "pathnames.h" +#include "perm.h" + #define MAIN #include "privs.h" -#include "perm.h" /* Macros */ -#define ALARMC 10 /* Number of seconds to wait for timeout */ + +#ifndef ATJOB_DIR +#define ATJOB_DIR _PATH_ATJOBS +#endif + +#ifndef LFILE +#define LFILE ATJOB_DIR ".lockfile" +#endif + +#ifndef ATJOB_MX +#define ATJOB_MX 255 +#endif + +#define ALARMC 10 /* Number of seconds to wait for timeout */ #define SIZE 255 #define TIMESIZE 50 +enum { ATQ, ATRM, AT, BATCH, CAT }; /* what program we want to run */ + /* File scope variables */ -static char rcsid[] = "$Id: at.c,v 1.1.1.2 2000/01/11 02:10:04 wsanchez Exp $"; -char *no_export[] = + +const char *no_export[] = { - "TERM", "TERMCAP", "DISPLAY", "_" -}; -static send_mail = 0; + "TERM", "TERMCAP", "DISPLAY", "_" +} ; +static int send_mail = 0; /* External variables */ + extern char **environ; int fcreated; -char *namep; -char atfile[FILENAME_MAX]; +char atfile[] = ATJOB_DIR "12345678901234"; -char *atinput = (char *) 0; /* where to get input from */ +char *atinput = (char*)0; /* where to get input from */ char atqueue = 0; /* which queue to examine for jobs (atq) */ char atverify = 0; /* verify time instead of queuing job */ +char *namep; +int posixly_correct; /* Behave as per POSIX */ +/* http://www.opengroup.org/onlinepubs/009695399/utilities/at.html */ /* Function declarations */ -static void sigc __P((int signo)); -static void alarmc __P((int signo)); -static char *cwdname __P((void)); -static void writefile __P((time_t runtimer, char queue)); -static void list_jobs __P((void)); + +static void sigc(int signo); +static void alarmc(int signo); +static char *cwdname(void); +static void writefile(time_t runtimer, char queue); +static void list_jobs(long *, int); +static long nextjob(void); +static time_t ttime(const char *arg); +static int in_job_list(long, long *, int); +static long *get_job_list(int, char *[], int *); /* Signal catching functions */ -static void -sigc(signo) - int signo; +static void sigc(int signo __unused) { -/* If the user presses ^C, remove the spool file and exit +/* If the user presses ^C, remove the spool file and exit */ - if (fcreated) { - PRIV_START - unlink(atfile); - PRIV_END - } + if (fcreated) + { + PRIV_START + unlink(atfile); + PRIV_END + } - exit(EXIT_FAILURE); + _exit(EXIT_FAILURE); } -static void -alarmc(signo) - int signo; +static void alarmc(int signo __unused) { -/* Time out after some seconds - */ - panic("File locking timed out"); + char buf[1024]; + + /* Time out after some seconds. */ + strlcpy(buf, namep, sizeof(buf)); + strlcat(buf, ": file locking timed out\n", sizeof(buf)); + write(STDERR_FILENO, buf, strlen(buf)); + sigc(0); } /* Local functions */ -static char * -cwdname() +static char *cwdname(void) { /* Read in the current directory; the name will be overwritten on * subsequent calls. */ - static char *ptr = NULL; - static size_t size = SIZE; - - if (ptr == NULL) - ptr = (char *) malloc(size); - - while (1) { - if (ptr == NULL) - panic("Out of memory"); + static char *ptr = NULL; + static size_t size = SIZE; - if (getcwd(ptr, size - 1) != NULL) - return ptr; + if (ptr == NULL) + if ((ptr = malloc(size)) == NULL) + errx(EXIT_FAILURE, "virtual memory exhausted"); - if (errno != ERANGE) - perr("Cannot get directory"); + while (1) + { + if (ptr == NULL) + panic("out of memory"); + + if (getcwd(ptr, size-1) != NULL) + return ptr; + + if (errno != ERANGE) + perr("cannot get directory"); + + free (ptr); + size += SIZE; + if ((ptr = malloc(size)) == NULL) + errx(EXIT_FAILURE, "virtual memory exhausted"); + } +} - free(ptr); - size += SIZE; - ptr = (char *) malloc(size); +static long +nextjob() +{ + long jobno; + FILE *fid; + + if ((fid = fopen(ATJOB_DIR ".SEQ", "r+")) != (FILE*)0) { + if (fscanf(fid, "%5lx", &jobno) == 1) { + rewind(fid); + jobno = (1+jobno) % 0xfffff; /* 2^20 jobs enough? */ + fprintf(fid, "%05lx\n", jobno); } + else + jobno = EOF; + fclose(fid); + return jobno; + } + else if ((fid = fopen(ATJOB_DIR ".SEQ", "w")) != (FILE*)0) { + fprintf(fid, "%05lx\n", jobno = 1); + fclose(fid); + return 1; + } + return EOF; } static void -writefile(runtimer, queue) - time_t runtimer; - char queue; +writefile(time_t runtimer, char queue) { - /* - * This does most of the work if at or batch are invoked for - * writing a job. - */ - int i; - char *ap, *ppos, *mailname; - struct passwd *pass_entry; - struct stat statbuf; - int fdes, lockdes, fd2; - FILE *fp, *fpin; - struct sigaction act; - char **atenv; - int ch; - mode_t cmask; - struct flock lock; - - /* - * Install the signal handler for SIGINT; terminate after removing the - * spool file if necessary - */ - act.sa_handler = sigc; - sigemptyset(&(act.sa_mask)); - act.sa_flags = 0; +/* This does most of the work if at or batch are invoked for writing a job. + */ + long jobno; + char *ap, *ppos, *mailname; + struct passwd *pass_entry; + struct stat statbuf; + int fdes, lockdes, fd2; + FILE *fp, *fpin; + struct sigaction act; + char **atenv; + int ch; + mode_t cmask; + struct flock lock; + +#ifdef __FreeBSD__ + (void) setlocale(LC_TIME, ""); +#endif + +/* Install the signal handler for SIGINT; terminate after removing the + * spool file if necessary + */ + act.sa_handler = sigc; + sigemptyset(&(act.sa_mask)); + act.sa_flags = 0; + + sigaction(SIGINT, &act, NULL); + + ppos = atfile + strlen(ATJOB_DIR); + + /* Loop over all possible file names for running something at this + * particular time, see if a file is there; the first empty slot at any + * particular time is used. Lock the file LFILE first to make sure + * we're alone when doing this. + */ + + PRIV_START + + if ((lockdes = open(LFILE, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR)) < 0) + perr("cannot open lockfile " LFILE); + + lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; + lock.l_len = 0; + + act.sa_handler = alarmc; + sigemptyset(&(act.sa_mask)); + act.sa_flags = 0; + + /* Set an alarm so a timeout occurs after ALARMC seconds, in case + * something is seriously broken. + */ + sigaction(SIGALRM, &act, NULL); + alarm(ALARMC); + fcntl(lockdes, F_SETLKW, &lock); + alarm(0); + + if ((jobno = nextjob()) == EOF) + perr("cannot generate job number"); + + sprintf(ppos, "%c%5lx%8lx", queue, + jobno, (unsigned long) (runtimer/60)); + + for(ap=ppos; *ap != '\0'; ap ++) + if (*ap == ' ') + *ap = '0'; + + if (stat(atfile, &statbuf) != 0) + if (errno != ENOENT) + perr("cannot access " ATJOB_DIR); + + /* Create the file. The x bit is only going to be set after it has + * been completely written out, to make sure it is not executed in the + * meantime. To make sure they do not get deleted, turn off their r + * bit. Yes, this is a kluge. + */ + cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR); + if ((fdes = creat(atfile, O_WRONLY)) == -1) + perr("cannot create atjob file"); + + if ((fd2 = dup(fdes)) <0) + perr("error in dup() of job file"); + + if(fchown(fd2, real_uid, real_gid) != 0) + perr("cannot give away file"); + + PRIV_END + + /* We no longer need suid root; now we just need to be able to write + * to the directory, if necessary. + */ + + REDUCE_PRIV(DAEMON_UID, DAEMON_GID) + + /* We've successfully created the file; let's set the flag so it + * gets removed in case of an interrupt or error. + */ + fcreated = 1; + + /* Now we can release the lock, so other people can access it + */ + lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; + lock.l_len = 0; + fcntl(lockdes, F_SETLKW, &lock); + close(lockdes); + + if((fp = fdopen(fdes, "w")) == NULL) + panic("cannot reopen atjob file"); + + /* Get the userid to mail to, first by trying getlogin(), + * then from LOGNAME, finally from getpwuid(). + */ + mailname = getlogin(); + if (mailname == NULL) + mailname = getenv("LOGNAME"); + + if ((mailname == NULL) || (mailname[0] == '\0') + || (strlen(mailname) >= MAXLOGNAME) || (getpwnam(mailname)==NULL)) + { + pass_entry = getpwuid(real_uid); + if (pass_entry != NULL) + mailname = pass_entry->pw_name; + } + + if (atinput != (char *) NULL) + { + fpin = freopen(atinput, "r", stdin); + if (fpin == NULL) + perr("cannot open input file"); + } + fprintf(fp, "#!/bin/sh\n# atrun uid=%ld gid=%ld\n# mail %.*s %d\n", + (long) real_uid, (long) real_gid, MAXLOGNAME - 1, mailname, + send_mail); + + /* Write out the umask at the time of invocation + */ + fprintf(fp, "umask %lo\n", (unsigned long) cmask); + + /* Write out the environment. Anything that may look like a + * special character to the shell is quoted, except for \n, which is + * done with a pair of "'s. Don't export the no_export list (such + * as TERM or DISPLAY) because we don't want these. + */ + for (atenv= environ; *atenv != NULL; atenv++) + { + int export = 1; + char *eqp; + + eqp = strchr(*atenv, '='); + if (ap == NULL) + eqp = *atenv; + else + { + size_t i; + for (i=0; i&2\n\t exit 1\n}\n"); - strcpy(atfile, _PATH_ATJOBS); - ppos = atfile + strlen(_PATH_ATJOBS); + while((ch = getchar()) != EOF) + fputc(ch, fp); - /* - * Loop over all possible file names for running something at this - * particular time, see if a file is there; the first empty slot at - * any particular time is used. Lock the file _PATH_LOCKFILE first - * to make sure we're alone when doing this. - */ + fprintf(fp, "\n"); + if (ferror(fp)) + panic("output error"); + + if (ferror(stdin)) + panic("input error"); - PRIV_START + fclose(fp); - if ((lockdes = open(_PATH_LOCKFILE, O_WRONLY | O_CREAT, 0600)) < 0) - perr2("Cannot open lockfile ", _PATH_LOCKFILE); + /* Set the x bit so that we're ready to start executing + */ - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; + if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0) + perr("cannot give away file"); - act.sa_handler = alarmc; - sigemptyset(&(act.sa_mask)); - act.sa_flags = 0; + close(fd2); + if (posixly_correct) { + struct tm runtime; + char timestr[TIMESIZE]; + runtime = *localtime(&runtimer); + strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime); + fprintf(stderr, "job %ld at %s\n", jobno, timestr); + } else + fprintf(stderr, "Job %ld will be executed using /bin/sh\n", jobno); +} - /* - * Set an alarm so a timeout occurs after ALARMC seconds, in case - * something is seriously broken. - */ - sigaction(SIGALRM, &act, NULL); - alarm(ALARMC); - fcntl(lockdes, F_SETLKW, &lock); - alarm(0); - - for (i = 0; i < AT_MAXJOBS; i++) { - sprintf(ppos, "%c%8lx.%3x", queue, - (unsigned long) (runtimer / 60), i); - for (ap = ppos; *ap != '\0'; ap++) - if (*ap == ' ') - *ap = '0'; - - if (stat(atfile, &statbuf) != 0) { - if (errno == ENOENT) - break; - else - perr2("Cannot access ", _PATH_ATJOBS); - } - } /* for */ +static int +in_job_list(long job, long *joblist, int len) +{ + int i; - if (i >= AT_MAXJOBS) - panic("Too many jobs already"); + for (i = 0; i < len; i++) + if (job == joblist[i]) + return 1; - /* - * Create the file. The x bit is only going to be set after it has - * been completely written out, to make sure it is not executed in - * the meantime. To make sure they do not get deleted, turn off - * their r bit. Yes, this is a kluge. - */ - cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR); - if ((fdes = creat(atfile, O_WRONLY)) == -1) - perr("Cannot create atjob file"); + return 0; +} - if ((fd2 = dup(fdes)) < 0) - perr("Error in dup() of job file"); +static void +list_one_job(char *name, long *joblist, int len, int *first) +{ + struct stat buf; + struct tm runtime; + unsigned long ctm; + char queue; + long jobno; + time_t runtimer; + char timestr[TIMESIZE]; + + if (stat(name, &buf) != 0) + perr("cannot stat in " ATJOB_DIR); + + /* See it's a regular file and has its x bit turned on and + * is the user's + */ + if (!S_ISREG(buf.st_mode) + || ((buf.st_uid != real_uid) && ! (real_uid == 0)) + || !(S_IXUSR & buf.st_mode || atverify)) + return; + + if(sscanf(name, "%c%5lx%8lx", &queue, &jobno, &ctm)!=3) + return; + + /* If jobs are given, only list those jobs */ + if (joblist && !in_job_list(jobno, joblist, len)) + return; + + if (atqueue && (queue != atqueue)) + return; + + runtimer = 60*(time_t) ctm; + runtime = *localtime(&runtimer); + strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime); + if (*first) { + if (!posixly_correct) + printf("Date\t\t\t\tOwner\t\tQueue\tJob#\n"); + *first=0; + } + if (posixly_correct) + printf("%ld\t%s\n", jobno, timestr); + else { + struct passwd *pw = getpwuid(buf.st_uid); + + printf("%s\t%s\t%c%s\t%s\n", + timestr, + pw ? pw->pw_name : "???", + queue, + (S_IXUSR & buf.st_mode) ? "":"(done)", + name); + } +} - if (fchown(fd2, real_uid, -1) != 0) - perr("Cannot give away file"); +static void +list_jobs(long *joblist, int len) +{ + /* List all a user's jobs in the queue, by looping through ATJOB_DIR, + * or everybody's if we are root + */ + DIR *spool; + struct dirent *dirent; + int first=1; + +#ifdef __FreeBSD__ + (void) setlocale(LC_TIME, ""); +#endif + + PRIV_START + + if (chdir(ATJOB_DIR) != 0) + perr("cannot change to " ATJOB_DIR); + + if (joblist) { /* Force order to match POSIX */ + char jobglob[32]; + glob_t g; + int i; - PRIV_END + sprintf(jobglob, "?%05lx*", joblist[0]); + g.gl_offs = 0; + glob(jobglob, GLOB_DOOFFS, NULL, &g); + for (i = 1; i < len; i++) { + sprintf(jobglob, "?%05lx*", joblist[i]); + glob(jobglob, GLOB_DOOFFS | GLOB_APPEND, NULL, &g); + } + for (i = 0; i < g.gl_pathc; i++) { + list_one_job(g.gl_pathv[i], joblist, len, &first); + } + globfree(&g); + } else { + if ((spool = opendir(".")) == NULL) + perr("cannot open " ATJOB_DIR); - /* - * We no longer need suid root; now we just need to be able to - * write to the directory, if necessary. + /* Loop over every file in the directory */ + while((dirent = readdir(spool)) != NULL) { + list_one_job(dirent->d_name, joblist, len, &first); + } + closedir(spool); + } + PRIV_END +} - REDUCE_PRIV(0); +static void +process_jobs(int argc, char **argv, int what) +{ + /* Delete every argument (job - ID) given + */ + int i; + struct stat buf; + DIR *spool; + struct dirent *dirent; + unsigned long ctm; + char queue; + long jobno; - /* - * We've successfully created the file; let's set the flag so it - * gets removed in case of an interrupt or error. - */ - fcreated = 1; + PRIV_START - /* Now we can release the lock, so other people can access it */ - lock.l_type = F_UNLCK; - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; - fcntl(lockdes, F_SETLKW, &lock); - close(lockdes); + if (chdir(ATJOB_DIR) != 0) + perr("cannot change to " ATJOB_DIR); - if ((fp = fdopen(fdes, "w")) == NULL) - panic("Cannot reopen atjob file"); + if ((spool = opendir(".")) == NULL) + perr("cannot open " ATJOB_DIR); - /* - * Get the userid to mail to, first by trying getlogin(), which - * reads /etc/utmp, then from LOGNAME, finally from getpwuid(). - */ - mailname = getlogin(); - if (mailname == NULL) - mailname = getenv("LOGNAME"); - - if ((mailname == NULL) || (mailname[0] == '\0') - || (strlen(mailname) > 8)) { - pass_entry = getpwuid(getuid()); - if (pass_entry != NULL) - mailname = pass_entry->pw_name; - } + PRIV_END - if (atinput != (char *) NULL) { - fpin = freopen(atinput, "r", stdin); - if (fpin == NULL) - perr("Cannot open input file"); - } - fprintf(fp, "#! /bin/sh\n# mail %8s %d\n", mailname, send_mail); + /* Loop over every file in the directory + */ + while((dirent = readdir(spool)) != NULL) { - /* Write out the umask at the time of invocation */ - fprintf(fp, "umask %lo\n", (unsigned long) cmask); + PRIV_START + if (stat(dirent->d_name, &buf) != 0) + perr("cannot stat in " ATJOB_DIR); + PRIV_END - /* - * Write out the environment. Anything that may look like a special - * character to the shell is quoted, except for \n, which is done - * with a pair of "'s. Dont't export the no_export list (such as - * TERM or DISPLAY) because we don't want these. - */ - for (atenv = environ; *atenv != NULL; atenv++) { - int export = 1; - char *eqp; - - eqp = strchr(*atenv, '='); - if (ap == NULL) - eqp = *atenv; - else { - int i; - - for (i = 0;i < sizeof(no_export) / - sizeof(no_export[0]); i++) { - export = export - && (strncmp(*atenv, no_export[i], - (size_t) (eqp - *atenv)) != 0); - } - eqp++; - } + if(sscanf(dirent->d_name, "%c%5lx%8lx", &queue, &jobno, &ctm)!=3) + continue; - if (export) { - fwrite(*atenv, sizeof(char), eqp - *atenv, fp); - for (ap = eqp; *ap != '\0'; ap++) { - if (*ap == '\n') - fprintf(fp, "\"\n\""); - else { - if (!isalnum(*ap)) - fputc('\\', fp); - - fputc(*ap, fp); - } - } - fputs("; export ", fp); - fwrite(*atenv, sizeof(char), eqp - *atenv - 1, fp); - fputc('\n', fp); + for (i=optind; i < argc; i++) { + if (atoi(argv[i]) == jobno || strcmp(argv[i], dirent->d_name)==0) { + if ((buf.st_uid != real_uid) && !(real_uid == 0)) + errx(EXIT_FAILURE, "%s: not owner", argv[i]); + switch (what) { + case ATRM: - } - } - /* - * Cd to the directory at the time and write out all the commands - * the user supplies from stdin. - */ - fprintf(fp, "cd %s\n", cwdname()); + PRIV_START - while ((ch = getchar()) != EOF) - fputc(ch, fp); + if (unlink(dirent->d_name) != 0) + perr(dirent->d_name); - fprintf(fp, "\n"); - if (ferror(fp)) - panic("Output error"); + PRIV_END - if (ferror(stdin)) - panic("Input error"); + break; - fclose(fp); + case CAT: + { + FILE *fp; + int ch; - /* - * Set the x bit so that we're ready to start executing - */ - if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0) - perr("Cannot give away file"); + PRIV_START - close(fd2); - fprintf(stderr, "Job %s will be executed using /bin/sh\n", ppos); -} + fp = fopen(dirent->d_name,"r"); -static void -list_jobs() -{ - /* - * List all a user's jobs in the queue, by looping through - * _PATH_ATJOBS, or everybody's if we are root - */ - struct passwd *pw; - DIR *spool; - struct dirent *dirent; - struct stat buf; - struct tm runtime; - unsigned long ctm; - char queue; - time_t runtimer; - char timestr[TIMESIZE]; - int first = 1; + PRIV_END - PRIV_START + if (!fp) { + perr("cannot open file"); + } + while((ch = getc(fp)) != EOF) { + putchar(ch); + } + } + break; + + default: + errx(EXIT_FAILURE, "internal error, process_jobs = %d", + what); + } + } + } + } +} /* process_jobs */ - if (chdir(_PATH_ATJOBS) != 0) - perr2("Cannot change to ", _PATH_ATJOBS); +#define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; - if ((spool = opendir(".")) == NULL) - perr2("Cannot open ", _PATH_ATJOBS); - - /* Loop over every file in the directory */ - while ((dirent = readdir(spool)) != NULL) { - if (stat(dirent->d_name, &buf) != 0) - perr2("Cannot stat in ", _PATH_ATJOBS); - - /* - * See it's a regular file and has its x bit turned on and - * is the user's - */ - if (!S_ISREG(buf.st_mode) - || ((buf.st_uid != real_uid) && !(real_uid == 0)) - || !(S_IXUSR & buf.st_mode || atverify)) - continue; - - if (sscanf(dirent->d_name, "%c%8lx", &queue, &ctm) != 2) - continue; - - if (atqueue && (queue != atqueue)) - continue; - - runtimer = 60 * (time_t) ctm; - runtime = *localtime(&runtimer); - strftime(timestr, TIMESIZE, "%X %x", &runtime); - if (first) { - printf("Date\t\t\tOwner\tQueue\tJob#\n"); - first = 0; - } - pw = getpwuid(buf.st_uid); - - printf("%s\t%s\t%c%s\t%s\n", - timestr, - pw ? pw->pw_name : "???", - queue, - (S_IXUSR & buf.st_mode) ? "" : "(done)", - dirent->d_name); +static time_t +ttime(const char *arg) +{ + /* + * This is pretty much a copy of stime_arg1() from touch.c. I changed + * the return value and the argument list because it's more convenient + * (IMO) to do everything in one place. - Joe Halpin + */ + struct timeval tv[2]; + time_t now; + struct tm *t; + int yearset; + char *p; + + if (gettimeofday(&tv[0], NULL)) + panic("Cannot get current time"); + + /* Start with the current time. */ + now = tv[0].tv_sec; + if ((t = localtime(&now)) == NULL) + panic("localtime"); + /* [[CC]YY]MMDDhhmm[.SS] */ + if ((p = strchr(arg, '.')) == NULL) + t->tm_sec = 0; /* Seconds defaults to 0. */ + else { + if (strlen(p + 1) != 2) + goto terr; + *p++ = '\0'; + t->tm_sec = ATOI2(p); + } + + yearset = 0; + switch(strlen(arg)) { + case 12: /* CCYYMMDDhhmm */ + t->tm_year = ATOI2(arg); + t->tm_year *= 100; + yearset = 1; + /* FALLTHROUGH */ + case 10: /* YYMMDDhhmm */ + if (yearset) { + yearset = ATOI2(arg); + t->tm_year += yearset; + } else { + yearset = ATOI2(arg); + t->tm_year = yearset + 2000; } - PRIV_END + t->tm_year -= 1900; /* Convert to UNIX time. */ + /* FALLTHROUGH */ + case 8: /* MMDDhhmm */ + t->tm_mon = ATOI2(arg); + --t->tm_mon; /* Convert from 01-12 to 00-11 */ + t->tm_mday = ATOI2(arg); + t->tm_hour = ATOI2(arg); + t->tm_min = ATOI2(arg); + break; + default: + goto terr; + } + + t->tm_isdst = -1; /* Figure out DST. */ + tv[0].tv_sec = tv[1].tv_sec = mktime(t); + if (tv[0].tv_sec != -1) + return tv[0].tv_sec; + else +terr: + panic( + "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); } -static void -delete_jobs(argc, argv) - int argc; - char **argv; +static long * +get_job_list(int argc, char *argv[], int *joblen) { - /* Delete every argument (job - ID) given */ - int i; - struct stat buf; - - PRIV_START - - if (chdir(_PATH_ATJOBS) != 0) - perr2("Cannot change to ", _PATH_ATJOBS); - - for (i = optind; i < argc; i++) { - if (stat(argv[i], &buf) != 0) - perr(argv[i]); - if ((buf.st_uid != real_uid) && !(real_uid == 0)) { - fprintf(stderr, "%s: Not owner\n", argv[i]); - exit(EXIT_FAILURE); - } - if (unlink(argv[i]) != 0) - perr(argv[i]); + int i, len; + long *joblist; + char *ep; + + joblist = NULL; + len = argc; + if (len > 0) { + if ((joblist = malloc(len * sizeof(*joblist))) == NULL) + panic("out of memory"); + + for (i = 0; i < argc; i++) { + errno = 0; + if ((joblist[i] = strtol(argv[i], &ep, 10)) < 0 || + ep == argv[i] || *ep != '\0' || errno) + panic("invalid job number"); } - PRIV_END -} /* delete_jobs */ + } -/* Global functions */ + *joblen = len; + return joblist; +} int -main(argc, argv) - int argc; - char **argv; +main(int argc, char **argv) { - int c; - char queue = 'a'; - char *pgm; - - enum { - ATQ, ATRM, AT, BATCH - }; /* what program we want to run */ - int program = AT; /* our default program */ - char *options = "q:f:mv"; /* default options for at */ - time_t timer; - - RELINQUISH_PRIVS - - /* Eat any leading paths */ - if ((pgm = strrchr(argv[0], '/')) == NULL) - pgm = argv[0]; - else - pgm++; - - namep = pgm; - - /* find out what this program is supposed to do */ - if (strcmp(pgm, "atq") == 0) { - program = ATQ; - options = "q:v"; - } else if (strcmp(pgm, "atrm") == 0) { - program = ATRM; - options = ""; - } else if (strcmp(pgm, "batch") == 0) { - program = BATCH; - options = "f:mv"; + int c; + char queue = DEFAULT_AT_QUEUE; + char queue_set = 0; + char *pgm; + + int program = AT; /* our default program */ + const char *options = "q:f:t:rmvldbc"; /* default options for at */ + time_t timer; + long *joblist; + int joblen; + + posixly_correct = COMPAT_MODE("bin/at", "Unix2003"); + joblist = NULL; + joblen = 0; + timer = -1; + RELINQUISH_PRIVS + + /* Eat any leading paths + */ + if ((pgm = strrchr(argv[0], '/')) == NULL) + pgm = argv[0]; + else + pgm++; + + namep = pgm; + + /* find out what this program is supposed to do + */ + if (strcmp(pgm, "atq") == 0) { + program = ATQ; + options = "q:v"; + } + else if (strcmp(pgm, "atrm") == 0) { + program = ATRM; + options = ""; + } + else if (strcmp(pgm, "batch") == 0) { + program = BATCH; + options = "f:q:mv"; + } + + /* process whatever options we can process + */ + opterr=1; + while ((c=getopt(argc, argv, options)) != -1) + switch (c) { + case 'v': /* verify time settings */ + atverify = 1; + break; + + case 'm': /* send mail when job is complete */ + send_mail = 1; + break; + + case 'f': + atinput = optarg; + break; + + case 'q': /* specify queue */ + if (strlen(optarg) > 1) + usage(); + + atqueue = queue = *optarg; + if (!(islower(queue)||isupper(queue))) + usage(); + + queue_set = 1; + break; + + case 'd': + warnx("-d is deprecated; use -r instead"); + /* fall through to 'r' */ + + case 'r': + if (program != AT) + usage(); + + program = ATRM; + options = ""; + break; + + case 't': + if (program != AT) + usage(); + timer = ttime(optarg); + break; + + case 'l': + if (program != AT) + usage(); + + program = ATQ; + options = "q:"; + break; + + case 'b': + if (program != AT) + usage(); + + program = BATCH; + options = "f:q:mv"; + break; + + case 'c': + program = CAT; + options = ""; + break; + + default: + usage(); + break; } + /* end of options eating + */ - /* process whatever options we can process */ - opterr = 1; - while ((c = getopt(argc, argv, options)) != EOF) - switch (c) { - case 'v': /* verify time settings */ - atverify = 1; - break; - - case 'm': /* send mail when job is complete */ - send_mail = 1; - break; - - case 'f': - atinput = optarg; - break; - - case 'q': /* specify queue */ - if (strlen(optarg) > 1) - usage(); - - atqueue = queue = *optarg; - if ((!islower(queue)) || (queue > 'l')) - usage(); - break; - - default: - usage(); - break; - } - /* end of options eating */ + /* select our program + */ + if(!check_permission()) + errx(EXIT_FAILURE, "you do not have permission to use this program"); + switch (program) { + case ATQ: - /* select our program */ - if (!check_permission()) - { - fprintf(stderr, "You do not have permission to use %s.\n",namep); - exit(EXIT_FAILURE); - } - switch (program) { - case ATQ: + REDUCE_PRIV(DAEMON_UID, DAEMON_GID) - REDUCE_PRIV(0); + if (queue_set == 0) + joblist = get_job_list(argc - optind, argv + optind, &joblen); + list_jobs(joblist, joblen); + break; - list_jobs(); - break; + case ATRM: - case ATRM: + REDUCE_PRIV(DAEMON_UID, DAEMON_GID) - REDUCE_PRIV(0); + process_jobs(argc, argv, ATRM); + break; - delete_jobs(argc, argv); - break; + case CAT: - case AT: - timer = parsetime(argc, argv); - if (atverify) { - struct tm *tm = localtime(&timer); + process_jobs(argc, argv, CAT); + break; - fprintf(stderr, "%s\n", asctime(tm)); - } - writefile(timer, queue); - break; + case AT: + /* + * If timer is > -1, then the user gave the time with -t. In that + * case, it's already been set. If not, set it now. + */ + if (timer == -1) + timer = parsetime(argc, argv); - case BATCH: - writefile(time(NULL), 'b'); - break; + if (atverify) + { + struct tm *tm = localtime(&timer); + fprintf(stderr, "%s\n", asctime(tm)); + } + writefile(timer, queue); + break; - default: - panic("Internal error"); - break; + case BATCH: + if (queue_set) + queue = toupper(queue); + else + queue = DEFAULT_BATCH_QUEUE; + + if (argc > optind) + timer = parsetime(argc, argv); + else + timer = time(NULL); + + if (atverify) + { + struct tm *tm = localtime(&timer); + fprintf(stderr, "%s\n", asctime(tm)); } - exit(EXIT_SUCCESS); + + writefile(timer, queue); + break; + + default: + panic("internal error"); + break; + } + exit(EXIT_SUCCESS); } diff --git a/at.tproj/at.h b/at.tproj/at.h index 117a547..ff8008d 100644 --- a/at.tproj/at.h +++ b/at.tproj/at.h @@ -1,30 +1,6 @@ -/* - * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." - * - * @APPLE_LICENSE_HEADER_END@ - */ -/* - * at.h - header for at(1) - * Copyright (c) 1993 by Thomas Koenig - * All rights reserved. +/* + * at.h - header for at(1) + * Copyright (C) 1993 Thomas Koenig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -38,7 +14,7 @@ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * IN NO EVENT SHALL THE AUTHOR(S) 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 @@ -46,12 +22,10 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $Id: at.h,v 1.1.1.2 2000/01/11 02:10:05 wsanchez Exp $ + * $FreeBSD: src/usr.bin/at/at.h,v 1.5 2001/07/24 14:15:51 obrien Exp $ */ extern int fcreated; extern char *namep; extern char atfile[]; extern char atverify; - -#define AT_MAXJOBS 255 /* max jobs outstanding per user */ diff --git a/at.tproj/panic.c b/at.tproj/panic.c index 839a54c..76831a8 100644 --- a/at.tproj/panic.c +++ b/at.tproj/panic.c @@ -1,30 +1,6 @@ -/* - * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." - * - * @APPLE_LICENSE_HEADER_END@ - */ -/* - * panic.c - terminate fast in case of error - * Copyright (c) 1993 by Thomas Koenig - * All rights reserved. +/* + * panic.c - terminate fast in case of error + * Copyright (C) 1993 Thomas Koenig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -38,7 +14,7 @@ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * IN NO EVENT SHALL THE AUTHOR(S) 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 @@ -47,8 +23,12 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +__FBSDID("$FreeBSD: src/usr.bin/at/panic.c,v 1.17 2002/05/16 00:47:14 tjr Exp $"); + /* System Headers */ +#include #include #include #include @@ -57,61 +37,56 @@ /* Local headers */ #include "panic.h" +#include "privs.h" #include "at.h" -/* File scope variables */ - -static char rcsid[] = "$Id: panic.c,v 1.1.1.2 2000/01/11 02:10:05 wsanchez Exp $"; - /* External variables */ /* Global functions */ -#ifdef __APPLE__ -__private_extern__ -#endif void -panic(a) - char *a; +panic(const char *a) { /* Something fatal has happened, print error message and exit. */ - fprintf(stderr, "%s: %s\n", namep, a); - if (fcreated) + if (fcreated) { + PRIV_START unlink(atfile); + PRIV_END + } - exit(EXIT_FAILURE); + errx(EXIT_FAILURE, "%s", a); } void -perr(a) - char *a; +perr(const char *a) { /* Some operating system error; print error message and exit. */ - perror(a); - if (fcreated) - unlink(atfile); + int serrno = errno; - exit(EXIT_FAILURE); -} + if (fcreated) { + PRIV_START + unlink(atfile); + PRIV_END + } -void -perr2(a, b) - char *a, *b; -{ - fprintf(stderr, "%s", a); - perr(b); + errno = serrno; + err(EXIT_FAILURE, "%s", a); } void usage(void) { -/* Print usage and exit. -*/ - fprintf(stderr, "Usage: at [-q x] [-f file] [-m] time\n" - " atq [-q x] [-v]\n" - " atrm [-q x] job ...\n" - " batch [-f file] [-m]\n"); - exit(EXIT_FAILURE); + /* Print usage and exit. */ + fprintf(stderr, "usage: at [-q x] [-f file] [-m] time\n" + " at -c job [job ...]\n" + " at [-f file] -t [[CC]YY]MMDDhhmm[.SS]\n" + " at -r job [job ...]\n" + " at -l -q queuename\n" + " at -l [job ...]\n" + " atq [-q x] [-v]\n" + " atrm job [job ...]\n" + " batch [-f file] [-m]\n"); + exit(EXIT_FAILURE); } diff --git a/at.tproj/panic.h b/at.tproj/panic.h index dda9212..d656d61 100644 --- a/at.tproj/panic.h +++ b/at.tproj/panic.h @@ -1,30 +1,6 @@ -/* - * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." - * - * @APPLE_LICENSE_HEADER_END@ - */ -/* - * panic.h - header for at(1) - * Copyright (c) 1993 Thomas Koenig - * All rights reserved. +/* + * panic.h - header for at(1) + * Copyright (C) 1993 Thomas Koenig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -38,7 +14,7 @@ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * IN NO EVENT SHALL THE AUTHOR(S) 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 @@ -46,10 +22,11 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $Id: panic.h,v 1.1.1.2 2000/01/11 02:10:05 wsanchez Exp $ + * $FreeBSD: src/usr.bin/at/panic.h,v 1.6 2002/01/13 20:21:08 mike Exp $ */ -void panic __P((char *a)); -void perr __P((char *a)); -void perr2 __P((char *a, char *b)); -void usage __P((void)); +#include + +void panic(const char *a) __dead2; +void perr(const char *a) __dead2; +void usage(void) __dead2; diff --git a/at.tproj/parsetime.c b/at.tproj/parsetime.c index 46811fa..4f1b21e 100644 --- a/at.tproj/parsetime.c +++ b/at.tproj/parsetime.c @@ -1,33 +1,9 @@ -/* - * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." - * - * @APPLE_LICENSE_HEADER_END@ - */ /* - * parsetime.c - parse time for at(1) - * Copyright (C) 1993 Thomas Koenig + * parsetime.c - parse time for at(1) + * Copyright (C) 1993, 1994 Thomas Koenig * - * modifications for english-language times - * Copyright (C) 1993 David Parsons - * All rights reserved. + * modifications for English-language times + * Copyright (C) 1993 David Parsons * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -41,7 +17,7 @@ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * IN NO EVENT SHALL THE AUTHOR(S) 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 @@ -50,27 +26,38 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS + * DOT ::= ':'|'.' * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \ * |NOON | |[TOMORROW] | - * |MIDNIGHT | |NUMBER [SLASH NUMBER [SLASH NUMBER]]| - * \TEATIME / \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ + * |MIDNIGHT | |[DAY OF WEEK] | + * \TEATIME / |NUMBER [SLASH NUMBER [SLASH NUMBER]]| + * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ */ +#include +__FBSDID("$FreeBSD: src/usr.bin/at/parsetime.c,v 1.25 2001/12/10 21:13:01 dwmalone Exp $"); + /* System Headers */ #include +#include +#include #include #include #include #include #include +#include #include -#include +#ifndef __FreeBSD__ +#include +#endif /* Local headers */ #include "at.h" #include "panic.h" +#include "parsetime.h" /* Structures and unions */ @@ -78,54 +65,81 @@ enum { /* symbols */ MIDNIGHT, NOON, TEATIME, PM, AM, TOMORROW, TODAY, NOW, - MINUTES, HOURS, DAYS, WEEKS, - NUMBER, PLUS, DOT, SLASH, ID, JUNK, + MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS, + NUMBER, PLUS, DOT, COMMA, SLASH, ID, JUNK, JAN, FEB, MAR, APR, MAY, JUN, - JUL, AUG, SEP, OCT, NOV, DEC -}; + JUL, AUG, SEP, OCT, NOV, DEC, + SUN, MON, TUE, WED, THU, FRI, SAT, + UTC + }; -/* - * parse translation table - table driven parsers can be your FRIEND! +/* parse translation table - table driven parsers can be your FRIEND! */ struct { - char *name; /* token name */ + const char *name; /* token name */ int value; /* token id */ + int plural; /* is this plural? */ } Specials[] = { - { "midnight", MIDNIGHT }, /* 00:00:00 of today or tomorrow */ - { "noon", NOON }, /* 12:00:00 of today or tomorrow */ - { "teatime", TEATIME }, /* 16:00:00 of today or tomorrow */ - { "am", AM }, /* morning times for 0-12 clock */ - { "pm", PM }, /* evening times for 0-12 clock */ - { "tomorrow", TOMORROW }, /* execute 24 hours from time */ - { "today", TODAY }, /* execute today - don't advance time */ - { "now", NOW }, /* opt prefix for PLUS */ - - { "minute", MINUTES }, /* minutes multiplier */ - { "min", MINUTES }, - { "m", MINUTES }, - { "minutes", MINUTES }, /* (pluralized) */ - { "hour", HOURS }, /* hours ... */ - { "hr", HOURS }, /* abbreviated */ - { "h", HOURS }, - { "hours", HOURS }, /* (pluralized) */ - { "day", DAYS }, /* days ... */ - { "d", DAYS }, - { "days", DAYS }, /* (pluralized) */ - { "week", WEEKS }, /* week ... */ - { "w", WEEKS }, - { "weeks", WEEKS }, /* (pluralized) */ - { "jan", JAN }, - { "feb", FEB }, - { "mar", MAR }, - { "apr", APR }, - { "may", MAY }, - { "jun", JUN }, - { "jul", JUL }, - { "aug", AUG }, - { "sep", SEP }, - { "oct", OCT }, - { "nov", NOV }, - { "dec", DEC } + { "midnight", MIDNIGHT,0 }, /* 00:00:00 of today or tomorrow */ + { "noon", NOON,0 }, /* 12:00:00 of today or tomorrow */ + { "teatime", TEATIME,0 }, /* 16:00:00 of today or tomorrow */ + { "am", AM,0 }, /* morning times for 0-12 clock */ + { "pm", PM,0 }, /* evening times for 0-12 clock */ + { "tomorrow", TOMORROW,0 }, /* execute 24 hours from time */ + { "today", TODAY, 0 }, /* execute today - don't advance time */ + { "now", NOW,0 }, /* opt prefix for PLUS */ + + { "minute", MINUTES,0 }, /* minutes multiplier */ + { "minutes", MINUTES,1 }, /* (pluralized) */ + { "hour", HOURS,0 }, /* hours ... */ + { "hours", HOURS,1 }, /* (pluralized) */ + { "day", DAYS,0 }, /* days ... */ + { "days", DAYS,1 }, /* (pluralized) */ + { "week", WEEKS,0 }, /* week ... */ + { "weeks", WEEKS,1 }, /* (pluralized) */ + { "month", MONTHS,0 }, /* month ... */ + { "months", MONTHS,1 }, /* (pluralized) */ + { "year", YEARS,0 }, /* year ... */ + { "years", YEARS,1 }, /* (pluralized) */ + { "jan", JAN,0 }, + { "feb", FEB,0 }, + { "mar", MAR,0 }, + { "apr", APR,0 }, + { "may", MAY,0 }, + { "jun", JUN,0 }, + { "jul", JUL,0 }, + { "aug", AUG,0 }, + { "sep", SEP,0 }, + { "oct", OCT,0 }, + { "nov", NOV,0 }, + { "dec", DEC,0 }, + { "january", JAN,0 }, + { "february", FEB,0 }, + { "march", MAR,0 }, + { "april", APR,0 }, + { "may", MAY,0 }, + { "june", JUN,0 }, + { "july", JUL,0 }, + { "august", AUG,0 }, + { "september", SEP,0 }, + { "october", OCT,0 }, + { "november", NOV,0 }, + { "december", DEC,0 }, + { "sunday", SUN, 0 }, + { "sun", SUN, 0 }, + { "monday", MON, 0 }, + { "mon", MON, 0 }, + { "tuesday", TUE, 0 }, + { "tue", TUE, 0 }, + { "wednesday", WED, 0 }, + { "wed", WED, 0 }, + { "thursday", THU, 0 }, + { "thu", THU, 0 }, + { "friday", FRI, 0 }, + { "fri", FRI, 0 }, + { "saturday", SAT, 0 }, + { "sat", SAT, 0 }, + { "utc", UTC, 0 }, } ; /* File scope variables */ @@ -136,10 +150,9 @@ static char *sct; /* scanner - next char pointer in current argument */ static int need; /* scanner - need to advance to next argument */ static char *sc_token; /* scanner - token buffer */ -static size_t sc_len; /* scanner - lenght of token buffer */ +static size_t sc_len; /* scanner - length of token buffer */ static int sc_tokid; /* scanner - token id */ - -static char rcsid[] = "$Id: parsetime.c,v 1.1.1.2 2000/01/11 02:10:05 wsanchez Exp $"; +static int sc_tokplur; /* scanner - is token plural? */ /* Local functions */ @@ -147,13 +160,13 @@ static char rcsid[] = "$Id: parsetime.c,v 1.1.1.2 2000/01/11 02:10:05 wsanchez E * parse a token, checking if it's something special to us */ static int -parse_token(arg) - char *arg; +parse_token(char *arg) { - int i; + size_t i; for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++) if (strcasecmp(Specials[i].name, arg) == 0) { + sc_tokplur = Specials[i].plural; return sc_tokid = Specials[i].value; } @@ -166,37 +179,34 @@ parse_token(arg) * init_scanner() sets up the scanner to eat arguments */ static void -init_scanner(argc, argv) - int argc; - char **argv; +init_scanner(int argc, char **argv) { scp = argv; scc = argc; need = 1; sc_len = 1; - while (--argc > 0) - sc_len += strlen(*++argv); + while (argc-- > 0) + sc_len += strlen(*argv++); - sc_token = (char *) malloc(sc_len); - if (sc_token == NULL) - panic("Insufficient virtual memory"); + if ((sc_token = malloc(sc_len)) == NULL) + errx(EXIT_FAILURE, "virtual memory exhausted"); } /* init_scanner */ /* * token() fetches a token from the input stream */ static int -token() +token(void) { int idx; while (1) { memset(sc_token, 0, sc_len); sc_tokid = EOF; + sc_tokplur = 0; idx = 0; - /* - * if we need to read another argument, walk along the argument list; + /* if we need to read another argument, walk along the argument list; * when we fall off the arglist, we'll just return EOF forever */ if (need) { @@ -207,8 +217,7 @@ token() scc--; need = 0; } - /* - * eat whitespace now - if we walk off the end of the argument, + /* eat whitespace now - if we walk off the end of the argument, * we'll continue, which puts us up at the top of the while loop * to fetch the next argument in */ @@ -219,20 +228,19 @@ token() continue; } - /* - * preserve the first character of the new token + /* preserve the first character of the new token */ sc_token[0] = *sct++; - /* - * then see what it is + /* then see what it is */ if (isdigit(sc_token[0])) { while (isdigit(*sct)) sc_token[++idx] = *sct++; sc_token[++idx] = 0; return sc_tokid = NUMBER; - } else if (isalpha(sc_token[0])) { + } + else if (isalpha(sc_token[0])) { while (isalpha(*sct)) sc_token[++idx] = *sct++; sc_token[++idx] = 0; @@ -244,6 +252,8 @@ token() return sc_tokid = PLUS; else if (sc_token[0] == '/') return sc_tokid = SLASH; + else if (sc_token[0] == ',') + return sc_tokid = COMMA; else return sc_tokid = JUNK; } /* while (1) */ @@ -254,8 +264,7 @@ token() * plonk() gives an appropriate error message if a token is incorrect */ static void -plonk(tok) - int tok; +plonk(int tok) { panic((tok == EOF) ? "incomplete time" : "garbled time"); @@ -266,103 +275,78 @@ plonk(tok) * expect() gets a token and dies most horribly if it's not the token we want */ static void -expect(desired) - int desired; +expect(int desired) { if (token() != desired) plonk(sc_tokid); /* and we die here... */ } /* expect */ -/* - * dateadd() adds a number of minutes to a date. It is extraordinarily - * stupid regarding day-of-month overflow, and will most likely not - * work properly - */ -static void -dateadd(minutes, tm) - int minutes; - struct tm *tm; -{ - /* increment days */ - - while (minutes > 24*60) { - minutes -= 24*60; - tm->tm_mday++; - } - - /* increment hours */ - while (minutes > 60) { - minutes -= 60; - tm->tm_hour++; - if (tm->tm_hour > 23) { - tm->tm_mday++; - tm->tm_hour = 0; - } - } - - /* increment minutes */ - tm->tm_min += minutes; - - if (tm->tm_min > 59) { - tm->tm_hour++; - tm->tm_min -= 60; - - if (tm->tm_hour > 23) { - tm->tm_mday++; - tm->tm_hour = 0; - } - } -} /* dateadd */ - - /* * plus() parses a now + time * - * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS] + * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS] * */ + static void -plus(tm) - struct tm *tm; +plus(struct tm *tm) { int delay; + int expectplur; expect(NUMBER); delay = atoi(sc_token); + expectplur = (delay != 1) ? 1 : 0; switch (token()) { + case YEARS: + tm->tm_year += delay; + break; + case MONTHS: + tm->tm_mon += delay; + break; case WEEKS: delay *= 7; case DAYS: - delay *= 24; + tm->tm_mday += delay; + break; case HOURS: - delay *= 60; + tm->tm_hour += delay; + break; case MINUTES: - dateadd(delay, tm); - return; + tm->tm_min += delay; + break; + default: + plonk(sc_tokid); + break; } - plonk(sc_tokid); + + if (expectplur != sc_tokplur) + warnx("pluralization is wrong"); + + tm->tm_isdst = -1; + if (mktime(tm) < 0) + plonk(sc_tokid); + } /* plus */ /* * tod() computes the time of day - * [NUMBER [DOT NUMBER] [AM|PM]] + * [NUMBER [DOT NUMBER] [AM|PM]] [UTC] */ static void -tod(tm) - struct tm *tm; +tod(struct tm *tm) { int hour, minute = 0; - int tlen; + size_t tlen; hour = atoi(sc_token); tlen = strlen(sc_token); - /* - * first pick out the time of day - if it's 4 digits, we assume + /* first pick out the time of day - if it's 4 digits, we assume * a HHMM time, otherwise it's HH DOT MM time */ if (token() == DOT) { @@ -371,33 +355,58 @@ tod(tm) if (minute > 59) panic("garbled time"); token(); - } else if (tlen == 4) { + } + else if (tlen == 4) { minute = hour%100; if (minute > 59) - panic("garbeld time"); + panic("garbled time"); hour = hour/100; } - /* - * check if an AM or PM specifier was given + /* check if an AM or PM specifier was given */ - if (sc_tokid == AM || sc_tokid == PM) { + switch (sc_tokid) { + case AM: + case PM: if (hour > 12) panic("garbled time"); - if (sc_tokid == PM) - hour += 12; - token(); - } else if (hour > 23) - panic("garbled time"); + if (sc_tokid == PM) { + if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */ + hour += 12; + } else { + if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */ + hour = 0; + } + if (UTC != token()) + break; /* else fallthrough */ + + case UTC: + hour += tm->tm_gmtoff/(60*60); + while (hour < 0) + hour += 24; + minute += (tm->tm_gmtoff/60); + while (minute < 0) + minute += 60; + tm->tm_gmtoff = 0; + token(); + break; + default: + if (hour > 23) + panic("garbled time"); + break; + } - /* - * if we specify an absolute time, we don't want to bump the day even + /* if we specify an absolute time, we don't want to bump the day even * if we've gone past that time - but if we're specifying a time plus * a relative offset, it's okay to bump things + * If minutes are the same assume tomorrow was meant */ - if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) + if ((sc_tokid == EOF || sc_tokid == PLUS) && + ((tm->tm_hour > hour) || ((tm->tm_hour == hour) && (tm->tm_min >= minute)))) { tm->tm_mday++; + tm->tm_wday++; + } tm->tm_hour = hour; tm->tm_min = minute; @@ -412,15 +421,30 @@ tod(tm) * assign_date() assigns a date, wrapping to next year if needed */ static void -assign_date(tm, mday, mon, year) - struct tm *tm; - long mday, mon, year; +assign_date(struct tm *tm, long mday, long mon, long year) { - if (year > 99) { - if (year > 1899) - year -= 1900; - else - panic("garbled time"); + /* + * Convert year into tm_year format (year - 1900). + * We may be given the year in 2 digit, 4 digit, or tm_year format. + */ + if (year != -1) { + if (year >= TM_YEAR_BASE) + year -= TM_YEAR_BASE; /* convert from 4 digit year */ + else if (year < 100) { + /* convert from 2 digit year */ + struct tm *lt; + time_t now; + + time(&now); + lt = localtime(&now); + + /* Convert to tm_year assuming current century */ + year += (lt->tm_year / 100) * 100; + + if (year == lt->tm_year - 1) year++; + else if (year < lt->tm_year) + year += 100; /* must be in next century */ + } } if (year < 0 && @@ -440,15 +464,15 @@ assign_date(tm, mday, mon, year) * * /[ NUMBER [NUMBER]] \ * |[TOMORROW] | + * |[DAY OF WEEK] | * |NUMBER [SLASH NUMBER [SLASH NUMBER]]| * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ */ static void -month(tm) - struct tm *tm; +month(struct tm *tm) { long year= (-1); - long mday, mon; + long mday = 0, wday, mon; int tlen; switch (sc_tokid) { @@ -459,28 +483,52 @@ month(tm) case TOMORROW: /* do something tomorrow */ tm->tm_mday ++; + tm->tm_wday ++; case TODAY: /* force ourselves to stay in today - no further processing */ token(); break; case JAN: case FEB: case MAR: case APR: case MAY: case JUN: case JUL: case AUG: case SEP: case OCT: case NOV: case DEC: - /* - * do month mday [year] + /* do month mday [,year] */ mon = (sc_tokid-JAN); expect(NUMBER); mday = atol(sc_token); - if (token() == NUMBER) { - year = atol(sc_token); - token(); + if (token() == COMMA) { + if (token() == NUMBER) { + year = atol(sc_token); + token(); + } } assign_date(tm, mday, mon, year); + if (sc_tokid == PLUS) + plus(tm); + break; + + case SUN: case MON: case TUE: + case WED: case THU: case FRI: + case SAT: + /* do a particular day of the week + */ + wday = (sc_tokid-SUN); + + mday = tm->tm_mday; + + /* if this day is < today, then roll to next week + */ + if (wday < tm->tm_wday) + mday += 7 - (tm->tm_wday - wday); + else + mday += (wday - tm->tm_wday); + + tm->tm_wday = wday; + + assign_date(tm, mday, tm->tm_mon, tm->tm_year); break; case NUMBER: - /* - * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy + /* get numeric MMDDYY, mm/dd/yy, or dd.mm.yy */ tlen = strlen(sc_token); mon = atol(sc_token); @@ -498,25 +546,27 @@ month(tm) token(); } - /* - * flip months and days for european timing + /* flip months and days for European timing */ if (sep == DOT) { int x = mday; mday = mon; mon = x; } - } else if (tlen == 6 || tlen == 8) { + } + else if (tlen == 6 || tlen == 8) { if (tlen == 8) { - year = (mon % 10000) - 1900; + year = (mon % 10000) - TM_YEAR_BASE; mon /= 10000; - } else { + } + else { year = mon % 100; mon /= 100; } mday = mon % 100; mon /= 100; - } else + } + else panic("garbled time"); mon--; @@ -532,12 +582,9 @@ month(tm) /* Global functions */ time_t -parsetime(argc, argv) - int argc; - char **argv; +parsetime(int argc, char **argv) { -/* - * Do the argument parsing, die if necessary, and return the time the job +/* Do the argument parsing, die if necessary, and return the time the job * should be run. */ time_t nowtimer, runtimer; @@ -558,7 +605,11 @@ parsetime(argc, argv) init_scanner(argc-optind, argv+optind); switch (token()) { - case NOW: /* now is optional prefix for PLUS tree */ + case NOW: + if (scc < 1) { + return nowtimer; + } + /* now is optional prefix for PLUS tree */ expect(PLUS); case PLUS: plus(&runtime); @@ -569,8 +620,7 @@ parsetime(argc, argv) month(&runtime); break; - /* - * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised + /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised * hr to zero up above, then fall into this case in such a * way so we add +12 +4 hours to it for teatime, +12 hours * to it for noon, and nothing at all for midnight, then @@ -582,20 +632,21 @@ parsetime(argc, argv) case NOON: hr += 12; case MIDNIGHT: - if (runtime.tm_hour >= hr) + if (runtime.tm_hour >= hr) { runtime.tm_mday++; + runtime.tm_wday++; + } runtime.tm_hour = hr; runtime.tm_min = 0; token(); - /* fall through to month setting */ + /* FALLTHROUGH to month setting */ default: month(&runtime); break; } /* ugly case statement */ expect(EOF); - /* - * adjust for daylight savings time + /* adjust for daylight savings time */ runtime.tm_isdst = -1; runtimer = mktime(&runtime); @@ -608,7 +659,7 @@ parsetime(argc, argv) panic("garbled time"); if (nowtimer > runtimer) - panic("Trying to travel back in time"); + panic("trying to travel back in time"); return runtimer; } /* parsetime */ diff --git a/at.tproj/parsetime.h b/at.tproj/parsetime.h index c2e792b..30c3f20 100644 --- a/at.tproj/parsetime.h +++ b/at.tproj/parsetime.h @@ -1,30 +1,6 @@ -/* - * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." - * - * @APPLE_LICENSE_HEADER_END@ - */ -/* - * at.h - header for at(1) - * Copyright (c) 1993 by Thomas Koenig - * All rights reserved. +/* + * at.h - header for at(1) + * Copyright (C) 1993 Thomas Koenig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -38,15 +14,13 @@ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * IN NO EVENT SHALL THE AUTHOR(S) 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, WETHER 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. - * - * $Id: parsetime.h,v 1.1.1.2 2000/01/11 02:10:05 wsanchez Exp $ */ -time_t parsetime __P((int argc, char **argv)); +time_t parsetime(int argc, char **argv); diff --git a/at.tproj/perm.c b/at.tproj/perm.c index 0a5ba30..872ce44 100644 --- a/at.tproj/perm.c +++ b/at.tproj/perm.c @@ -1,26 +1,3 @@ -/* - * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." - * - * @APPLE_LICENSE_HEADER_END@ - */ /* * perm.c - check user permission for at(1) * Copyright (C) 1994 Thomas Koenig @@ -46,9 +23,13 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +__FBSDID("$FreeBSD: src/usr.bin/at/perm.c,v 1.13 2001/12/10 21:13:01 dwmalone Exp $"); + /* System Headers */ #include +#include #include #include #include @@ -59,9 +40,9 @@ /* Local headers */ -#include "privs.h" #include "at.h" -#include "pathnames.h" +#include "perm.h" +#include "privs.h" /* Macros */ @@ -69,10 +50,6 @@ /* Structures and unions */ -/* File scope variables */ - -static char rcsid[] = "$Id: perm.c,v 1.1.1.2 2000/01/11 02:10:05 wsanchez Exp $"; - /* Function declarations */ static int check_for_user(FILE *fp,const char *name); @@ -86,10 +63,8 @@ static int check_for_user(FILE *fp,const char *name) int found = 0; len = strlen(name); - if ((buffer = malloc(sizeof (char) * (len+2))) == NULL) { - fprintf(stderr, "malloc error!"); - exit(EXIT_FAILURE); - } + if ((buffer = malloc(len+2)) == NULL) + errx(EXIT_FAILURE, "virtual memory exhausted"); while(fgets(buffer, len+2, fp) != NULL) { @@ -105,7 +80,7 @@ static int check_for_user(FILE *fp,const char *name) return found; } /* Global functions */ -int check_permission() +int check_permission(void) { FILE *fp; uid_t uid = geteuid(); @@ -115,14 +90,11 @@ int check_permission() return 1; if ((pentry = getpwuid(uid)) == NULL) - { - perror("Cannot access user database"); - exit(EXIT_FAILURE); - } + err(EXIT_FAILURE, "cannot access user database"); PRIV_START - fp=fopen(_PATH_AT "at.allow","r"); + fp=fopen(PERM_PATH "at.allow","r"); PRIV_END @@ -130,12 +102,12 @@ int check_permission() { return check_for_user(fp, pentry->pw_name); } - else + else if (errno == ENOENT) { PRIV_START - fp=fopen(_PATH_AT "at.deny", "r"); + fp=fopen(PERM_PATH "at.deny", "r"); PRIV_END @@ -143,7 +115,10 @@ int check_permission() { return !check_for_user(fp, pentry->pw_name); } - perror("at.deny"); + else if (errno != ENOENT) + warn("at.deny"); } + else + warn("at.allow"); return 0; } diff --git a/at.tproj/perm.h b/at.tproj/perm.h index 8d14b37..92d0e31 100644 --- a/at.tproj/perm.h +++ b/at.tproj/perm.h @@ -1,26 +1,3 @@ -/* - * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." - * - * @APPLE_LICENSE_HEADER_END@ - */ /* * perm.h - header for at(1) * Copyright (C) 1994 Thomas Koenig @@ -44,6 +21,8 @@ * THEORY OF LIABILITY, WETHER 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. + * + * $FreeBSD: src/usr.bin/at/perm.h,v 1.4 2001/12/02 12:26:18 markm Exp $ */ -int check_permission(); +int check_permission(void); diff --git a/at.tproj/privs.h b/at.tproj/privs.h index ae37877..9c5e39d 100644 --- a/at.tproj/privs.h +++ b/at.tproj/privs.h @@ -1,30 +1,6 @@ -/* - * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." - * - * @APPLE_LICENSE_HEADER_END@ - */ -/* - * privs.h - header for privileged operations - * Copyright (c) 1993 by Thomas Koenig - * All rights reserved. +/* + * privs.h - header for privileged operations + * Copyright (C) 1993 Thomas Koenig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -38,7 +14,7 @@ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * IN NO EVENT SHALL THE AUTHOR(S) 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 @@ -46,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $Id: privs.h,v 1.1.1.2 2000/01/11 02:10:05 wsanchez Exp $ + * $FreeBSD: src/usr.bin/at/privs.h,v 1.8 2001/09/04 16:15:51 ru Exp $ */ #ifndef _PRIVS_H @@ -54,22 +30,22 @@ #include -/* Relinquish privileges temporarily for a setuid program - * with the option of getting them back later. This is done by swapping - * the real and effective userid BSD style. Call RELINQUISH_PRIVS once - * at the beginning of the main program. This will cause all operatons +/* Relinquish privileges temporarily for a setuid or setgid program + * with the option of getting them back later. This is done by + * utilizing POSIX saved user and group IDs. Call RELINQUISH_PRIVS once + * at the beginning of the main program. This will cause all operations * to be executed with the real userid. When you need the privileges - * of the setuid invocation, call PRIV_START; when you no longer + * of the setuid/setgid invocation, call PRIV_START; when you no longer * need it, call PRIV_END. Note that it is an error to call PRIV_START * and not PRIV_END within the same function. * - * Use RELINQUISH_PRIVS_ROOT(a) if your program started out running + * Use RELINQUISH_PRIVS_ROOT(a,b) if your program started out running * as root, and you want to drop back the effective userid to a * and the effective group id to b, with the option to get them back * later. * * If you no longer need root privileges, but those of some other - * userid, you can call REDUCE_PRIV(a) when your effective + * userid/groupid, you can call REDUCE_PRIV(a,b) when your effective * is the user's. * * Problems: Do not use return between PRIV_START and PRIV_END; this @@ -79,7 +55,7 @@ * It is NOT safe to call exec(), system() or popen() with a user- * supplied program (i.e. without carefully checking PATH and any * library load paths) with relinquished privileges; the called program - * can aquire them just as easily. Set both effective and real userid + * can acquire them just as easily. Set both effective and real userid * to the real userid before calling any of them. */ @@ -88,28 +64,47 @@ extern #endif uid_t real_uid, effective_uid; +#ifndef MAIN +extern +#endif +gid_t real_gid, effective_gid; + #define RELINQUISH_PRIVS { \ real_uid = getuid(); \ effective_uid = geteuid(); \ + real_gid = getgid(); \ + effective_gid = getegid(); \ + setegid(real_gid); \ seteuid(real_uid); \ } -#define RELINQUISH_PRIVS_ROOT(a) { \ +#define RELINQUISH_PRIVS_ROOT(a, b) { \ real_uid = (a); \ effective_uid = geteuid(); \ + real_gid = (b); \ + effective_gid = getegid(); \ + setegid(real_gid); \ seteuid(real_uid); \ } #define PRIV_START { \ - seteuid(effective_uid); + if (seteuid(0)<0) perr("cannot regain privs"); \ + if (setegid(effective_gid)<0) perr("cannot reset gid"); \ + if (seteuid(effective_uid)<0) perr("cannot reset uid"); \ +} -#define PRIV_END \ - seteuid(real_uid); \ +#define PRIV_END { \ + if (seteuid(0)<0) perr("cannot regain privs"); \ + if (setegid(real_gid)<0) perr("cannot reset gid"); \ + if (seteuid(real_uid)<0) perr("cannot reset uid"); \ } -#define REDUCE_PRIV(a) { \ - seteuid(effective_uid); \ - real_uid = effective_uid = (a); \ - setuid(real_uid); \ +#define REDUCE_PRIV(a, b) { \ + PRIV_START \ + effective_uid = (a); \ + effective_gid = (b); \ + setregid((gid_t)-1, effective_gid); \ + setreuid((uid_t)-1, effective_uid); \ + PRIV_END \ } #endif diff --git a/atrun.tproj/Makefile b/atrun.tproj/Makefile index 8150fd1..b2bb44b 100644 --- a/atrun.tproj/Makefile +++ b/atrun.tproj/Makefile @@ -16,7 +16,9 @@ HFILES = atrun.h CFILES = atrun.c -OTHERSRCS = Makefile.preamble Makefile Makefile.dist atrun.8 +OTHERSRCS = Makefile.preamble Makefile Makefile.dist atrun.8 Makefile.postamble + +NEXTSTEP_PB_CFLAGS += -DDAEMON_UID=1 -DDAEMON_GID=1 MAKEFILEDIR = $(MAKEFILEPATH)/pb_makefiles @@ -30,7 +32,7 @@ DEBUG_LIBS = $(LIBS) PROF_LIBS = $(LIBS) -HEADER_PATHS = -I../at +HEADER_PATHS = -I../at.tproj NEXTSTEP_OBJCPLUS_COMPILER = /usr/bin/cc diff --git a/atrun.tproj/Makefile.postamble b/atrun.tproj/Makefile.postamble new file mode 100644 index 0000000..dd90965 --- /dev/null +++ b/atrun.tproj/Makefile.postamble @@ -0,0 +1,3 @@ +after_install: + mkdir -p $(DSTROOT)/usr/share/man/man8 + install -c -m 444 atrun.8 $(DSTROOT)/usr/share/man/man8 diff --git a/atrun.tproj/atrun.c b/atrun.tproj/atrun.c index f9e31bd..9d52ffa 100644 --- a/atrun.tproj/atrun.c +++ b/atrun.tproj/atrun.c @@ -1,30 +1,6 @@ -/* - * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights - * Reserved. This file contains Original Code and/or Modifications of - * Original Code as defined in and that are subject to the Apple Public - * Source License Version 1.0 (the 'License'). You may not use this file - * except in compliance with the License. Please obtain a copy of the - * License at http://www.apple.com/publicsource and read it before using - * this file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License." - * - * @APPLE_LICENSE_HEADER_END@ - */ -/* - * atrun.c - run jobs queued by at; run with root privileges. - * Copyright (c) 1993 by Thomas Koenig - * All rights reserved. +/* + * atrun.c - run jobs queued by at; run with root privileges. + * Copyright (C) 1993, 1994 Thomas Koenig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -38,7 +14,7 @@ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * IN NO EVENT SHALL THE AUTHOR(S) 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 @@ -47,304 +23,479 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#ifndef lint +static const char rcsid[] = + "$FreeBSD: src/libexec/atrun/atrun.c,v 1.18 2004/07/11 17:37:32 stefanf Exp $"; +#endif /* not lint */ + /* System Headers */ #include #include #include #include +#include +#include #include -#include +#include +#include #include #include #include #include #include #include +#include #include #include -#include - +#include +#if 1 #include +#else +#include +#endif + +#if (MAXLOGNAME-1) > UT_NAMESIZE +#define LOGNAMESIZE UT_NAMESIZE +#else +#define LOGNAMESIZE (MAXLOGNAME-1) +#endif /* Local headers */ #define MAIN #include "privs.h" #include "pathnames.h" -#include "atrun.h" +/* Macros */ + +#ifndef ATJOB_DIR +#define ATJOB_DIR _PATH_ATJOBS +#endif + +#ifndef ATSPOOL_DIR +#define ATSPOOL_DIR _PATH_ATSPOOL +#endif + +#ifndef LOADAVG_MX +#define LOADAVG_MX 1.5 +#endif /* File scope variables */ -static char *namep; -static char rcsid[] = "$Id: atrun.c,v 1.1.1.2 2000/01/11 02:10:06 wsanchez Exp $"; +static int debug = 0; + +void perr(const char *a); +static void usage(void); /* Local functions */ -static void -perr(a) - const char *a; +static int +write_string(int fd, const char* a) { - syslog(LOG_ERR, "%s: %m", a); - exit(EXIT_FAILURE); + return write(fd, a, strlen(a)); } -static int -write_string(fd, a) - int fd; - const char *a; +#undef DEBUG_FORK +#ifdef DEBUG_FORK +static pid_t +myfork(void) { - return write(fd, a, strlen(a)); + pid_t res; + res = fork(); + if (res == 0) + kill(getpid(),SIGSTOP); + return res; } +#define fork myfork +#endif + static void -run_file(filename, uid, gid) - const char *filename; - uid_t uid; - gid_t gid; +run_file(const char *filename, uid_t uid, gid_t gid) { - /* - * Run a file by by spawning off a process which redirects I/O, - * spawns a subshell, then waits for it to complete and spawns another - * process to send mail to the user. - */ - pid_t pid; - int fd_out, fd_in; - int queue; - char mailbuf[9]; - char *mailname = NULL; - FILE *stream; - int send_mail = 0; - struct stat buf; - off_t size; - struct passwd *pentry; - int fflags; - - pid = fork(); - if (pid == -1) - perr("Cannot fork"); - else if (pid > 0) - return; - - /* - * Let's see who we mail to. Hopefully, we can read it from the - * command file; if not, send it to the owner, or, failing that, to - * root. - */ +/* Run a file by spawning off a process which redirects I/O, + * spawns a subshell, then waits for it to complete and sends + * mail to the user. + */ + pid_t pid; + int fd_out, fd_in; + int queue; + char mailbuf[LOGNAMESIZE + 1], fmt[49]; + char *mailname = NULL; + FILE *stream; + int send_mail = 0; + struct stat buf, lbuf; + off_t size; + struct passwd *pentry; + int fflags; + long nuid; + long ngid; + + + PRIV_START + + if (chmod(filename, S_IRUSR) != 0) + { + perr("cannot change file permissions"); + } + + PRIV_END + + pid = fork(); + if (pid == -1) + perr("cannot fork"); + + else if (pid != 0) + return; + + /* Let's see who we mail to. Hopefully, we can read it from + * the command file; if not, send it to the owner, or, failing that, + * to root. + */ + + pentry = getpwuid(uid); + if (pentry == NULL) + { + syslog(LOG_ERR,"Userid %lu not found - aborting job %s", + (unsigned long) uid, filename); + exit(EXIT_FAILURE); + } + PRIV_START + + stream=fopen(filename, "r"); + + PRIV_END + +#ifdef __FreeBSD__ + if (pentry->pw_expire && time(NULL) >= pentry->pw_expire) + { + syslog(LOG_ERR, "Userid %lu is expired - aborting job %s", + (unsigned long) uid, filename); + exit(EXIT_FAILURE); + } +#endif - PRIV_START + if (stream == NULL) + perr("cannot open input file"); - stream = fopen(filename, "r"); + if ((fd_in = dup(fileno(stream))) <0) + perr("error duplicating input file descriptor"); - PRIV_END + if (fstat(fd_in, &buf) == -1) + perr("error in fstat of input file descriptor"); - if (stream == NULL) - perr("Cannot open input file"); + if (lstat(filename, &lbuf) == -1) + perr("error in fstat of input file"); - if ((fd_in = dup(fileno(stream))) < 0) - perr("Error duplicating input file descriptor"); + if (S_ISLNK(lbuf.st_mode)) { + syslog(LOG_ERR,"Symbolic link encountered in job %s - aborting", + filename); + exit(EXIT_FAILURE); + } + if ((lbuf.st_dev != buf.st_dev) || (lbuf.st_ino != buf.st_ino) || + (lbuf.st_uid != buf.st_uid) || (lbuf.st_gid != buf.st_gid) || + (lbuf.st_size!=buf.st_size)) { + syslog(LOG_ERR,"Somebody changed files from under us for job %s - " + "aborting",filename); + exit(EXIT_FAILURE); + } + if (buf.st_nlink > 1) { + syslog(LOG_ERR,"Someboy is trying to run a linked script for job %s", + filename); + exit(EXIT_FAILURE); + } + if ((fflags = fcntl(fd_in, F_GETFD)) <0) + perr("error in fcntl"); - if ((fflags = fcntl(fd_in, F_GETFD)) < 0) - perr("Error in fcntl"); + fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC); - fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC); + snprintf(fmt, sizeof(fmt), + "#!/bin/sh\n# atrun uid=%%ld gid=%%ld\n# mail %%%ds %%d", + LOGNAMESIZE); + if (fscanf(stream, fmt, &nuid, &ngid, mailbuf, &send_mail) != 4) { + syslog(LOG_ERR,"File %s is in wrong format - aborting", filename); + exit(EXIT_FAILURE); + } + if (mailbuf[0] == '-') { + syslog(LOG_ERR,"illegal mail name %s in %s",mailbuf,filename); + exit(EXIT_FAILURE); + } + mailname = mailbuf; + if (nuid != uid) { + syslog(LOG_ERR,"Job %s - userid %ld does not match file uid %lu", + filename, nuid, (unsigned long)uid); + exit(EXIT_FAILURE); + } + if (ngid != gid) { + syslog(LOG_ERR,"Job %s - groupid %ld does not match file gid %lu", + filename, ngid, (unsigned long)gid); + exit(EXIT_FAILURE); + } + fclose(stream); + if (chdir(ATSPOOL_DIR) < 0) + perr("cannot chdir to " ATSPOOL_DIR); + + /* Create a file to hold the output of the job we are about to run. + * Write the mail header. + */ + if((fd_out=open(filename, + O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0) + perr("cannot create output file"); + + write_string(fd_out, "Subject: Output from your job "); + write_string(fd_out, filename); + write_string(fd_out, "\n\n"); + fstat(fd_out, &buf); + size = buf.st_size; + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + pid = fork(); + if (pid < 0) + perr("error in fork"); + + else if (pid == 0) + { + char *nul = NULL; + char **nenvp = &nul; + + /* Set up things for the child; we want standard input from the input file, + * and standard output and error sent to our output file. + */ - if (fscanf(stream, "#! /bin/sh\n# mail %8s %d", mailbuf, &send_mail) == 2) { - mailname = mailbuf; - } else { - pentry = getpwuid(uid); - if (pentry == NULL) - mailname = "root"; - else - mailname = pentry->pw_name; - } - fclose(stream); - if (chdir(_PATH_ATSPOOL) < 0) - perr("Cannot chdir to " _PATH_ATSPOOL); + if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0) + perr("error in lseek"); - /* - * Create a file to hold the output of the job we are about to - * run. Write the mail header. - */ - if ((fd_out = open(filename, - O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0) - perr("Cannot create output file"); + if (dup(fd_in) != STDIN_FILENO) + perr("error in I/O redirection"); + + if (dup(fd_out) != STDOUT_FILENO) + perr("error in I/O redirection"); + + if (dup(fd_out) != STDERR_FILENO) + perr("error in I/O redirection"); - write_string(fd_out, "Subject: Output from your job "); - write_string(fd_out, filename); - write_string(fd_out, "\n\n"); - fstat(fd_out, &buf); - size = buf.st_size; + close(fd_in); + close(fd_out); + if (chdir(ATJOB_DIR) < 0) + perr("cannot chdir to " ATJOB_DIR); - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); + queue = *filename; - pid = fork(); - if (pid < 0) - perr("Error in fork"); - else if (pid == 0) { - char *nul = NULL; - char **nenvp = &nul; + PRIV_START - /* - * Set up things for the child; we want standard input from - * the input file, and standard output and error sent to - * our output file. - */ + nice(tolower(queue) - 'a'); + + if (initgroups(pentry->pw_name,pentry->pw_gid)) + perr("cannot delete saved userids"); - if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0) - perr("Error in lseek"); + if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0) + perr("cannot change group"); - if (dup(fd_in) != STDIN_FILENO) - perr("Error in I/O redirection"); + if (setlogin(pentry->pw_name)) + perr("cannot set login name"); - if (dup(fd_out) != STDOUT_FILENO) - perr("Error in I/O redirection"); + if (setuid(uid) < 0 || seteuid(uid) < 0) + perr("cannot set user id"); - if (dup(fd_out) != STDERR_FILENO) - perr("Error in I/O redirection"); + if (chdir(pentry->pw_dir)) + chdir("/"); - close(fd_in); - close(fd_out); - if (chdir(_PATH_ATJOBS) < 0) - perr("Cannot chdir to " _PATH_ATJOBS); + if(execle("/bin/sh","sh",(char *) NULL, nenvp) != 0) + perr("exec failed for /bin/sh"); - queue = *filename; + PRIV_END + } + /* We're the parent. Let's wait. + */ + close(fd_in); + close(fd_out); + waitpid(pid, (int *) NULL, 0); + + /* Send mail. Unlink the output file first, so it is deleted after + * the run. + */ + stat(filename, &buf); + if (open(filename, O_RDONLY) != STDIN_FILENO) + perr("open of jobfile failed"); + + unlink(filename); + if ((buf.st_size != size) || send_mail) + { + PRIV_START - PRIV_START + if (initgroups(pentry->pw_name,pentry->pw_gid)) + perr("cannot delete saved userids"); - if (queue > 'b') - nice(queue - 'b'); + if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0) + perr("cannot change group"); - if (setgid(gid) < 0) - perr("Cannot change group"); + if (setlogin(pentry->pw_name)) + perr("cannot set login name"); - if (setuid(uid) < 0) - perr("Cannot set user id"); + if (setuid(uid) < 0 || seteuid(uid) < 0) + perr("cannot set user id"); + if (chdir(pentry->pw_dir)) chdir("/"); - if (execle("/bin/sh", "sh", (char *) NULL, nenvp) != 0) - perr("Exec failed"); +#if 1 + execl(_PATH_SENDMAIL, "sendmail", "-F", "Atrun Service", + "-odi", "-oem", + mailname, (char *) NULL); +#else + execl(MAIL_CMD, MAIL_CMD, mailname, (char *) NULL); +#endif + perr("exec failed for mail command"); - PRIV_END - } - /* We're the parent. Let's wait. */ - close(fd_in); - close(fd_out); - waitpid(pid, (int *) NULL, 0); - - stat(filename, &buf); - if ((buf.st_size != size) || send_mail) { - /* Fork off a child for sending mail */ - pid = fork(); - if (pid < 0) - perr("Fork failed"); - else if (pid == 0) { - if (open(filename, O_RDONLY) != STDIN_FILENO) - perr("Cannot reopen output file"); - - execl(_PATH_SENDMAIL, _PATH_SENDMAIL, mailname, - (char *) NULL); - perr("Exec failed"); - } - waitpid(pid, (int *) NULL, 0); - } - unlink(filename); - exit(EXIT_SUCCESS); + PRIV_END + } + exit(EXIT_SUCCESS); } /* Global functions */ +/* Needed in gloadavg.c */ +void +perr(const char *a) +{ + if (debug) + { + warn("%s", a); + } + else + syslog(LOG_ERR, "%s: %m", a); + + exit(EXIT_FAILURE); +} + int -main(argc, argv) - int argc; - char *argv[]; +main(int argc, char *argv[]) { - /* - * Browse through _PATH_ATJOBS, checking all the jobfiles wether - * they should be executed and or deleted. The queue is coded into - * the first byte of the job filename, the date (in minutes since - * Eon) as a hex number in the following eight bytes, followed by - * a dot and a serial number. A file which has not been executed - * yet is denoted by its execute - bit set. For those files which - * are to be executed, run_file() is called, which forks off a - * child which takes care of I/O redirection, forks off another - * child for execution and yet another one, optionally, for sending - * mail. Files which already have run are removed during the - * next invocation. +/* Browse through ATJOB_DIR, checking all the jobfiles wether they should + * be executed and or deleted. The queue is coded into the first byte of + * the job filename, the date (in minutes since Eon) as a hex number in the + * following eight bytes, followed by a dot and a serial number. A file + * which has not been executed yet is denoted by its execute - bit set. + * For those files which are to be executed, run_file() is called, which forks + * off a child which takes care of I/O redirection, forks off another child + * for execution and yet another one, optionally, for sending mail. + * Files which already have run are removed during the next invocation. + */ + DIR *spool; + struct dirent *dirent; + struct stat buf; + unsigned long ctm; + unsigned long jobno; + char queue; + time_t now, run_time; + char batch_name[] = "Z2345678901234"; + uid_t batch_uid; + gid_t batch_gid; + int c; + int run_batch; + double load_avg = LOADAVG_MX, la; + +/* We don't need root privileges all the time; running under uid and gid daemon + * is fine. + */ + + RELINQUISH_PRIVS_ROOT(DAEMON_UID, DAEMON_GID) + + openlog("atrun", LOG_PID, LOG_CRON); + + opterr = 0; + while((c=getopt(argc, argv, "dl:"))!= -1) + { + switch (c) + { + case 'l': + if (sscanf(optarg, "%lf", &load_avg) != 1) + perr("garbled option -l"); + if (load_avg <= 0.) + load_avg = LOADAVG_MX; + break; + + case 'd': + debug ++; + break; + + case '?': + default: + usage(); + } + } + + if (chdir(ATJOB_DIR) != 0) + perr("cannot change to " ATJOB_DIR); + + /* Main loop. Open spool directory for reading and look over all the + * files in there. If the filename indicates that the job should be run + * and the x bit is set, fork off a child which sets its user and group + * id to that of the files and exec a /bin/sh which executes the shell + * script. Unlink older files if they should no longer be run. For + * deletion, their r bit has to be turned on. + * + * Also, pick the oldest batch job to run, at most one per invocation of + * atrun. + */ + if ((spool = opendir(".")) == NULL) + perr("cannot read " ATJOB_DIR); + + now = time(NULL); + run_batch = 0; + batch_uid = (uid_t) -1; + batch_gid = (gid_t) -1; + + while ((dirent = readdir(spool)) != NULL) { + if (stat(dirent->d_name,&buf) != 0) + perr("cannot stat in " ATJOB_DIR); + + /* We don't want directories */ - DIR *spool; - struct dirent *dirent; - struct stat buf; - int older; - unsigned long ctm; - char queue; - - /* - * We don't need root privileges all the time; running under uid - * and gid daemon is fine. + if (!S_ISREG(buf.st_mode)) + continue; + + if (sscanf(dirent->d_name,"%c%5lx%8lx",&queue,&jobno,&ctm) != 3) + continue; + + run_time = (time_t) ctm*60; + + if ((S_IXUSR & buf.st_mode) && (run_time <=now)) { + if (isupper(queue) && (strcmp(batch_name,dirent->d_name) > 0)) { + run_batch = 1; + strncpy(batch_name, dirent->d_name, sizeof(batch_name)); + batch_uid = buf.st_uid; + batch_gid = buf.st_gid; + } + + /* The file is executable and old enough */ - - RELINQUISH_PRIVS_ROOT(0) /* it's setuid root */ - openlog("atrun", LOG_PID, LOG_CRON); - - namep = argv[0]; - if (chdir(_PATH_ATJOBS) != 0) - perr("Cannot change to " _PATH_ATJOBS); - - /* - * Main loop. Open spool directory for reading and look over all - * the files in there. If the filename indicates that the job - * should be run and the x bit is set, fork off a child which sets - * its user and group id to that of the files and exec a /bin/sh - * which executes the shell script. Unlink older files if they - * should no longer be run. For deletion, their r bit has to be - * turned on. + if (islower(queue)) + run_file(dirent->d_name, buf.st_uid, buf.st_gid); + } + /* Delete older files */ - if ((spool = opendir(".")) == NULL) - perr("Cannot read " _PATH_ATJOBS); - - while ((dirent = readdir(spool)) != NULL) { - double la; - - if (stat(dirent->d_name, &buf) != 0) - perr("Cannot stat in " _PATH_ATJOBS); - - /* We don't want directories */ - if (!S_ISREG(buf.st_mode)) - continue; - - if (sscanf(dirent->d_name, "%c%8lx", &queue, &ctm) != 2) - continue; - - if ((queue == 'b') && ((getloadavg(&la, 1) != 1) || - (la > ATRUN_MAXLOAD))) - continue; - - older = (time_t) ctm *60 <= time(NULL); - - /* The file is executable and old enough */ - if (older && (S_IXUSR & buf.st_mode)) { - /* - * Now we know we want to run the file, we can turn - * off the execute bit - */ - - PRIV_START - - if (chmod(dirent->d_name, S_IRUSR) != 0) - perr("Cannot change file permissions"); + if ((run_time < now) && !(S_IXUSR & buf.st_mode) && (S_IRUSR & buf.st_mode)) + unlink(dirent->d_name); + } + /* run the single batch file, if any + */ + if (run_batch && (getloadavg(&la, 1) == 1) && la < load_avg) + run_file(batch_name, batch_uid, batch_gid); + + closelog(); + exit(EXIT_SUCCESS); +} - PRIV_END +static void +usage(void) +{ + if (debug) + fprintf(stderr, "usage: atrun [-l load_avg] [-d]\n"); + else + syslog(LOG_ERR, "usage: atrun [-l load_avg] [-d]"); - run_file(dirent->d_name, buf.st_uid, buf.st_gid); - } - /* Delete older files */ - if (older && !(S_IXUSR & buf.st_mode) && - (S_IRUSR & buf.st_mode)) - unlink(dirent->d_name); - } - closelog(); - exit(EXIT_SUCCESS); + exit(EXIT_FAILURE); } -- 2.47.2