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