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