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