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