1 /* $NetBSD: at.c,v 1.14 2000/04/23 18:11:21 mjl 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.14 2000/04/23 18:11:21 mjl 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 (eqp == 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:mvldbrVc"; /* 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 case 'r': 641 if (program != AT) 642 usage(); 643 644 program = ATRM; 645 options = "V"; 646 break; 647 648 case 'l': 649 if (program != AT) 650 usage(); 651 652 program = ATQ; 653 options = "q:vV"; 654 break; 655 656 case 'b': 657 if (program != AT) 658 usage(); 659 660 program = BATCH; 661 options = "f:q:mvV"; 662 break; 663 664 case 'V': 665 disp_version = 1; 666 break; 667 668 case 'c': 669 program = CAT; 670 options = ""; 671 break; 672 673 default: 674 usage(); 675 break; 676 } 677 /* end of options eating */ 678 679 if (disp_version) 680 (void)fprintf(stderr, "%s version %.1f\n", namep, AT_VERSION); 681 682 if (!check_permission()) { 683 (void)fprintf(stderr, "You do not have permission to use %s.\n", 684 namep); 685 exit(EXIT_FAILURE); 686 } 687 688 /* select our program */ 689 switch (program) { 690 case ATQ: 691 if (optind != argc) 692 usage(); 693 list_jobs(); 694 break; 695 696 case ATRM: 697 case CAT: 698 if (optind == argc) 699 usage(); 700 process_jobs(argc, argv, program); 701 break; 702 703 case AT: 704 timer = parsetime(argc, argv); 705 if (atverify) { 706 struct tm *tm = localtime(&timer); 707 (void)fprintf(stderr, "%s\n", asctime(tm)); 708 } 709 writefile(timer, queue); 710 break; 711 712 case BATCH: 713 if (queue_set) 714 queue = toupper(queue); 715 else 716 queue = DEFAULT_BATCH_QUEUE; 717 718 if (argc > optind) 719 timer = parsetime(argc, argv); 720 else 721 timer = time(NULL); 722 723 if (atverify) { 724 struct tm *tm = localtime(&timer); 725 (void)fprintf(stderr, "%s\n", asctime(tm)); 726 } 727 728 writefile(timer, queue); 729 break; 730 731 default: 732 panic("Internal error"); 733 break; 734 } 735 exit(EXIT_SUCCESS); 736 } 737