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