Annotation of src/usr.bin/at/at.c, Revision 1.3
1.3 ! millert 1: /* $OpenBSD: at.c,v 1.2 1996/06/26 05:31:27 deraadt Exp $ */
1.1 deraadt 2: /* $NetBSD: at.c,v 1.4 1995/03/25 18:13:31 glass Exp $ */
3:
4: /*
5: * at.c : Put file into atrun queue
6: * Copyright (C) 1993 Thomas Koenig
7: *
8: * Atrun & Atq modifications
9: * Copyright (C) 1993 David Parsons
10: * All rights reserved.
11: *
12: * Redistribution and use in source and binary forms, with or without
13: * modification, are permitted provided that the following conditions
14: * are met:
15: * 1. Redistributions of source code must retain the above copyright
16: * notice, this list of conditions and the following disclaimer.
17: * 2. The name of the author(s) may not be used to endorse or promote
18: * products derived from this software without specific prior written
19: * permission.
20: *
21: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
22: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28: * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31: */
32:
33: #define _USE_BSD 1
34:
35: /* System Headers */
36: #include <sys/types.h>
37: #include <sys/stat.h>
38: #include <sys/wait.h>
39: #include <ctype.h>
40: #include <dirent.h>
41: #include <errno.h>
42: #include <fcntl.h>
43: #include <pwd.h>
44: #include <signal.h>
45: #include <stddef.h>
46: #include <stdio.h>
47: #include <stdlib.h>
48: #include <string.h>
49: #include <time.h>
50: #include <unistd.h>
51:
52: /* Local headers */
53: #include "at.h"
54: #include "panic.h"
55: #include "parsetime.h"
56: #include "pathnames.h"
57: #define MAIN
58: #include "privs.h"
59:
60: /* Macros */
61: #define ALARMC 10 /* Number of seconds to wait for timeout */
62:
63: #define SIZE 255
64: #define TIMESIZE 50
65:
66: /* File scope variables */
67: #ifndef lint
1.3 ! millert 68: static char rcsid[] = "$OpenBSD: at.c,v 1.2 1996/06/26 05:31:27 deraadt Exp $";
1.1 deraadt 69: #endif
70:
71: char *no_export[] =
72: {
73: "TERM", "TERMCAP", "DISPLAY", "_"
74: };
75: static send_mail = 0;
76:
77: /* External variables */
78: extern char **environ;
79: int fcreated;
80: char *namep;
81: char atfile[FILENAME_MAX];
82:
83: char *atinput = (char *) 0; /* where to get input from */
84: char atqueue = 0; /* which queue to examine for jobs (atq) */
85: char atverify = 0; /* verify time instead of queuing job */
86:
87: /* Function declarations */
88: static void sigc __P((int signo));
89: static void alarmc __P((int signo));
90: static char *cwdname __P((void));
91: static void writefile __P((time_t runtimer, char queue));
92: static void list_jobs __P((void));
93:
94: /* Signal catching functions */
95:
96: static void
97: sigc(signo)
98: int signo;
99: {
100: /* If the user presses ^C, remove the spool file and exit
101: */
102: if (fcreated) {
103: PRIV_START
104: unlink(atfile);
105: PRIV_END
106: }
107:
108: exit(EXIT_FAILURE);
109: }
110:
111: static void
112: alarmc(signo)
113: int signo;
114: {
115: /* Time out after some seconds
116: */
117: panic("File locking timed out");
118: }
119:
120: /* Local functions */
121:
122: static char *
123: cwdname()
124: {
125: /* Read in the current directory; the name will be overwritten on
126: * subsequent calls.
127: */
128: static char *ptr = NULL;
129: static size_t size = SIZE;
130:
131: if (ptr == NULL)
132: ptr = (char *) malloc(size);
133:
134: while (1) {
135: if (ptr == NULL)
136: panic("Out of memory");
137:
138: if (getcwd(ptr, size - 1) != NULL)
139: return ptr;
140:
141: if (errno != ERANGE)
142: perr("Cannot get directory");
143:
144: free(ptr);
145: size += SIZE;
146: ptr = (char *) malloc(size);
147: }
148: }
149:
150: static void
151: writefile(runtimer, queue)
152: time_t runtimer;
153: char queue;
154: {
155: /*
156: * This does most of the work if at or batch are invoked for
157: * writing a job.
158: */
159: int i;
160: char *ap, *ppos, *mailname;
161: struct passwd *pass_entry;
162: struct stat statbuf;
163: int fdes, lockdes, fd2;
164: FILE *fp, *fpin;
165: struct sigaction act;
166: char **atenv;
167: int ch;
168: mode_t cmask;
169: struct flock lock;
170:
171: /*
172: * Install the signal handler for SIGINT; terminate after removing the
173: * spool file if necessary
174: */
175: act.sa_handler = sigc;
176: sigemptyset(&(act.sa_mask));
177: act.sa_flags = 0;
178:
179: sigaction(SIGINT, &act, NULL);
180:
181: strcpy(atfile, _PATH_ATJOBS);
182: ppos = atfile + strlen(_PATH_ATJOBS);
183:
184: /*
185: * Loop over all possible file names for running something at this
186: * particular time, see if a file is there; the first empty slot at
187: * any particular time is used. Lock the file _PATH_LOCKFILE first
188: * to make sure we're alone when doing this.
189: */
190:
191: PRIV_START
192:
193: if ((lockdes = open(_PATH_LOCKFILE, O_WRONLY | O_CREAT, 0600)) < 0)
194: perr2("Cannot open lockfile ", _PATH_LOCKFILE);
195:
196: lock.l_type = F_WRLCK;
197: lock.l_whence = SEEK_SET;
198: lock.l_start = 0;
199: lock.l_len = 0;
200:
201: act.sa_handler = alarmc;
202: sigemptyset(&(act.sa_mask));
203: act.sa_flags = 0;
204:
205: /*
206: * Set an alarm so a timeout occurs after ALARMC seconds, in case
207: * something is seriously broken.
208: */
209: sigaction(SIGALRM, &act, NULL);
210: alarm(ALARMC);
211: fcntl(lockdes, F_SETLKW, &lock);
212: alarm(0);
213:
214: for (i = 0; i < AT_MAXJOBS; i++) {
215: sprintf(ppos, "%c%8lx.%3x", queue,
216: (unsigned long) (runtimer / 60), i);
217: for (ap = ppos; *ap != '\0'; ap++)
218: if (*ap == ' ')
219: *ap = '0';
220:
221: if (stat(atfile, &statbuf) != 0) {
222: if (errno == ENOENT)
223: break;
224: else
225: perr2("Cannot access ", _PATH_ATJOBS);
226: }
227: } /* for */
228:
229: if (i >= AT_MAXJOBS)
230: panic("Too many jobs already");
231:
232: /*
233: * Create the file. The x bit is only going to be set after it has
234: * been completely written out, to make sure it is not executed in
235: * the meantime. To make sure they do not get deleted, turn off
236: * their r bit. Yes, this is a kluge.
237: */
238: cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR);
239: if ((fdes = creat(atfile, O_WRONLY)) == -1)
240: perr("Cannot create atjob file");
241:
242: if ((fd2 = dup(fdes)) < 0)
243: perr("Error in dup() of job file");
244:
245: if (fchown(fd2, real_uid, -1) != 0)
246: perr("Cannot give away file");
247:
248: PRIV_END
249:
250: /*
251: * We've successfully created the file; let's set the flag so it
252: * gets removed in case of an interrupt or error.
253: */
254: fcreated = 1;
255:
256: /* Now we can release the lock, so other people can access it */
257: lock.l_type = F_UNLCK;
258: lock.l_whence = SEEK_SET;
259: lock.l_start = 0;
260: lock.l_len = 0;
261: fcntl(lockdes, F_SETLKW, &lock);
262: close(lockdes);
263:
264: if ((fp = fdopen(fdes, "w")) == NULL)
265: panic("Cannot reopen atjob file");
266:
267: /*
268: * Get the userid to mail to, first by trying getlogin(), which
269: * reads /etc/utmp, then from LOGNAME, finally from getpwuid().
270: */
271: mailname = getlogin();
272: if (mailname == NULL)
273: mailname = getenv("LOGNAME");
274:
275: if ((mailname == NULL) || (mailname[0] == '\0')
276: || (strlen(mailname) > 8)) {
277: pass_entry = getpwuid(getuid());
278: if (pass_entry != NULL)
279: mailname = pass_entry->pw_name;
280: }
281:
282: if (atinput != (char *) NULL) {
283: fpin = freopen(atinput, "r", stdin);
284: if (fpin == NULL)
285: perr("Cannot open input file");
286: }
287: fprintf(fp, "#! /bin/sh\n# mail %8s %d\n", mailname, send_mail);
288:
289: /* Write out the umask at the time of invocation */
290: fprintf(fp, "umask %lo\n", (unsigned long) cmask);
291:
292: /*
293: * Write out the environment. Anything that may look like a special
294: * character to the shell is quoted, except for \n, which is done
295: * with a pair of "'s. Dont't export the no_export list (such as
296: * TERM or DISPLAY) because we don't want these.
297: */
298: for (atenv = environ; *atenv != NULL; atenv++) {
299: int export = 1;
300: char *eqp;
301:
302: eqp = strchr(*atenv, '=');
303: if (ap == NULL)
304: eqp = *atenv;
305: else {
306: int i;
307:
308: for (i = 0;i < sizeof(no_export) /
309: sizeof(no_export[0]); i++) {
310: export = export
311: && (strncmp(*atenv, no_export[i],
312: (size_t) (eqp - *atenv)) != 0);
313: }
314: eqp++;
315: }
316:
317: if (export) {
318: fwrite(*atenv, sizeof(char), eqp - *atenv, fp);
319: for (ap = eqp; *ap != '\0'; ap++) {
320: if (*ap == '\n')
321: fprintf(fp, "\"\n\"");
322: else {
323: if (!isalnum(*ap))
324: fputc('\\', fp);
325:
326: fputc(*ap, fp);
327: }
328: }
329: fputs("; export ", fp);
330: fwrite(*atenv, sizeof(char), eqp - *atenv - 1, fp);
331: fputc('\n', fp);
332:
333: }
334: }
335: /*
336: * Cd to the directory at the time and write out all the commands
337: * the user supplies from stdin.
338: */
339: fprintf(fp, "cd %s\n", cwdname());
340:
1.3 ! millert 341: if ((ch = getchar()) == EOF)
! 342: panic("Input error");
! 343:
! 344: do {
1.1 deraadt 345: fputc(ch, fp);
1.3 ! millert 346: } while ((ch = getchar()) != EOF);
1.1 deraadt 347:
348: fprintf(fp, "\n");
349: if (ferror(fp))
350: panic("Output error");
351:
352: if (ferror(stdin))
353: panic("Input error");
354:
355: fclose(fp);
356:
357: /*
358: * Set the x bit so that we're ready to start executing
359: */
360: if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
361: perr("Cannot give away file");
362:
363: close(fd2);
364: fprintf(stderr, "Job %s will be executed using /bin/sh\n", ppos);
365: }
366:
367: static void
368: list_jobs()
369: {
370: /*
371: * List all a user's jobs in the queue, by looping through
372: * _PATH_ATJOBS, or everybody's if we are root
373: */
374: struct passwd *pw;
375: DIR *spool;
376: struct dirent *dirent;
377: struct stat buf;
378: struct tm runtime;
379: unsigned long ctm;
380: char queue;
381: time_t runtimer;
382: char timestr[TIMESIZE];
383: int first = 1;
384:
385: PRIV_START
386:
387: if (chdir(_PATH_ATJOBS) != 0)
388: perr2("Cannot change to ", _PATH_ATJOBS);
389:
390: if ((spool = opendir(".")) == NULL)
391: perr2("Cannot open ", _PATH_ATJOBS);
392:
393: /* Loop over every file in the directory */
394: while ((dirent = readdir(spool)) != NULL) {
395: if (stat(dirent->d_name, &buf) != 0)
396: perr2("Cannot stat in ", _PATH_ATJOBS);
397:
398: /*
399: * See it's a regular file and has its x bit turned on and
400: * is the user's
401: */
402: if (!S_ISREG(buf.st_mode)
403: || ((buf.st_uid != real_uid) && !(real_uid == 0))
404: || !(S_IXUSR & buf.st_mode || atverify))
405: continue;
406:
407: if (sscanf(dirent->d_name, "%c%8lx", &queue, &ctm) != 2)
408: continue;
409:
410: if (atqueue && (queue != atqueue))
411: continue;
412:
413: runtimer = 60 * (time_t) ctm;
414: runtime = *localtime(&runtimer);
415: strftime(timestr, TIMESIZE, "%X %x", &runtime);
416: if (first) {
417: printf("Date\t\t\tOwner\tQueue\tJob#\n");
418: first = 0;
419: }
420: pw = getpwuid(buf.st_uid);
421:
422: printf("%s\t%s\t%c%s\t%s\n",
423: timestr,
424: pw ? pw->pw_name : "???",
425: queue,
426: (S_IXUSR & buf.st_mode) ? "" : "(done)",
427: dirent->d_name);
428: }
429: PRIV_END
430: }
431:
432: static void
433: delete_jobs(argc, argv)
434: int argc;
435: char **argv;
436: {
437: /* Delete every argument (job - ID) given */
438: int i;
439: struct stat buf;
440:
441: PRIV_START
442:
443: if (chdir(_PATH_ATJOBS) != 0)
444: perr2("Cannot change to ", _PATH_ATJOBS);
445:
446: for (i = optind; i < argc; i++) {
447: if (stat(argv[i], &buf) != 0)
448: perr(argv[i]);
449: if ((buf.st_uid != real_uid) && !(real_uid == 0)) {
450: fprintf(stderr, "%s: Not owner\n", argv[i]);
451: exit(EXIT_FAILURE);
452: }
453: if (unlink(argv[i]) != 0)
454: perr(argv[i]);
455: }
456: PRIV_END
457: } /* delete_jobs */
458:
459: /* Global functions */
460:
461: int
462: main(argc, argv)
463: int argc;
464: char **argv;
465: {
466: int c;
467: char queue = 'a';
468: char *pgm;
469:
470: enum {
471: ATQ, ATRM, AT, BATCH
472: }; /* what program we want to run */
473: int program = AT; /* our default program */
474: char *options = "q:f:mv"; /* default options for at */
475: time_t timer;
476:
477: RELINQUISH_PRIVS
478:
479: /* Eat any leading paths */
480: if ((pgm = strrchr(argv[0], '/')) == NULL)
481: pgm = argv[0];
482: else
483: pgm++;
484:
485: namep = pgm;
486:
487: /* find out what this program is supposed to do */
488: if (strcmp(pgm, "atq") == 0) {
489: program = ATQ;
490: options = "q:v";
491: } else if (strcmp(pgm, "atrm") == 0) {
492: program = ATRM;
493: options = "";
494: } else if (strcmp(pgm, "batch") == 0) {
495: program = BATCH;
496: options = "f:mv";
497: }
498:
499: /* process whatever options we can process */
500: opterr = 1;
501: while ((c = getopt(argc, argv, options)) != EOF)
502: switch (c) {
503: case 'v': /* verify time settings */
504: atverify = 1;
505: break;
506:
507: case 'm': /* send mail when job is complete */
508: send_mail = 1;
509: break;
510:
511: case 'f':
512: atinput = optarg;
513: break;
514:
515: case 'q': /* specify queue */
516: if (strlen(optarg) > 1)
517: usage();
518:
519: atqueue = queue = *optarg;
520: if ((!islower(queue)) || (queue > 'l'))
521: usage();
522: break;
523:
524: default:
525: usage();
526: break;
527: }
528: /* end of options eating */
529:
530: /* select our program */
531: switch (program) {
532: case ATQ:
533: list_jobs();
534: break;
535:
536: case ATRM:
537: delete_jobs(argc, argv);
538: break;
539:
540: case AT:
541: timer = parsetime(argc, argv);
542: if (atverify) {
543: struct tm *tm = localtime(&timer);
544:
545: fprintf(stderr, "%s\n", asctime(tm));
546: }
547: writefile(timer, queue);
548: break;
549:
550: case BATCH:
551: writefile(time(NULL), 'b');
552: break;
553:
554: default:
555: panic("Internal error");
556: break;
557: }
558: exit(EXIT_SUCCESS);
559: }