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