Annotation of src/usr.bin/at/at.c, Revision 1.29
1.29 ! millert 1: /* $OpenBSD: at.c,v 1.28 2002/05/13 18:43:53 millert Exp $ */
1.1 deraadt 2: /* $NetBSD: at.c,v 1.4 1995/03/25 18:13:31 glass Exp $ */
3:
4: /*
1.7 millert 5: * at.c : Put file into atrun queue
6: * Copyright (C) 1993, 1994 Thomas Koenig
1.1 deraadt 7: *
1.7 millert 8: * Atrun & Atq modifications
9: * Copyright (C) 1993 David Parsons
1.1 deraadt 10: *
1.29 ! millert 11: * Traditional BSD behavior and other significant modifications
! 12: * Copyright (C) 2002 Todd C. Miller
! 13: *
1.1 deraadt 14: * Redistribution and use in source and binary forms, with or without
15: * modification, are permitted provided that the following conditions
16: * are met:
17: * 1. Redistributions of source code must retain the above copyright
18: * notice, this list of conditions and the following disclaimer.
19: * 2. The name of the author(s) may not be used to endorse or promote
20: * products derived from this software without specific prior written
21: * permission.
22: *
23: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
24: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1.7 millert 26: * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
1.1 deraadt 27: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30: * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33: */
34:
1.7 millert 35: #include <sys/param.h>
1.1 deraadt 36: #include <sys/stat.h>
1.25 millert 37: #include <sys/time.h>
1.1 deraadt 38: #include <ctype.h>
39: #include <dirent.h>
1.29 ! millert 40: #include <err.h>
1.1 deraadt 41: #include <errno.h>
42: #include <fcntl.h>
1.29 ! millert 43: #include <locale.h>
1.1 deraadt 44: #include <pwd.h>
45: #include <signal.h>
46: #include <stddef.h>
47: #include <stdio.h>
48: #include <stdlib.h>
49: #include <string.h>
50: #include <time.h>
51: #include <unistd.h>
1.7 millert 52: #include <utmp.h>
53:
54: #if (MAXLOGNAME-1) > UT_NAMESIZE
55: #define LOGNAMESIZE UT_NAMESIZE
56: #else
57: #define LOGNAMESIZE (MAXLOGNAME-1)
58: #endif
1.1 deraadt 59:
60: #include "at.h"
61: #include "panic.h"
62: #include "parsetime.h"
1.7 millert 63: #include "perm.h"
1.1 deraadt 64: #include "pathnames.h"
65: #define MAIN
66: #include "privs.h"
67:
68: #define ALARMC 10 /* Number of seconds to wait for timeout */
1.29 ! millert 69: #define TIMESIZE 50 /* Size of buffer passed to strftime() */
1.1 deraadt 70:
71: #ifndef lint
1.29 ! millert 72: static const char rcsid[] = "$OpenBSD: at.c,v 1.28 2002/05/13 18:43:53 millert Exp $";
1.1 deraadt 73: #endif
74:
1.29 ! millert 75: /* Variables to remove from the job's environment. */
1.1 deraadt 76: char *no_export[] =
77: {
1.28 millert 78: "TERM", "TERMCAP", "DISPLAY", "_", "SHELLOPTS", "BASH_VERSINFO",
79: "EUID", "GROUPS", "PPID", "UID", "SSH_AUTH_SOCK", "SSH_AGENT_PID",
1.1 deraadt 80: };
1.7 millert 81:
1.27 millert 82: int program = AT; /* default program mode */
1.29 ! millert 83: char atfile[PATH_MAX]; /* path to the at spool file */
! 84: int fcreated; /* whether or not we created the file yet */
! 85: char *atinput = NULL; /* where to get input from */
1.1 deraadt 86: char atqueue = 0; /* which queue to examine for jobs (atq) */
1.29 ! millert 87: char vflag = 0; /* show completed but unremoved jobs (atq) */
! 88: char force = 0; /* suppress errors (atrm) */
! 89: char interactive = 0; /* interactive mode (atrm) */
! 90: static int send_mail = 0; /* whether we are sending mail */
1.7 millert 91:
1.21 millert 92: static void sigc(int);
93: static void alarmc(int);
94: static void writefile(time_t, char);
1.29 ! millert 95: static void list_jobs(int, char **, int, int);
1.25 millert 96: static time_t ttime(const char *);
1.1 deraadt 97:
98: static void
1.26 millert 99: sigc(int signo)
1.1 deraadt 100: {
1.7 millert 101: /* If the user presses ^C, remove the spool file and exit. */
1.1 deraadt 102: if (fcreated) {
1.26 millert 103: PRIV_START;
1.7 millert 104: (void)unlink(atfile);
1.26 millert 105: PRIV_END;
1.1 deraadt 106: }
107:
1.20 deraadt 108: _exit(EXIT_FAILURE);
1.1 deraadt 109: }
110:
111: static void
1.26 millert 112: alarmc(int signo)
1.1 deraadt 113: {
1.20 deraadt 114: char buf[1024];
115:
1.7 millert 116: /* Time out after some seconds. */
1.25 millert 117: strlcpy(buf, __progname, sizeof(buf));
1.20 deraadt 118: strlcat(buf, ": File locking timed out\n", sizeof(buf));
119: write(STDERR_FILENO, buf, strlen(buf));
120: if (fcreated) {
1.26 millert 121: PRIV_START;
1.20 deraadt 122: unlink(atfile);
1.26 millert 123: PRIV_END;
1.20 deraadt 124: }
125: _exit(EXIT_FAILURE);
1.1 deraadt 126: }
127:
1.29 ! millert 128: static int
! 129: newjob(time_t runtimer, int queue)
! 130: {
! 131: int fd, i;
1.1 deraadt 132:
1.7 millert 133: /*
1.29 ! millert 134: * If we have a collision, try shifting the time by up to
! 135: * two minutes. Perhaps it would be better to try different
! 136: * queues instead...
1.7 millert 137: */
1.29 ! millert 138: for (i = 0; i < 120; i++) {
! 139: snprintf(atfile, sizeof(atfile), "%s/%ld.%c",
! 140: _PATH_ATJOBS, (long)runtimer, queue);
! 141: fd = open(atfile, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR);
! 142: if (fd >= 0)
! 143: return (fd);
! 144: }
! 145: return (-1);
1.1 deraadt 146: }
147:
1.29 ! millert 148: /*
! 149: * This does most of the work if at or batch are invoked for
! 150: * writing a job.
! 151: */
1.1 deraadt 152: static void
1.26 millert 153: writefile(time_t runtimer, char queue)
1.1 deraadt 154: {
1.29 ! millert 155: char *ap, *mailname, *shell;
1.28 millert 156: char timestr[TIMESIZE];
1.29 ! millert 157: char path[PATH_MAX];
1.1 deraadt 158: struct passwd *pass_entry;
1.28 millert 159: struct tm runtime;
1.1 deraadt 160: int fdes, lockdes, fd2;
161: FILE *fp, *fpin;
162: struct sigaction act;
163: char **atenv;
164: int ch;
165: mode_t cmask;
1.29 ! millert 166: extern char **environ;
1.1 deraadt 167:
1.7 millert 168: (void)setlocale(LC_TIME, "");
169:
1.1 deraadt 170: /*
171: * Install the signal handler for SIGINT; terminate after removing the
172: * spool file if necessary
173: */
1.15 deraadt 174: memset(&act, 0, sizeof act);
1.1 deraadt 175: act.sa_handler = sigc;
1.29 ! millert 176: sigemptyset(&act.sa_mask);
1.1 deraadt 177: act.sa_flags = 0;
178: sigaction(SIGINT, &act, NULL);
179:
1.26 millert 180: PRIV_START;
1.1 deraadt 181:
1.22 millert 182: /*
1.29 ! millert 183: * Lock the jobs dir so we don't have to worry about someone
! 184: * else grabbing a file name out from under us.
1.22 millert 185: * Set an alarm so we don't sleep forever waiting on the lock.
186: * If we don't succeed with ALARMC seconds, something is wrong...
187: */
1.29 ! millert 188: memset(&act, 0, sizeof act);
1.1 deraadt 189: act.sa_handler = alarmc;
1.29 ! millert 190: sigemptyset(&act.sa_mask);
1.1 deraadt 191: act.sa_flags = 0;
192: sigaction(SIGALRM, &act, NULL);
193: alarm(ALARMC);
1.22 millert 194: lockdes = open(_PATH_ATJOBS, O_RDONLY|O_EXLOCK, 0);
1.1 deraadt 195: alarm(0);
196:
1.22 millert 197: if (lockdes < 0)
198: perr("Cannot lock jobs dir");
199:
1.1 deraadt 200: /*
201: * Create the file. The x bit is only going to be set after it has
202: * been completely written out, to make sure it is not executed in
203: * the meantime. To make sure they do not get deleted, turn off
204: * their r bit. Yes, this is a kluge.
205: */
206: cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR);
1.29 ! millert 207: if ((fdes = newjob(runtimer, queue)) == -1)
1.1 deraadt 208: perr("Cannot create atjob file");
209:
210: if ((fd2 = dup(fdes)) < 0)
211: perr("Error in dup() of job file");
212:
1.7 millert 213: if (fchown(fd2, real_uid, real_gid) != 0)
1.1 deraadt 214: perr("Cannot give away file");
215:
1.26 millert 216: PRIV_END;
1.1 deraadt 217:
218: /*
219: * We've successfully created the file; let's set the flag so it
220: * gets removed in case of an interrupt or error.
221: */
222: fcreated = 1;
223:
224: /* Now we can release the lock, so other people can access it */
1.7 millert 225: (void)close(lockdes);
1.1 deraadt 226:
227: if ((fp = fdopen(fdes, "w")) == NULL)
228: panic("Cannot reopen atjob file");
229:
230: /*
1.18 millert 231: * Get the userid to mail to, first by trying getlogin(), which asks
232: * the kernel, then from $LOGNAME or $USER, finally from getpwuid().
1.1 deraadt 233: */
234: mailname = getlogin();
1.5 millert 235: if (mailname == NULL && (mailname = getenv("LOGNAME")) == NULL)
236: mailname = getenv("USER");
1.1 deraadt 237:
1.7 millert 238: if ((mailname == NULL) || (mailname[0] == '\0') ||
239: (strlen(mailname) > LOGNAMESIZE) || (getpwnam(mailname) == NULL)) {
240: pass_entry = getpwuid(real_uid);
1.1 deraadt 241: if (pass_entry != NULL)
242: mailname = pass_entry->pw_name;
243: }
244:
1.28 millert 245: /*
246: * Get the shell to run the job under. First check $SHELL, falling
247: * back to the user's shell in the password database or, failing
248: * that, /bin/sh.
249: */
250: if ((shell = getenv("SHELL")) == NULL || *shell == '\0') {
251: pass_entry = getpwuid(real_uid);
252: if (pass_entry != NULL && *pass_entry->pw_shell != '\0')
253: shell = pass_entry->pw_shell;
254: else
255: shell = _PATH_BSHELL;
256: }
257:
1.13 kstailey 258: if (atinput != NULL) {
1.1 deraadt 259: fpin = freopen(atinput, "r", stdin);
260: if (fpin == NULL)
261: perr("Cannot open input file");
262: }
1.7 millert 263: (void)fprintf(fp, "#!/bin/sh\n# atrun uid=%u gid=%u\n# mail %*s %d\n",
264: real_uid, real_gid, LOGNAMESIZE, mailname, send_mail);
1.1 deraadt 265:
266: /* Write out the umask at the time of invocation */
1.7 millert 267: (void)fprintf(fp, "umask %o\n", cmask);
1.1 deraadt 268:
269: /*
270: * Write out the environment. Anything that may look like a special
271: * character to the shell is quoted, except for \n, which is done
272: * with a pair of "'s. Dont't export the no_export list (such as
273: * TERM or DISPLAY) because we don't want these.
274: */
275: for (atenv = environ; *atenv != NULL; atenv++) {
276: int export = 1;
277: char *eqp;
278:
279: eqp = strchr(*atenv, '=');
1.19 millert 280: if (eqp == NULL)
1.1 deraadt 281: eqp = *atenv;
282: else {
283: int i;
284:
285: for (i = 0;i < sizeof(no_export) /
286: sizeof(no_export[0]); i++) {
287: export = export
288: && (strncmp(*atenv, no_export[i],
289: (size_t) (eqp - *atenv)) != 0);
290: }
291: eqp++;
292: }
293:
294: if (export) {
1.7 millert 295: (void)fwrite(*atenv, sizeof(char), eqp - *atenv, fp);
1.1 deraadt 296: for (ap = eqp; *ap != '\0'; ap++) {
297: if (*ap == '\n')
1.7 millert 298: (void)fprintf(fp, "\"\n\"");
1.1 deraadt 299: else {
1.7 millert 300: if (!isalnum(*ap)) {
301: switch (*ap) {
302: case '%': case '/': case '{':
303: case '[': case ']': case '=':
304: case '}': case '@': case '+':
305: case '#': case ',': case '.':
306: case ':': case '-': case '_':
307: break;
308: default:
309: (void)fputc('\\', fp);
310: break;
311: }
312: }
313: (void)fputc(*ap, fp);
1.1 deraadt 314: }
315: }
1.7 millert 316: (void)fputs("; export ", fp);
317: (void)fwrite(*atenv, sizeof(char), eqp - *atenv - 1, fp);
318: (void)fputc('\n', fp);
319: }
320: }
321: /*
322: * Cd to the directory at the time and write out all the
323: * commands the user supplies from stdin.
324: */
1.29 ! millert 325: if ((ap = getcwd(path, sizeof(path))) == NULL)
1.18 millert 326: perr("Cannot get current working directory");
1.7 millert 327: (void)fputs("cd ", fp);
1.18 millert 328: for (; *ap != '\0'; ap++) {
1.7 millert 329: if (*ap == '\n')
330: fprintf(fp, "\"\n\"");
331: else {
332: if (*ap != '/' && !isalnum(*ap))
333: (void)fputc('\\', fp);
1.1 deraadt 334:
1.7 millert 335: (void)fputc(*ap, fp);
1.1 deraadt 336: }
337: }
338: /*
1.7 millert 339: * Test cd's exit status: die if the original directory has been
340: * removed, become unreadable or whatever.
1.1 deraadt 341: */
1.29 ! millert 342: (void)fprintf(fp, " || {\n\t echo 'Execution directory inaccessible'"
! 343: " >&2\n\t exit 1\n}\n");
1.1 deraadt 344:
1.3 millert 345: if ((ch = getchar()) == EOF)
346: panic("Input error");
347:
1.28 millert 348: /* We want the job to run under the user's shell. */
349: fprintf(fp, "%s << '_END_OF_AT_JOB'\n", shell);
350:
1.3 millert 351: do {
1.7 millert 352: (void)fputc(ch, fp);
1.3 millert 353: } while ((ch = getchar()) != EOF);
1.1 deraadt 354:
1.28 millert 355: (void)fprintf(fp, "\n_END_OF_AT_JOB\n");
1.1 deraadt 356: if (ferror(fp))
357: panic("Output error");
358:
359: if (ferror(stdin))
360: panic("Input error");
361:
1.7 millert 362: (void)fclose(fp);
1.1 deraadt 363:
364: /*
365: * Set the x bit so that we're ready to start executing
366: */
367: if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
368: perr("Cannot give away file");
369:
1.7 millert 370: (void)close(fd2);
1.28 millert 371:
372: runtime = *localtime(&runtimer);
373: strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime);
374: (void)fprintf(stderr, "commands will be executed using %s\n", shell);
1.29 ! millert 375: (void)fprintf(stderr, "job %s at %s\n", &atfile[sizeof(_PATH_ATJOBS)],
! 376: timestr);
! 377: }
! 378:
! 379: /* Sort by creation time. */
! 380: static int
! 381: byctime(const void *v1, const void *v2)
! 382: {
! 383: const struct atjob *j1 = *(struct atjob **)v1;
! 384: const struct atjob *j2 = *(struct atjob **)v2;
! 385:
! 386: return (j1->ctime - j2->ctime);
! 387: }
! 388:
! 389: /* Sort by job number (and thus execution time). */
! 390: static int
! 391: byjobno(const void *v1, const void *v2)
! 392: {
! 393: const struct atjob *j1 = *(struct atjob **)v1;
! 394: const struct atjob *j2 = *(struct atjob **)v2;
! 395:
! 396: if (j1->runtimer == j2->runtimer)
! 397: return (j1->queue - j2->queue);
! 398: return (j1->runtimer - j2->runtimer);
! 399: }
! 400:
! 401: static void
! 402: print_job(struct atjob *job, int n, struct stat *st, int shortformat)
! 403: {
! 404: struct passwd *pw;
! 405: struct tm runtime;
! 406: char timestr[TIMESIZE];
! 407: static char *ranks[] = {
! 408: "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
! 409: };
! 410:
! 411: runtime = *localtime(&job->runtimer);
! 412: if (shortformat) {
! 413: strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime);
! 414: (void)printf("%ld.%c\t%s\n", (long)job->runtimer,
! 415: job->queue, timestr);
! 416: } else {
! 417: pw = getpwuid(st->st_uid);
! 418: /* Rank hack shamelessly stolen from lpq */
! 419: if (n / 10 == 1)
! 420: printf("%3d%-5s", n,"th");
! 421: else
! 422: printf("%3d%-5s", n, ranks[n % 10]);
! 423: strftime(timestr, TIMESIZE, "%b %e, %Y %R", &runtime);
! 424: (void)printf("%-21.18s%-11.8s%10ld.%c %c%s\n",
! 425: timestr, pw ? pw->pw_name : "???",
! 426: (long)job->runtimer, job->queue, job->queue,
! 427: (S_IXUSR & st->st_mode) ? "" : " (done)");
! 428: }
1.1 deraadt 429: }
430:
1.29 ! millert 431: /*
! 432: * List all of a user's jobs in the queue, by looping through
! 433: * _PATH_ATJOBS, or all jobs if we are root. If argc is > 0, argv
! 434: * contains the list of users whose jobs shall be displayed. By
! 435: * default, the list is sorted by execution date and queue. If
! 436: * csort is non-zero jobs will be sorted by creation/submission date.
! 437: */
1.1 deraadt 438: static void
1.29 ! millert 439: list_jobs(int argc, char **argv, int count_only, int csort)
1.1 deraadt 440: {
441: struct passwd *pw;
442: struct dirent *dirent;
1.29 ! millert 443: struct atjob **atjobs, *job;
! 444: struct stat stbuf;
1.1 deraadt 445: time_t runtimer;
1.29 ! millert 446: uid_t *uids;
! 447: long l;
! 448: char queue, *ep;
! 449: DIR *spool;
! 450: int i, shortformat, numjobs, maxjobs;
! 451:
! 452: if (argc) {
! 453: if ((uids = malloc(sizeof(uid_t) * argc)) == NULL)
! 454: err(EXIT_FAILURE, "malloc");
! 455:
! 456: for (i = 0; i < argc; i++) {
! 457: if ((pw = getpwnam(argv[i])) == NULL)
! 458: errx(EXIT_FAILURE,
! 459: "%s: invalid user name", argv[i]);
! 460: if (pw->pw_uid != real_uid && real_uid != 0)
! 461: errx(EXIT_FAILURE, "Only the superuser may "
! 462: "display other users' jobs");
! 463: uids[i] = pw->pw_uid;
! 464: }
! 465: } else
! 466: uids = NULL;
! 467:
! 468: shortformat = strcmp(__progname, "at") == 0;
1.1 deraadt 469:
1.26 millert 470: PRIV_START;
1.1 deraadt 471:
1.7 millert 472: if (chdir(_PATH_ATJOBS) != 0)
1.1 deraadt 473: perr2("Cannot change to ", _PATH_ATJOBS);
474:
475: if ((spool = opendir(".")) == NULL)
476: perr2("Cannot open ", _PATH_ATJOBS);
477:
1.29 ! millert 478: PRIV_END;
! 479:
! 480: if (fstat(dirfd(spool), &stbuf) != 0)
! 481: perr2("Cannot stat ", _PATH_ATJOBS);
! 482:
! 483: /*
! 484: * The directory's link count should give us a good idea
! 485: * of how many files are in it. Fudge things a little just
! 486: * in case someone adds a job or two.
! 487: */
! 488: numjobs = 0;
! 489: maxjobs = stbuf.st_nlink + 4;
! 490: atjobs = (struct atjob **)malloc(maxjobs * sizeof(struct atjob *));
! 491: if (atjobs == NULL)
! 492: err(EXIT_FAILURE, "malloc");
! 493:
! 494: /* Loop over every file in the directory. */
1.1 deraadt 495: while ((dirent = readdir(spool)) != NULL) {
1.29 ! millert 496: PRIV_START;
! 497:
! 498: if (stat(dirent->d_name, &stbuf) != 0)
1.1 deraadt 499: perr2("Cannot stat in ", _PATH_ATJOBS);
500:
1.29 ! millert 501: PRIV_END;
! 502:
1.1 deraadt 503: /*
504: * See it's a regular file and has its x bit turned on and
505: * is the user's
506: */
1.29 ! millert 507: if (!S_ISREG(stbuf.st_mode)
! 508: || ((stbuf.st_uid != real_uid) && !(real_uid == 0))
! 509: || !(S_IXUSR & stbuf.st_mode || vflag))
1.1 deraadt 510: continue;
511:
1.29 ! millert 512: l = strtol(dirent->d_name, &ep, 10);
! 513: if (*ep != '.' || !isalpha(*(ep + 1)) || *(ep + 2) != '\0' ||
! 514: l < 0 || l >= INT_MAX)
1.1 deraadt 515: continue;
1.29 ! millert 516: runtimer = (time_t)l;
! 517: queue = *(ep + 1);
1.1 deraadt 518:
519: if (atqueue && (queue != atqueue))
520: continue;
521:
1.29 ! millert 522: /* Check against specified user(s). */
! 523: if (argc) {
! 524: for (i = 0; i < argc; i++) {
! 525: if (uids[0] == stbuf.st_uid)
! 526: break;
! 527: }
! 528: if (i == argc)
! 529: continue; /* user doesn't match */
! 530: }
! 531:
! 532: if (count_only) {
! 533: numjobs++;
! 534: continue;
! 535: }
! 536:
! 537: job = (struct atjob *)malloc(sizeof(struct atjob));
! 538: if (job == NULL)
! 539: err(EXIT_FAILURE, "malloc");
! 540: job->runtimer = runtimer;
! 541: job->ctime = stbuf.st_ctime;
! 542: job->queue = queue;
! 543: if (numjobs == maxjobs) {
! 544: maxjobs *= 2;
! 545: atjobs = realloc(atjobs, maxjobs * sizeof(struct atjob *));
! 546: if (atjobs == NULL)
! 547: err(EXIT_FAILURE, "realloc");
! 548: }
! 549: atjobs[numjobs++] = job;
! 550: }
! 551: free(uids);
! 552:
! 553: if (count_only || numjobs == 0) {
! 554: if (numjobs == 0 && !shortformat)
! 555: fprintf(stderr, "no files in queue.\n");
! 556: else if (count_only)
! 557: printf("%d\n", numjobs);
! 558: free(atjobs);
! 559: return;
! 560: }
! 561:
! 562: /* Sort by job run time or by job creation time. */
! 563: qsort(atjobs, numjobs, sizeof(struct atjob *),
! 564: csort ? byctime : byjobno);
! 565:
! 566: if (!shortformat)
! 567: (void)puts(" Rank Execution Date Owner "
! 568: "Job Queue");
! 569:
! 570: for (i = 0; i < numjobs; i++) {
! 571: print_job(atjobs[i], i + 1, &stbuf, shortformat);
! 572: free(atjobs[i]);
1.1 deraadt 573: }
1.29 ! millert 574: free(atjobs);
! 575: }
! 576:
! 577: static int
! 578: rmok(int job)
! 579: {
! 580: int ch, junk;
! 581:
! 582: printf("%d: remove it? ", job);
! 583: ch = getchar();
! 584: while ((junk = getchar()) != EOF && junk != '\n')
! 585: ;
! 586: return (ch == 'y' || ch == 'Y');
1.1 deraadt 587: }
588:
1.29 ! millert 589: /*
! 590: * Loop through all jobs in _PATH_ATJOBS and display or delete ones
! 591: * that match argv (may be job or username), or all if argc == 0.
! 592: * Only the superuser may display/delete other people's jobs.
! 593: */
1.28 millert 594: static int
1.26 millert 595: process_jobs(int argc, char **argv, int what)
1.1 deraadt 596: {
1.29 ! millert 597: struct stat stbuf;
! 598: struct dirent *dirent;
! 599: struct passwd *pw;
! 600: time_t runtimer;
! 601: uid_t *uids;
! 602: char **jobs, *ep, queue;
! 603: long l;
! 604: FILE *fp;
1.7 millert 605: DIR *spool;
1.29 ! millert 606: int job_matches, jobs_len, uids_len;
! 607: int error, i, ch;
1.9 millert 608:
1.26 millert 609: PRIV_START;
1.1 deraadt 610:
1.7 millert 611: if (chdir(_PATH_ATJOBS) != 0)
1.1 deraadt 612: perr2("Cannot change to ", _PATH_ATJOBS);
613:
1.7 millert 614: if ((spool = opendir(".")) == NULL)
615: perr2("Cannot open ", _PATH_ATJOBS);
616:
1.26 millert 617: PRIV_END;
1.7 millert 618:
1.29 ! millert 619: /* Convert argv into a list of jobs and uids. */
! 620: jobs = NULL;
! 621: uids = NULL;
! 622: jobs_len = uids_len = 0;
! 623: if (argc > 0) {
! 624: if ((jobs = malloc(sizeof(char *) * argc)) == NULL ||
! 625: (uids = malloc(sizeof(uid_t) * argc)) == NULL)
! 626: err(EXIT_FAILURE, "malloc");
! 627:
! 628: for (i = 0; i < argc; i++) {
! 629: l = strtol(argv[i], &ep, 10);
! 630: if (*ep == '.' && isalpha(*(ep + 1)) &&
! 631: *(ep + 2) == '\0' && l > 0 && l < INT_MAX)
! 632: jobs[jobs_len++] = argv[i];
! 633: else if ((pw = getpwnam(argv[i])) != NULL) {
! 634: if (real_uid != pw->pw_uid && real_uid != 0)
! 635: errx(EXIT_FAILURE,
! 636: "Only the superuser may %s"
! 637: " other users' jobs", what == ATRM
! 638: ? "remove" : "print");
! 639: uids[uids_len++] = pw->pw_uid;
! 640: } else
! 641: errx(EXIT_FAILURE,
! 642: "%s: invalid user name", argv[i]);
! 643: }
! 644: }
! 645:
1.7 millert 646: /* Loop over every file in the directory */
1.28 millert 647: while ((dirent = readdir(spool)) != NULL) {
1.7 millert 648:
1.26 millert 649: PRIV_START;
1.29 ! millert 650: if (stat(dirent->d_name, &stbuf) != 0)
1.7 millert 651: perr2("Cannot stat in ", _PATH_ATJOBS);
1.26 millert 652: PRIV_END;
1.7 millert 653:
1.29 ! millert 654: if (stbuf.st_uid != real_uid && real_uid != 0)
1.7 millert 655: continue;
656:
1.29 ! millert 657: l = strtol(dirent->d_name, &ep, 10);
! 658: if (*ep != '.' || !isalpha(*(ep + 1)) || *(ep + 2) != '\0' ||
! 659: l < 0 || l >= INT_MAX)
! 660: continue;
! 661: runtimer = (time_t)l;
! 662: queue = *(ep + 1);
1.7 millert 663:
1.29 ! millert 664: /* Check runtimer against argv; argc==0 means do all. */
! 665: job_matches = (argc == 0) ? 1 : 0;
! 666: if (!job_matches) {
! 667: for (i = 0; i < jobs_len; i++) {
! 668: if (strcmp(dirent->d_name, jobs[i]) == 0) {
! 669: jobs[i] = NULL;
! 670: job_matches = 1;
! 671: break;
! 672: }
! 673: }
! 674: }
! 675: if (!job_matches) {
! 676: for (i = 0; i < uids_len; i++) {
! 677: if (uids[i] == stbuf.st_uid) {
! 678: job_matches = 1;
! 679: break;
! 680: }
! 681: }
! 682: }
! 683:
! 684: if (job_matches) {
! 685: switch (what) {
! 686: case ATRM:
! 687: PRIV_START;
! 688:
! 689: if (!interactive ||
! 690: (interactive && rmok(runtimer))) {
1.7 millert 691: if (unlink(dirent->d_name) != 0)
692: perr(dirent->d_name);
1.29 ! millert 693: if (!force && !interactive)
! 694: fprintf(stderr,
! 695: "%s removed\n",
! 696: dirent->d_name);
! 697: }
1.7 millert 698:
1.29 ! millert 699: PRIV_END;
1.7 millert 700:
1.29 ! millert 701: break;
1.7 millert 702:
1.29 ! millert 703: case CAT:
! 704: PRIV_START;
1.7 millert 705:
1.29 ! millert 706: fp = fopen(dirent->d_name, "r");
1.7 millert 707:
1.29 ! millert 708: PRIV_END;
1.7 millert 709:
1.29 ! millert 710: if (!fp)
! 711: perr("Cannot open file");
1.7 millert 712:
1.29 ! millert 713: while ((ch = getc(fp)) != EOF)
! 714: putchar(ch);
1.7 millert 715:
1.29 ! millert 716: break;
1.7 millert 717:
1.29 ! millert 718: default:
! 719: errx(EXIT_FAILURE,
! 720: "Internal error, process_jobs = %d",
! 721: what);
! 722: break;
1.7 millert 723: }
1.1 deraadt 724: }
725: }
1.29 ! millert 726: for (error = 0, i = 0; i < jobs_len; i++) {
! 727: if (jobs[i] != NULL) {
! 728: if (!force)
! 729: warnx("%s: no such job", jobs[i]);
1.28 millert 730: error++;
731: }
732: }
1.29 ! millert 733: free(jobs);
! 734: free(uids);
! 735:
! 736: return (error);
1.28 millert 737: }
1.1 deraadt 738:
1.25 millert 739: #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
740:
1.29 ! millert 741: /*
! 742: * This is pretty much a copy of stime_arg1() from touch.c.
! 743: */
1.25 millert 744: static time_t
745: ttime(const char *arg)
746: {
747: struct timeval tv[2];
748: time_t now;
749: struct tm *t;
750: int yearset;
751: char *p;
752:
753: if (gettimeofday(&tv[0], NULL))
754: panic("Cannot get current time");
755:
756: /* Start with the current time. */
757: now = tv[0].tv_sec;
758: if ((t = localtime(&now)) == NULL)
759: panic("localtime");
760: /* [[CC]YY]MMDDhhmm[.SS] */
761: if ((p = strchr(arg, '.')) == NULL)
762: t->tm_sec = 0; /* Seconds defaults to 0. */
763: else {
764: if (strlen(p + 1) != 2)
765: goto terr;
766: *p++ = '\0';
767: t->tm_sec = ATOI2(p);
768: }
769:
770: yearset = 0;
771: switch(strlen(arg)) {
772: case 12: /* CCYYMMDDhhmm */
773: t->tm_year = ATOI2(arg);
774: t->tm_year *= 100;
775: yearset = 1;
776: /* FALLTHROUGH */
777: case 10: /* YYMMDDhhmm */
778: if (yearset) {
779: yearset = ATOI2(arg);
780: t->tm_year += yearset;
781: } else {
782: yearset = ATOI2(arg);
783: t->tm_year = yearset + 2000;
784: }
785: t->tm_year -= 1900; /* Convert to UNIX time. */
786: /* FALLTHROUGH */
787: case 8: /* MMDDhhmm */
788: t->tm_mon = ATOI2(arg);
789: --t->tm_mon; /* Convert from 01-12 to 00-11 */
790: t->tm_mday = ATOI2(arg);
791: t->tm_hour = ATOI2(arg);
792: t->tm_min = ATOI2(arg);
793: break;
794: default:
795: goto terr;
796: }
797:
798: t->tm_isdst = -1; /* Figure out DST. */
799: tv[0].tv_sec = tv[1].tv_sec = mktime(t);
800: if (tv[0].tv_sec != -1)
801: return (tv[0].tv_sec);
802: else
803: terr:
804: panic("out of range or illegal time specification: "
805: "[[CC]YY]MMDDhhmm[.SS]");
806: }
807:
1.1 deraadt 808: int
1.26 millert 809: main(int argc, char **argv)
1.1 deraadt 810: {
1.29 ! millert 811: time_t timer = -1;
1.7 millert 812: char queue = DEFAULT_AT_QUEUE;
813: char queue_set = 0;
1.25 millert 814: char *options = "q:f:t:bcdlmrv"; /* default options for at */
1.29 ! millert 815: int ch;
! 816: int aflag = 0;
! 817: int cflag = 0;
! 818: int nflag = 0;
1.1 deraadt 819:
1.26 millert 820: RELINQUISH_PRIVS;
1.1 deraadt 821:
822: /* find out what this program is supposed to do */
1.25 millert 823: if (strcmp(__progname, "atq") == 0) {
1.1 deraadt 824: program = ATQ;
1.29 ! millert 825: options = "cnvq:";
1.25 millert 826: } else if (strcmp(__progname, "atrm") == 0) {
1.1 deraadt 827: program = ATRM;
1.29 ! millert 828: options = "afi";
1.25 millert 829: } else if (strcmp(__progname, "batch") == 0) {
1.1 deraadt 830: program = BATCH;
1.24 millert 831: options = "f:q:mv";
1.1 deraadt 832: }
833:
834: /* process whatever options we can process */
1.29 ! millert 835: while ((ch = getopt(argc, argv, options)) != -1) {
! 836: switch (ch) {
! 837: case 'a':
! 838: aflag = 1;
! 839: break;
! 840:
! 841: case 'i':
! 842: interactive = 1;
! 843: force = 0;
! 844: break;
! 845:
! 846: case 'v': /* show completed but unremoved jobs */
! 847: /*
! 848: * This option is only useful when we are invoked
! 849: * as atq but we accept (and ignore) this flag in
! 850: * the other programs for backwards compatibility.
! 851: */
! 852: vflag = 1;
1.1 deraadt 853: break;
854:
855: case 'm': /* send mail when job is complete */
856: send_mail = 1;
857: break;
858:
859: case 'f':
1.29 ! millert 860: if (program == ATRM) {
! 861: force = 1;
! 862: interactive = 0;
! 863: } else
! 864: atinput = optarg;
1.1 deraadt 865: break;
866:
867: case 'q': /* specify queue */
868: if (strlen(optarg) > 1)
869: usage();
870:
871: atqueue = queue = *optarg;
1.7 millert 872: if (!(islower(queue) || isupper(queue)))
1.1 deraadt 873: usage();
1.7 millert 874:
875: queue_set = 1;
876: break;
877:
1.25 millert 878: case 'd': /* for backwards compatibility */
879: case 'r':
1.7 millert 880: program = ATRM;
1.24 millert 881: options = "";
1.7 millert 882: break;
883:
1.25 millert 884: case 't':
885: timer = ttime(optarg);
886: break;
887:
1.7 millert 888: case 'l':
889: program = ATQ;
1.29 ! millert 890: options = "cnvq:";
1.7 millert 891: break;
892:
893: case 'b':
894: program = BATCH;
1.24 millert 895: options = "f:q:mv";
1.7 millert 896: break;
897:
898: case 'c':
1.29 ! millert 899: if (program == ATQ) {
! 900: cflag = 1;
! 901: } else {
! 902: program = CAT;
! 903: options = "";
! 904: }
! 905: break;
! 906:
! 907: case 'n':
! 908: nflag = 1;
1.1 deraadt 909: break;
910:
911: default:
912: usage();
913: break;
914: }
1.29 ! millert 915: }
! 916: argc -= optind;
! 917: argv += optind;
1.7 millert 918:
1.16 mickey 919: if (!check_permission())
920: errx(EXIT_FAILURE, "You do not have permission to use %s.",
1.25 millert 921: __progname);
1.7 millert 922:
1.1 deraadt 923: /* select our program */
924: switch (program) {
925: case ATQ:
1.29 ! millert 926: list_jobs(argc, argv, nflag, cflag);
1.1 deraadt 927: break;
928:
929: case ATRM:
1.7 millert 930: case CAT:
1.29 ! millert 931: if ((aflag && argc) || (!aflag && !argc))
1.10 millert 932: usage();
1.28 millert 933: exit(process_jobs(argc, argv, program));
1.1 deraadt 934: break;
935:
936: case AT:
1.25 millert 937: /* Time may have been specified via the -t flag. */
1.29 ! millert 938: if (timer == -1)
1.25 millert 939: timer = parsetime(argc, argv);
1.1 deraadt 940: writefile(timer, queue);
941: break;
942:
943: case BATCH:
1.7 millert 944: if (queue_set)
945: queue = toupper(queue);
946: else
947: queue = DEFAULT_BATCH_QUEUE;
948:
1.29 ! millert 949: if (argc > 0)
1.7 millert 950: timer = parsetime(argc, argv);
951: else
952: timer = time(NULL);
953:
954: writefile(timer, queue);
1.1 deraadt 955: break;
956:
957: default:
958: panic("Internal error");
959: break;
960: }
961: exit(EXIT_SUCCESS);
962: }