at.c revision 100494
110154Sache/* 27767Sache * at.c : Put file into atrun queue 37767Sache * Copyright (C) 1993, 1994 Thomas Koenig 4941Snate * 57767Sache * Atrun & Atq modifications 67767Sache * Copyright (C) 1993 David Parsons 7941Snate * 8941Snate * Redistribution and use in source and binary forms, with or without 9941Snate * modification, are permitted provided that the following conditions 10941Snate * are met: 11941Snate * 1. Redistributions of source code must retain the above copyright 12941Snate * notice, this list of conditions and the following disclaimer. 13941Snate * 2. The name of the author(s) may not be used to endorse or promote 14941Snate * products derived from this software without specific prior written 15941Snate * permission. 16941Snate * 17941Snate * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 18941Snate * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19941Snate * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2010154Sache * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 21941Snate * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22941Snate * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23941Snate * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24941Snate * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25941Snate * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26941Snate * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27941Snate */ 28941Snate 2987230Smarkm#include <sys/cdefs.h> 3087230Smarkm__FBSDID("$FreeBSD: head/usr.bin/at/at.c 100494 2002-07-22 11:32:16Z robert $"); 3187230Smarkm 32941Snate#define _USE_BSD 1 33941Snate 34941Snate/* System Headers */ 357767Sache 3691220Sbde#include <sys/param.h> 37941Snate#include <sys/stat.h> 3891220Sbde#include <sys/time.h> 39941Snate#include <sys/wait.h> 40941Snate#include <ctype.h> 41941Snate#include <dirent.h> 4254158Scharnier#include <err.h> 43941Snate#include <errno.h> 44941Snate#include <fcntl.h> 4591220Sbde#ifndef __FreeBSD__ 4691220Sbde#include <getopt.h> 4791220Sbde#endif 4891220Sbde#ifdef __FreeBSD__ 4991220Sbde#include <locale.h> 5091220Sbde#endif 51941Snate#include <pwd.h> 52941Snate#include <signal.h> 53941Snate#include <stddef.h> 54941Snate#include <stdio.h> 55941Snate#include <stdlib.h> 56941Snate#include <string.h> 57941Snate#include <time.h> 58941Snate#include <unistd.h> 59941Snate 60941Snate/* Local headers */ 617767Sache 62941Snate#include "at.h" 63941Snate#include "panic.h" 64941Snate#include "parsetime.h" 657767Sache#include "perm.h" 667767Sache 67941Snate#define MAIN 68941Snate#include "privs.h" 69941Snate 70941Snate/* Macros */ 71941Snate 7210154Sache#ifndef ATJOB_DIR 737767Sache#define ATJOB_DIR "/usr/spool/atjobs/" 747767Sache#endif 757767Sache 767767Sache#ifndef LFILE 777767Sache#define LFILE ATJOB_DIR ".lockfile" 787767Sache#endif 797767Sache 807767Sache#ifndef ATJOB_MX 817767Sache#define ATJOB_MX 255 827767Sache#endif 837767Sache 847767Sache#define ALARMC 10 /* Number of seconds to wait for timeout */ 857767Sache 86941Snate#define SIZE 255 87941Snate#define TIMESIZE 50 88941Snate 8910154Sacheenum { ATQ, ATRM, AT, BATCH, CAT }; /* what program we want to run */ 9010154Sache 91941Snate/* File scope variables */ 927767Sache 9387208Smarkmconst char *no_export[] = 94941Snate{ 957767Sache "TERM", "TERMCAP", "DISPLAY", "_" 967767Sache} ; 9746081Simpstatic int send_mail = 0; 98941Snate 99941Snate/* External variables */ 1007767Sache 101941Snateextern char **environ; 102941Snateint fcreated; 1037767Sachechar atfile[] = ATJOB_DIR "12345678901234"; 104941Snate 1057767Sachechar *atinput = (char*)0; /* where to get input from */ 106941Snatechar atqueue = 0; /* which queue to examine for jobs (atq) */ 107941Snatechar atverify = 0; /* verify time instead of queuing job */ 10882722Skrischar *namep; 109941Snate 110941Snate/* Function declarations */ 111941Snate 1127767Sachestatic void sigc(int signo); 1137767Sachestatic void alarmc(int signo); 1147767Sachestatic char *cwdname(void); 1157767Sachestatic void writefile(time_t runtimer, char queue); 11696701Stjrstatic void list_jobs(long *, int); 11787208Smarkmstatic long nextjob(void); 11889315Smikestatic time_t ttime(const char *arg); 11996701Stjrstatic int in_job_list(long, long *, int); 12096701Stjrstatic long *get_job_list(int, char *[], int *); 1217767Sache 122941Snate/* Signal catching functions */ 123941Snate 12487208Smarkmstatic void sigc(int signo __unused) 125941Snate{ 12610154Sache/* If the user presses ^C, remove the spool file and exit 127941Snate */ 1287767Sache if (fcreated) 1297767Sache { 1307767Sache PRIV_START 1317767Sache unlink(atfile); 1327767Sache PRIV_END 1337767Sache } 134941Snate 13582722Skris _exit(EXIT_FAILURE); 136941Snate} 137941Snate 13887208Smarkmstatic void alarmc(int signo __unused) 139941Snate{ 14082722Skris char buf[1024]; 14182722Skris 14282722Skris /* Time out after some seconds. */ 14382722Skris strlcpy(buf, namep, sizeof(buf)); 14482722Skris strlcat(buf, ": file locking timed out\n", sizeof(buf)); 14582722Skris write(STDERR_FILENO, buf, strlen(buf)); 14682722Skris sigc(0); 147941Snate} 148941Snate 149941Snate/* Local functions */ 150941Snate 1517767Sachestatic char *cwdname(void) 152941Snate{ 153941Snate/* Read in the current directory; the name will be overwritten on 154941Snate * subsequent calls. 155941Snate */ 1567767Sache static char *ptr = NULL; 1577767Sache static size_t size = SIZE; 158941Snate 1597767Sache if (ptr == NULL) 16080294Sobrien if ((ptr = malloc(size)) == NULL) 16180294Sobrien errx(EXIT_FAILURE, "virtual memory exhausted"); 1627767Sache 1637767Sache while (1) 1647767Sache { 165941Snate if (ptr == NULL) 16654158Scharnier panic("out of memory"); 167941Snate 1687767Sache if (getcwd(ptr, size-1) != NULL) 1697767Sache return ptr; 17010154Sache 1717767Sache if (errno != ERANGE) 17254158Scharnier perr("cannot get directory"); 17310154Sache 1747767Sache free (ptr); 1757767Sache size += SIZE; 17680294Sobrien if ((ptr = malloc(size)) == NULL) 17780294Sobrien errx(EXIT_FAILURE, "virtual memory exhausted"); 1787767Sache } 179941Snate} 180941Snate 18110154Sachestatic long 18210154Sachenextjob() 18310154Sache{ 18410154Sache long jobno; 18510154Sache FILE *fid; 18610154Sache 18710154Sache if ((fid = fopen(ATJOB_DIR ".SEQ", "r+")) != (FILE*)0) { 18810154Sache if (fscanf(fid, "%5lx", &jobno) == 1) { 18910154Sache rewind(fid); 19010154Sache jobno = (1+jobno) % 0xfffff; /* 2^20 jobs enough? */ 19110154Sache fprintf(fid, "%05lx\n", jobno); 19210154Sache } 19310154Sache else 19410154Sache jobno = EOF; 19510154Sache fclose(fid); 19610154Sache return jobno; 19710154Sache } 19810154Sache else if ((fid = fopen(ATJOB_DIR ".SEQ", "w")) != (FILE*)0) { 19910154Sache fprintf(fid, "%05lx\n", jobno = 1); 20010154Sache fclose(fid); 20110154Sache return 1; 20210154Sache } 20310154Sache return EOF; 20410154Sache} 20510154Sache 206941Snatestatic void 2077767Sachewritefile(time_t runtimer, char queue) 208941Snate{ 2097767Sache/* This does most of the work if at or batch are invoked for writing a job. 2107767Sache */ 21110154Sache long jobno; 2127767Sache char *ap, *ppos, *mailname; 2137767Sache struct passwd *pass_entry; 2147767Sache struct stat statbuf; 2157767Sache int fdes, lockdes, fd2; 2167767Sache FILE *fp, *fpin; 2177767Sache struct sigaction act; 2187767Sache char **atenv; 2197767Sache int ch; 2207767Sache mode_t cmask; 2217767Sache struct flock lock; 22210154Sache 22311760Sache#ifdef __FreeBSD__ 22411760Sache (void) setlocale(LC_TIME, ""); 22511760Sache#endif 22611760Sache 2277767Sache/* Install the signal handler for SIGINT; terminate after removing the 2287767Sache * spool file if necessary 2297767Sache */ 2307767Sache act.sa_handler = sigc; 2317767Sache sigemptyset(&(act.sa_mask)); 2327767Sache act.sa_flags = 0; 233941Snate 2347767Sache sigaction(SIGINT, &act, NULL); 235941Snate 2367767Sache ppos = atfile + strlen(ATJOB_DIR); 237941Snate 2387767Sache /* Loop over all possible file names for running something at this 2397767Sache * particular time, see if a file is there; the first empty slot at any 2407767Sache * particular time is used. Lock the file LFILE first to make sure 2417767Sache * we're alone when doing this. 2427767Sache */ 243941Snate 2447767Sache PRIV_START 245941Snate 2467767Sache if ((lockdes = open(LFILE, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR)) < 0) 24754158Scharnier perr("cannot open lockfile " LFILE); 248941Snate 2497767Sache lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; 2507767Sache lock.l_len = 0; 251941Snate 2527767Sache act.sa_handler = alarmc; 2537767Sache sigemptyset(&(act.sa_mask)); 2547767Sache act.sa_flags = 0; 255941Snate 2567767Sache /* Set an alarm so a timeout occurs after ALARMC seconds, in case 2577767Sache * something is seriously broken. 2587767Sache */ 2597767Sache sigaction(SIGALRM, &act, NULL); 2607767Sache alarm(ALARMC); 2617767Sache fcntl(lockdes, F_SETLKW, &lock); 2627767Sache alarm(0); 263941Snate 26410154Sache if ((jobno = nextjob()) == EOF) 26554158Scharnier perr("cannot generate job number"); 266941Snate 26710154Sache sprintf(ppos, "%c%5lx%8lx", queue, 26810154Sache jobno, (unsigned long) (runtimer/60)); 269941Snate 27010154Sache for(ap=ppos; *ap != '\0'; ap ++) 27110154Sache if (*ap == ' ') 27210154Sache *ap = '0'; 273941Snate 27410154Sache if (stat(atfile, &statbuf) != 0) 27510154Sache if (errno != ENOENT) 27654158Scharnier perr("cannot access " ATJOB_DIR); 27710154Sache 2787767Sache /* Create the file. The x bit is only going to be set after it has 2797767Sache * been completely written out, to make sure it is not executed in the 2807767Sache * meantime. To make sure they do not get deleted, turn off their r 2817767Sache * bit. Yes, this is a kluge. 2827767Sache */ 2837767Sache cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR); 2847767Sache if ((fdes = creat(atfile, O_WRONLY)) == -1) 28554158Scharnier perr("cannot create atjob file"); 286941Snate 2877767Sache if ((fd2 = dup(fdes)) <0) 28854158Scharnier perr("error in dup() of job file"); 289941Snate 2907767Sache if(fchown(fd2, real_uid, real_gid) != 0) 29154158Scharnier perr("cannot give away file"); 292941Snate 2937767Sache PRIV_END 294941Snate 2958112Sache /* We no longer need suid root; now we just need to be able to write 2968112Sache * to the directory, if necessary. 2978112Sache */ 2988112Sache 2998112Sache REDUCE_PRIV(DAEMON_UID, DAEMON_GID) 3008112Sache 30110154Sache /* We've successfully created the file; let's set the flag so it 3027767Sache * gets removed in case of an interrupt or error. 3037767Sache */ 3047767Sache fcreated = 1; 305941Snate 3067767Sache /* Now we can release the lock, so other people can access it 3077767Sache */ 3087767Sache lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; 3097767Sache lock.l_len = 0; 3107767Sache fcntl(lockdes, F_SETLKW, &lock); 3117767Sache close(lockdes); 312941Snate 3137767Sache if((fp = fdopen(fdes, "w")) == NULL) 31454158Scharnier panic("cannot reopen atjob file"); 315941Snate 316100494Srobert /* Get the userid to mail to, first by trying getlogin(), 317100494Srobert * then from LOGNAME, finally from getpwuid(). 3187767Sache */ 3197767Sache mailname = getlogin(); 3207767Sache if (mailname == NULL) 3217767Sache mailname = getenv("LOGNAME"); 322941Snate 32310154Sache if ((mailname == NULL) || (mailname[0] == '\0') 324100494Srobert || (strlen(mailname) >= MAXLOGNAME) || (getpwnam(mailname)==NULL)) 3257767Sache { 32610154Sache pass_entry = getpwuid(real_uid); 3277767Sache if (pass_entry != NULL) 3287767Sache mailname = pass_entry->pw_name; 3297767Sache } 330941Snate 3317767Sache if (atinput != (char *) NULL) 3327767Sache { 3337767Sache fpin = freopen(atinput, "r", stdin); 3347767Sache if (fpin == NULL) 33554158Scharnier perr("cannot open input file"); 3367767Sache } 33722873Sdavidn fprintf(fp, "#!/bin/sh\n# atrun uid=%ld gid=%ld\n# mail %*s %d\n", 338100494Srobert (long) real_uid, (long) real_gid, MAXLOGNAME - 1, mailname, 339100494Srobert send_mail); 340941Snate 3417767Sache /* Write out the umask at the time of invocation 3427767Sache */ 3437767Sache fprintf(fp, "umask %lo\n", (unsigned long) cmask); 344941Snate 3457767Sache /* Write out the environment. Anything that may look like a 3467767Sache * special character to the shell is quoted, except for \n, which is 34754158Scharnier * done with a pair of "'s. Don't export the no_export list (such 3487767Sache * as TERM or DISPLAY) because we don't want these. 3497767Sache */ 3507767Sache for (atenv= environ; *atenv != NULL; atenv++) 3517767Sache { 3527767Sache int export = 1; 3537767Sache char *eqp; 354941Snate 3557767Sache eqp = strchr(*atenv, '='); 3567767Sache if (ap == NULL) 3577767Sache eqp = *atenv; 3587767Sache else 3597767Sache { 36087208Smarkm size_t i; 3617767Sache for (i=0; i<sizeof(no_export)/sizeof(no_export[0]); i++) 3627767Sache { 3637767Sache export = export 36410154Sache && (strncmp(*atenv, no_export[i], 3657767Sache (size_t) (eqp-*atenv)) != 0); 3667767Sache } 3677767Sache eqp++; 3687767Sache } 369941Snate 3707767Sache if (export) 3717767Sache { 3727767Sache fwrite(*atenv, sizeof(char), eqp-*atenv, fp); 3737767Sache for(ap = eqp;*ap != '\0'; ap++) 3747767Sache { 3757767Sache if (*ap == '\n') 3767767Sache fprintf(fp, "\"\n\""); 3777767Sache else 3787767Sache { 37910154Sache if (!isalnum(*ap)) { 38010154Sache switch (*ap) { 38110154Sache case '%': case '/': case '{': case '[': 38210154Sache case ']': case '=': case '}': case '@': 38310154Sache case '+': case '#': case ',': case '.': 38410154Sache case ':': case '-': case '_': 38510154Sache break; 38610154Sache default: 38710154Sache fputc('\\', fp); 38810154Sache break; 38910154Sache } 39010154Sache } 3917767Sache fputc(*ap, fp); 392941Snate } 3937767Sache } 3947767Sache fputs("; export ", fp); 3957767Sache fwrite(*atenv, sizeof(char), eqp-*atenv -1, fp); 3967767Sache fputc('\n', fp); 39710154Sache 398941Snate } 39910154Sache } 4007767Sache /* Cd to the directory at the time and write out all the 4017767Sache * commands the user supplies from stdin. 4027767Sache */ 4037767Sache fprintf(fp, "cd "); 4047767Sache for (ap = cwdname(); *ap != '\0'; ap++) 4057767Sache { 4067767Sache if (*ap == '\n') 4077767Sache fprintf(fp, "\"\n\""); 4087767Sache else 4097767Sache { 4107767Sache if (*ap != '/' && !isalnum(*ap)) 4117767Sache fputc('\\', fp); 41210154Sache 4137767Sache fputc(*ap, fp); 4147767Sache } 4157767Sache } 4167767Sache /* Test cd's exit status: die if the original directory has been 4177767Sache * removed, become unreadable or whatever 4187767Sache */ 4197767Sache fprintf(fp, " || {\n\t echo 'Execution directory " 4207767Sache "inaccessible' >&2\n\t exit 1\n}\n"); 421941Snate 4227767Sache while((ch = getchar()) != EOF) 4237767Sache fputc(ch, fp); 424941Snate 4257767Sache fprintf(fp, "\n"); 4267767Sache if (ferror(fp)) 42754158Scharnier panic("output error"); 42810154Sache 4297767Sache if (ferror(stdin)) 43054158Scharnier panic("input error"); 431941Snate 4327767Sache fclose(fp); 433941Snate 4347767Sache /* Set the x bit so that we're ready to start executing 4357767Sache */ 436941Snate 4377767Sache if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0) 43854158Scharnier perr("cannot give away file"); 439941Snate 4407767Sache close(fd2); 44110154Sache fprintf(stderr, "Job %ld will be executed using /bin/sh\n", jobno); 442941Snate} 443941Snate 44496701Stjrstatic int 44596701Stjrin_job_list(long job, long *joblist, int len) 44696701Stjr{ 44796701Stjr int i; 44896701Stjr 44996701Stjr for (i = 0; i < len; i++) 45096701Stjr if (job == joblist[i]) 45196701Stjr return 1; 45296701Stjr 45396701Stjr return 0; 45496701Stjr} 45596701Stjr 456941Snatestatic void 45796701Stjrlist_jobs(long *joblist, int len) 458941Snate{ 45910154Sache /* List all a user's jobs in the queue, by looping through ATJOB_DIR, 4607767Sache * or everybody's if we are root 4617767Sache */ 4627767Sache struct passwd *pw; 4637767Sache DIR *spool; 4647767Sache struct dirent *dirent; 4657767Sache struct stat buf; 4667767Sache struct tm runtime; 4677767Sache unsigned long ctm; 4687767Sache char queue; 46910154Sache long jobno; 4707767Sache time_t runtimer; 4717767Sache char timestr[TIMESIZE]; 4727767Sache int first=1; 47340389Smckay 47440389Smckay#ifdef __FreeBSD__ 47540389Smckay (void) setlocale(LC_TIME, ""); 47640389Smckay#endif 477941Snate 4787767Sache PRIV_START 479941Snate 4807767Sache if (chdir(ATJOB_DIR) != 0) 48154158Scharnier perr("cannot change to " ATJOB_DIR); 482941Snate 4837767Sache if ((spool = opendir(".")) == NULL) 48454158Scharnier perr("cannot open " ATJOB_DIR); 485941Snate 48610154Sache /* Loop over every file in the directory 4877767Sache */ 4887767Sache while((dirent = readdir(spool)) != NULL) { 4897767Sache if (stat(dirent->d_name, &buf) != 0) 49054158Scharnier perr("cannot stat in " ATJOB_DIR); 49110154Sache 4927767Sache /* See it's a regular file and has its x bit turned on and 4937767Sache * is the user's 4947767Sache */ 4957767Sache if (!S_ISREG(buf.st_mode) 4967767Sache || ((buf.st_uid != real_uid) && ! (real_uid == 0)) 4977767Sache || !(S_IXUSR & buf.st_mode || atverify)) 4987767Sache continue; 499941Snate 50010154Sache if(sscanf(dirent->d_name, "%c%5lx%8lx", &queue, &jobno, &ctm)!=3) 5017767Sache continue; 502941Snate 50396701Stjr /* If jobs are given, only list those jobs */ 50496701Stjr if (joblist && !in_job_list(jobno, joblist, len)) 50596701Stjr continue; 50696701Stjr 5077767Sache if (atqueue && (queue != atqueue)) 5087767Sache continue; 509941Snate 5107767Sache runtimer = 60*(time_t) ctm; 5117767Sache runtime = *localtime(&runtimer); 51287208Smarkm strftime(timestr, TIMESIZE, "%+", &runtime); 5137767Sache if (first) { 51496216Skuriyama printf("Date\t\t\t\tOwner\t\tQueue\tJob#\n"); 5157767Sache first=0; 5167767Sache } 5177767Sache pw = getpwuid(buf.st_uid); 518941Snate 51996216Skuriyama printf("%s\t%-16s%c%s\t%ld\n", 52010154Sache timestr, 52110154Sache pw ? pw->pw_name : "???", 52210154Sache queue, 52310154Sache (S_IXUSR & buf.st_mode) ? "":"(done)", 52410154Sache jobno); 5257767Sache } 5267767Sache PRIV_END 527941Snate} 528941Snate 529941Snatestatic void 53010154Sacheprocess_jobs(int argc, char **argv, int what) 531941Snate{ 5327767Sache /* Delete every argument (job - ID) given 5337767Sache */ 5347767Sache int i; 5357767Sache struct stat buf; 53610154Sache DIR *spool; 53710154Sache struct dirent *dirent; 53810154Sache unsigned long ctm; 53910154Sache char queue; 54010154Sache long jobno; 541941Snate 5427767Sache PRIV_START 543941Snate 5447767Sache if (chdir(ATJOB_DIR) != 0) 54554158Scharnier perr("cannot change to " ATJOB_DIR); 5468874Srgrimes 54710154Sache if ((spool = opendir(".")) == NULL) 54854158Scharnier perr("cannot open " ATJOB_DIR); 54910154Sache 55010154Sache PRIV_END 55110154Sache 55210154Sache /* Loop over every file in the directory 55310154Sache */ 55410154Sache while((dirent = readdir(spool)) != NULL) { 55510154Sache 55610154Sache PRIV_START 55710154Sache if (stat(dirent->d_name, &buf) != 0) 55854158Scharnier perr("cannot stat in " ATJOB_DIR); 55910154Sache PRIV_END 56010154Sache 56110154Sache if(sscanf(dirent->d_name, "%c%5lx%8lx", &queue, &jobno, &ctm)!=3) 5627860Sache continue; 56310154Sache 56410154Sache for (i=optind; i < argc; i++) { 56510154Sache if (atoi(argv[i]) == jobno) { 56654158Scharnier if ((buf.st_uid != real_uid) && !(real_uid == 0)) 56754158Scharnier errx(EXIT_FAILURE, "%s: not owner", argv[i]); 56810154Sache switch (what) { 56910154Sache case ATRM: 57010154Sache 57110154Sache PRIV_START 57210154Sache 57310154Sache if (unlink(dirent->d_name) != 0) 57410154Sache perr(dirent->d_name); 57510154Sache 57610154Sache PRIV_END 57710154Sache 57810154Sache break; 57910154Sache 58010154Sache case CAT: 58110154Sache { 58210154Sache FILE *fp; 58310154Sache int ch; 58410154Sache 58510154Sache PRIV_START 58610154Sache 58710154Sache fp = fopen(dirent->d_name,"r"); 58810154Sache 58910154Sache PRIV_END 59010154Sache 59110154Sache if (!fp) { 59254158Scharnier perr("cannot open file"); 59310154Sache } 59410154Sache while((ch = getc(fp)) != EOF) { 59510154Sache putchar(ch); 59610154Sache } 59710154Sache } 59810154Sache break; 59910154Sache 60010154Sache default: 60154158Scharnier errx(EXIT_FAILURE, "internal error, process_jobs = %d", 60254158Scharnier what); 60310154Sache } 60410154Sache } 6057860Sache } 6067767Sache } 6077767Sache} /* delete_jobs */ 608941Snate 60989315Smike#define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; 61089315Smike 61189315Smikestatic time_t 61289315Smikettime(const char *arg) 61389315Smike{ 61489315Smike /* 61589315Smike * This is pretty much a copy of stime_arg1() from touch.c. I changed 61689315Smike * the return value and the argument list because it's more convenient 61789315Smike * (IMO) to do everything in one place. - Joe Halpin 61889315Smike */ 61989315Smike struct timeval tv[2]; 62089315Smike time_t now; 62189315Smike struct tm *t; 62289315Smike int yearset; 62389315Smike char *p; 62489315Smike 62589315Smike if (gettimeofday(&tv[0], NULL)) 62689315Smike panic("Cannot get current time"); 62789315Smike 62889315Smike /* Start with the current time. */ 62989315Smike now = tv[0].tv_sec; 63089315Smike if ((t = localtime(&now)) == NULL) 63189315Smike panic("localtime"); 63289315Smike /* [[CC]YY]MMDDhhmm[.SS] */ 63389315Smike if ((p = strchr(arg, '.')) == NULL) 63489315Smike t->tm_sec = 0; /* Seconds defaults to 0. */ 63589315Smike else { 63689315Smike if (strlen(p + 1) != 2) 63789315Smike goto terr; 63889315Smike *p++ = '\0'; 63989315Smike t->tm_sec = ATOI2(p); 64089315Smike } 64189315Smike 64289315Smike yearset = 0; 64389315Smike switch(strlen(arg)) { 64489315Smike case 12: /* CCYYMMDDhhmm */ 64589315Smike t->tm_year = ATOI2(arg); 64689315Smike t->tm_year *= 100; 64789315Smike yearset = 1; 64889315Smike /* FALLTHROUGH */ 64989315Smike case 10: /* YYMMDDhhmm */ 65089315Smike if (yearset) { 65189315Smike yearset = ATOI2(arg); 65289315Smike t->tm_year += yearset; 65389315Smike } else { 65489315Smike yearset = ATOI2(arg); 65589315Smike t->tm_year = yearset + 2000; 65689315Smike } 65789315Smike t->tm_year -= 1900; /* Convert to UNIX time. */ 65889315Smike /* FALLTHROUGH */ 65989315Smike case 8: /* MMDDhhmm */ 66089315Smike t->tm_mon = ATOI2(arg); 66189315Smike --t->tm_mon; /* Convert from 01-12 to 00-11 */ 66289315Smike t->tm_mday = ATOI2(arg); 66389315Smike t->tm_hour = ATOI2(arg); 66489315Smike t->tm_min = ATOI2(arg); 66589315Smike break; 66689315Smike default: 66789315Smike goto terr; 66889315Smike } 66989315Smike 67089315Smike t->tm_isdst = -1; /* Figure out DST. */ 67189315Smike tv[0].tv_sec = tv[1].tv_sec = mktime(t); 67289315Smike if (tv[0].tv_sec != -1) 67389315Smike return tv[0].tv_sec; 67489315Smike else 67589315Smiketerr: 67689315Smike panic( 67789315Smike "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); 67889315Smike} 67989315Smike 68096701Stjrstatic long * 68196701Stjrget_job_list(int argc, char *argv[], int *joblen) 68296701Stjr{ 68396701Stjr int i, len; 68496701Stjr long *joblist; 68596701Stjr char *ep; 68696701Stjr 68796701Stjr joblist = NULL; 68896701Stjr len = argc; 68996701Stjr if (len > 0) { 69096701Stjr if ((joblist = malloc(len * sizeof(*joblist))) == NULL) 69196701Stjr panic("out of memory"); 69296701Stjr 69396701Stjr for (i = 0; i < argc; i++) { 69496701Stjr errno = 0; 69596701Stjr if ((joblist[i] = strtol(argv[i], &ep, 10)) < 0 || 69696701Stjr ep == argv[i] || *ep != '\0' || errno) 69796701Stjr panic("invalid job number"); 69896701Stjr } 69996701Stjr } 70096701Stjr 70196701Stjr *joblen = len; 70296701Stjr return joblist; 70396701Stjr} 70496701Stjr 705941Snateint 7067767Sachemain(int argc, char **argv) 707941Snate{ 7087767Sache int c; 7097767Sache char queue = DEFAULT_AT_QUEUE; 7107767Sache char queue_set = 0; 7117767Sache char *pgm; 712941Snate 7137767Sache int program = AT; /* our default program */ 71489633Smike const char *options = "q:f:t:rmvldbc"; /* default options for at */ 7157767Sache time_t timer; 71696701Stjr long *joblist; 71796701Stjr int joblen; 718941Snate 71996701Stjr joblist = NULL; 72096701Stjr joblen = 0; 72189315Smike timer = -1; 7227767Sache RELINQUISH_PRIVS 723941Snate 7247767Sache /* Eat any leading paths 7257767Sache */ 7267767Sache if ((pgm = strrchr(argv[0], '/')) == NULL) 7277767Sache pgm = argv[0]; 7287767Sache else 7297767Sache pgm++; 730941Snate 73182722Skris namep = pgm; 73282722Skris 7337767Sache /* find out what this program is supposed to do 7347767Sache */ 7357767Sache if (strcmp(pgm, "atq") == 0) { 7367767Sache program = ATQ; 73789633Smike options = "q:v"; 7387767Sache } 7397767Sache else if (strcmp(pgm, "atrm") == 0) { 7407767Sache program = ATRM; 74189633Smike options = ""; 7427767Sache } 7437767Sache else if (strcmp(pgm, "batch") == 0) { 7447767Sache program = BATCH; 74589633Smike options = "f:q:mv"; 7467767Sache } 747941Snate 7487767Sache /* process whatever options we can process 7497767Sache */ 7507767Sache opterr=1; 75124360Simp while ((c=getopt(argc, argv, options)) != -1) 7527767Sache switch (c) { 7537767Sache case 'v': /* verify time settings */ 7547767Sache atverify = 1; 7557767Sache break; 756941Snate 7577767Sache case 'm': /* send mail when job is complete */ 7587767Sache send_mail = 1; 7597767Sache break; 760941Snate 7617767Sache case 'f': 7627767Sache atinput = optarg; 7637767Sache break; 76410154Sache 7657767Sache case 'q': /* specify queue */ 7667767Sache if (strlen(optarg) > 1) 7677767Sache usage(); 768941Snate 7697767Sache atqueue = queue = *optarg; 7707767Sache if (!(islower(queue)||isupper(queue))) 7717767Sache usage(); 772941Snate 7737767Sache queue_set = 1; 7747767Sache break; 775941Snate 7767767Sache case 'd': 77789315Smike warnx("-d is deprecated; use -r instead"); 77889315Smike /* fall through to 'r' */ 77989315Smike 78089315Smike case 'r': 7817767Sache if (program != AT) 7827767Sache usage(); 783941Snate 7847767Sache program = ATRM; 78589633Smike options = ""; 7867767Sache break; 787941Snate 78889315Smike case 't': 78989315Smike if (program != AT) 79089315Smike usage(); 79189315Smike timer = ttime(optarg); 79289315Smike break; 79389315Smike 7947767Sache case 'l': 7957767Sache if (program != AT) 7967767Sache usage(); 797941Snate 7987767Sache program = ATQ; 79996701Stjr options = "q:"; 8007767Sache break; 801941Snate 8027767Sache case 'b': 8037767Sache if (program != AT) 8047767Sache usage(); 805941Snate 8067767Sache program = BATCH; 80789633Smike options = "f:q:mv"; 8087767Sache break; 809941Snate 81010154Sache case 'c': 81110154Sache program = CAT; 81210154Sache options = ""; 81310154Sache break; 81410154Sache 8157767Sache default: 8167767Sache usage(); 8177767Sache break; 8187767Sache } 8197767Sache /* end of options eating 8207767Sache */ 821941Snate 8227767Sache /* select our program 8237767Sache */ 8247767Sache if(!check_permission()) 82554158Scharnier errx(EXIT_FAILURE, "you do not have permission to use this program"); 8267767Sache switch (program) { 8277767Sache case ATQ: 828941Snate 8297767Sache REDUCE_PRIV(DAEMON_UID, DAEMON_GID) 8307767Sache 83196701Stjr if (queue_set == 0) 83296701Stjr joblist = get_job_list(argc - optind, argv + optind, &joblen); 83396701Stjr list_jobs(joblist, joblen); 8347767Sache break; 8357767Sache 8367767Sache case ATRM: 8377767Sache 8387767Sache REDUCE_PRIV(DAEMON_UID, DAEMON_GID) 8397767Sache 84010154Sache process_jobs(argc, argv, ATRM); 8417767Sache break; 8427767Sache 84310154Sache case CAT: 84410154Sache 84510154Sache process_jobs(argc, argv, CAT); 84610154Sache break; 84710154Sache 8487767Sache case AT: 84989315Smike /* 85089315Smike * If timer is > -1, then the user gave the time with -t. In that 85189315Smike * case, it's already been set. If not, set it now. 85289315Smike */ 85389315Smike if (timer == -1) 85489315Smike timer = parsetime(argc, argv); 85589315Smike 8567767Sache if (atverify) 8577767Sache { 8587767Sache struct tm *tm = localtime(&timer); 8597767Sache fprintf(stderr, "%s\n", asctime(tm)); 860941Snate } 8617767Sache writefile(timer, queue); 8627767Sache break; 8637767Sache 8647767Sache case BATCH: 8657767Sache if (queue_set) 8667767Sache queue = toupper(queue); 8677767Sache else 8687767Sache queue = DEFAULT_BATCH_QUEUE; 8697767Sache 8707767Sache if (argc > optind) 8717767Sache timer = parsetime(argc, argv); 8727767Sache else 8737767Sache timer = time(NULL); 87410154Sache 8757767Sache if (atverify) 8767767Sache { 8777767Sache struct tm *tm = localtime(&timer); 8787767Sache fprintf(stderr, "%s\n", asctime(tm)); 8797767Sache } 8807767Sache 8817767Sache writefile(timer, queue); 8827767Sache break; 8837767Sache 8847767Sache default: 88554158Scharnier panic("internal error"); 8867767Sache break; 8877767Sache } 8887767Sache exit(EXIT_SUCCESS); 889941Snate} 890