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