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