1 /* $OpenBSD: at.c,v 1.64 2014/10/08 03:56:52 doug 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 * Traditional BSD behavior and other significant modifications 11 * Copyright (C) 2002-2003 Todd C. Miller 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. The name of the author(s) may not be used to endorse or promote 19 * products derived from this software without specific prior written 20 * permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #define MAIN_PROGRAM 35 36 #include "cron.h" 37 #include "at.h" 38 #include "privs.h" 39 #include <limits.h> 40 41 #define ALARMC 10 /* Number of seconds to wait for timeout */ 42 #define TIMESIZE 50 /* Size of buffer passed to strftime() */ 43 44 /* Variables to remove from the job's environment. */ 45 char *no_export[] = 46 { 47 "TERM", "TERMCAP", "DISPLAY", "_", "SHELLOPTS", "BASH_VERSINFO", 48 "EUID", "GROUPS", "PPID", "UID", "SSH_AUTH_SOCK", "SSH_AGENT_PID", 49 }; 50 51 int program = AT; /* default program mode */ 52 char atfile[MAX_FNAME]; /* path to the at spool file */ 53 int fcreated; /* whether or not we created the file yet */ 54 char atqueue = 0; /* which queue to examine for jobs (atq) */ 55 char vflag = 0; /* show completed but unremoved jobs (atq) */ 56 char force = 0; /* suppress errors (atrm) */ 57 char interactive = 0; /* interactive mode (atrm) */ 58 static int send_mail = 0; /* whether we are sending mail */ 59 60 static void sigc(int); 61 static void alarmc(int); 62 static void writefile(const char *, time_t, char); 63 static void list_jobs(int, char **, int, int); 64 static time_t ttime(char *); 65 static int check_permission(void); 66 static __dead void panic(const char *); 67 static void perr(const char *); 68 static void perr2(const char *, const char *); 69 static __dead void usage(void); 70 static int rmok(long long); 71 time_t parsetime(int, char **); 72 73 /* 74 * Something fatal has happened, print error message and exit. 75 */ 76 static __dead void 77 panic(const char *a) 78 { 79 (void)fprintf(stderr, "%s: %s\n", ProgramName, a); 80 if (fcreated) { 81 PRIV_START; 82 unlink(atfile); 83 PRIV_END; 84 } 85 86 exit(EXIT_FAILURE); 87 } 88 89 /* 90 * Two-parameter version of panic(). 91 */ 92 static __dead void 93 panic2(const char *a, const char *b) 94 { 95 (void)fprintf(stderr, "%s: %s%s\n", ProgramName, a, b); 96 if (fcreated) { 97 PRIV_START; 98 unlink(atfile); 99 PRIV_END; 100 } 101 102 exit(EXIT_FAILURE); 103 } 104 105 /* 106 * Some operating system error; print error message and exit. 107 */ 108 static __dead void 109 perr(const char *a) 110 { 111 if (!force) 112 perror(a); 113 if (fcreated) { 114 PRIV_START; 115 unlink(atfile); 116 PRIV_END; 117 } 118 119 exit(EXIT_FAILURE); 120 } 121 122 /* 123 * Two-parameter version of perr(). 124 */ 125 static __dead void 126 perr2(const char *a, const char *b) 127 { 128 if (!force) 129 (void)fputs(a, stderr); 130 perr(b); 131 } 132 133 /* ARGSUSED */ 134 static void 135 sigc(int signo) 136 { 137 /* If the user presses ^C, remove the spool file and exit. */ 138 if (fcreated) { 139 PRIV_START; 140 (void)unlink(atfile); 141 PRIV_END; 142 } 143 144 _exit(EXIT_FAILURE); 145 } 146 147 /* ARGSUSED */ 148 static void 149 alarmc(int signo) 150 { 151 /* just return */ 152 } 153 154 static int 155 newjob(time_t runtimer, int queue) 156 { 157 int fd, i; 158 159 /* 160 * If we have a collision, try shifting the time by up to 161 * two minutes. Perhaps it would be better to try different 162 * queues instead... 163 */ 164 for (i = 0; i < 120; i++) { 165 snprintf(atfile, sizeof(atfile), "%s/%lld.%c", AT_DIR, 166 (long long)runtimer, queue); 167 fd = open(atfile, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR); 168 if (fd >= 0) 169 return (fd); 170 runtimer++; 171 } 172 return (-1); 173 } 174 175 /* 176 * This does most of the work if at or batch are invoked for 177 * writing a job. 178 */ 179 static void 180 writefile(const char *cwd, time_t runtimer, char queue) 181 { 182 const char *ap; 183 char *mailname, *shell; 184 char timestr[TIMESIZE]; 185 struct passwd *pass_entry; 186 struct tm runtime; 187 int fdes, lockdes, fd2; 188 FILE *fp; 189 struct sigaction act; 190 char **atenv; 191 int ch; 192 mode_t cmask; 193 extern char **environ; 194 195 (void)setlocale(LC_TIME, ""); 196 197 /* 198 * Install the signal handler for SIGINT; terminate after removing the 199 * spool file if necessary 200 */ 201 bzero(&act, sizeof act); 202 act.sa_handler = sigc; 203 sigemptyset(&act.sa_mask); 204 act.sa_flags = 0; 205 sigaction(SIGINT, &act, NULL); 206 207 PRIV_START; 208 209 if ((lockdes = open(AT_DIR, O_RDONLY, 0)) < 0) 210 perr("Cannot open jobs dir"); 211 212 /* 213 * Lock the jobs dir so we don't have to worry about someone 214 * else grabbing a file name out from under us. 215 * Set an alarm so we don't sleep forever waiting on the lock. 216 * If we don't succeed with ALARMC seconds, something is wrong... 217 */ 218 bzero(&act, sizeof act); 219 act.sa_handler = alarmc; 220 sigemptyset(&act.sa_mask); 221 #ifdef SA_INTERRUPT 222 act.sa_flags = SA_INTERRUPT; 223 #endif 224 sigaction(SIGALRM, &act, NULL); 225 alarm(ALARMC); 226 ch = flock(lockdes, LOCK_EX); 227 alarm(0); 228 if (ch != 0) 229 panic("Unable to lock jobs dir"); 230 231 /* 232 * Create the file. The x bit is only going to be set after it has 233 * been completely written out, to make sure it is not executed in 234 * the meantime. To make sure they do not get deleted, turn off 235 * their r bit. Yes, this is a kluge. 236 */ 237 cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR); 238 if ((fdes = newjob(runtimer, queue)) == -1) 239 perr("Cannot create atjob file"); 240 241 if ((fd2 = dup(fdes)) < 0) 242 perr("Error in dup() of job file"); 243 244 if (fchown(fd2, real_uid, real_gid) != 0) 245 perr("Cannot give away file"); 246 247 PRIV_END; 248 249 /* 250 * We've successfully created the file; let's set the flag so it 251 * gets removed in case of an interrupt or error. 252 */ 253 fcreated = 1; 254 255 /* Now we can release the lock, so other people can access it */ 256 (void)close(lockdes); 257 258 if ((fp = fdopen(fdes, "w")) == NULL) 259 panic("Cannot reopen atjob file"); 260 261 /* 262 * Get the userid to mail to, first by trying getlogin(), which asks 263 * the kernel, then from $LOGNAME or $USER, finally from getpwuid(). 264 */ 265 mailname = getlogin(); 266 if (mailname == NULL && (mailname = getenv("LOGNAME")) == NULL) 267 mailname = getenv("USER"); 268 269 if ((mailname == NULL) || (mailname[0] == '\0') || 270 (strlen(mailname) > MAX_UNAME) || (getpwnam(mailname) == NULL)) { 271 pass_entry = getpwuid(real_uid); 272 if (pass_entry != NULL) 273 mailname = pass_entry->pw_name; 274 } 275 276 /* 277 * Get the shell to run the job under. First check $SHELL, falling 278 * back to the user's shell in the password database or, failing 279 * that, /bin/sh. 280 */ 281 if ((shell = getenv("SHELL")) == NULL || *shell == '\0') { 282 pass_entry = getpwuid(real_uid); 283 if (pass_entry != NULL && *pass_entry->pw_shell != '\0') 284 shell = pass_entry->pw_shell; 285 else 286 shell = _PATH_BSHELL; 287 } 288 289 (void)fprintf(fp, "#!/bin/sh\n# atrun uid=%lu gid=%lu\n# mail %*s %d\n", 290 (unsigned long)real_uid, (unsigned long)real_gid, 291 MAX_UNAME, mailname, send_mail); 292 293 /* Write out the umask at the time of invocation */ 294 (void)fprintf(fp, "umask %o\n", cmask); 295 296 /* 297 * Write out the environment. Anything that may look like a special 298 * character to the shell is quoted, except for \n, which is done 299 * with a pair of "'s. Don't export the no_export list (such as 300 * TERM or DISPLAY) because we don't want these. 301 */ 302 for (atenv = environ; *atenv != NULL; atenv++) { 303 int export = 1; 304 char *eqp; 305 306 eqp = strchr(*atenv, '='); 307 if (eqp == NULL) 308 eqp = *atenv; 309 else { 310 int i; 311 312 for (i = 0;i < sizeof(no_export) / 313 sizeof(no_export[0]); i++) { 314 export = export 315 && (strncmp(*atenv, no_export[i], 316 (size_t) (eqp - *atenv)) != 0); 317 } 318 eqp++; 319 } 320 321 if (export) { 322 (void)fputs("export ", fp); 323 (void)fwrite(*atenv, sizeof(char), eqp - *atenv, fp); 324 for (ap = eqp; *ap != '\0'; ap++) { 325 if (*ap == '\n') 326 (void)fprintf(fp, "\"\n\""); 327 else { 328 if (!isalnum((unsigned char)*ap)) { 329 switch (*ap) { 330 case '%': case '/': case '{': 331 case '[': case ']': case '=': 332 case '}': case '@': case '+': 333 case '#': case ',': case '.': 334 case ':': case '-': case '_': 335 break; 336 default: 337 (void)fputc('\\', fp); 338 break; 339 } 340 } 341 (void)fputc(*ap, fp); 342 } 343 } 344 (void)fputc('\n', fp); 345 } 346 } 347 /* 348 * Cd to the directory at the time and write out all the 349 * commands the user supplies from stdin. 350 */ 351 (void)fputs("cd ", fp); 352 for (ap = cwd; *ap != '\0'; ap++) { 353 if (*ap == '\n') 354 fprintf(fp, "\"\n\""); 355 else { 356 if (*ap != '/' && !isalnum((unsigned char)*ap)) 357 (void)fputc('\\', fp); 358 359 (void)fputc(*ap, fp); 360 } 361 } 362 /* 363 * Test cd's exit status: die if the original directory has been 364 * removed, become unreadable or whatever. 365 */ 366 (void)fprintf(fp, " || {\n\t echo 'Execution directory inaccessible'" 367 " >&2\n\t exit 1\n}\n"); 368 369 if ((ch = getchar()) == EOF) 370 panic("Input error"); 371 372 /* We want the job to run under the user's shell. */ 373 fprintf(fp, "%s << '_END_OF_AT_JOB'\n", shell); 374 375 do { 376 (void)fputc(ch, fp); 377 } while ((ch = getchar()) != EOF); 378 379 (void)fprintf(fp, "\n_END_OF_AT_JOB\n"); 380 if (ferror(fp)) 381 panic("Output error"); 382 383 if (ferror(stdin)) 384 panic("Input error"); 385 386 (void)fclose(fp); 387 388 /* 389 * Set the x bit so that we're ready to start executing 390 */ 391 if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0) 392 perr("Cannot give away file"); 393 394 (void)close(fd2); 395 396 /* Poke cron so it knows to reload the at spool. */ 397 PRIV_START; 398 poke_daemon(AT_DIR, RELOAD_AT); 399 PRIV_END; 400 401 runtime = *localtime(&runtimer); 402 strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime); 403 (void)fprintf(stderr, "commands will be executed using %s\n", shell); 404 (void)fprintf(stderr, "job %s at %s\n", &atfile[sizeof(AT_DIR)], 405 timestr); 406 } 407 408 /* Sort by creation time. */ 409 static int 410 byctime(const void *v1, const void *v2) 411 { 412 const struct atjob *j1 = *(const struct atjob **)v1; 413 const struct atjob *j2 = *(const struct atjob **)v2; 414 415 return (j1->ctime - j2->ctime); 416 } 417 418 /* Sort by job number (and thus execution time). */ 419 static int 420 byjobno(const void *v1, const void *v2) 421 { 422 const struct atjob *j1 = *(struct atjob **)v1; 423 const struct atjob *j2 = *(struct atjob **)v2; 424 425 if (j1->runtimer == j2->runtimer) 426 return (j1->queue - j2->queue); 427 return (j1->runtimer - j2->runtimer); 428 } 429 430 static void 431 print_job(struct atjob *job, int n, int shortformat) 432 { 433 struct passwd *pw; 434 struct tm runtime; 435 char timestr[TIMESIZE]; 436 static char *ranks[] = { 437 "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" 438 }; 439 440 runtime = *localtime(&job->runtimer); 441 if (shortformat) { 442 strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime); 443 (void)printf("%lld.%c\t%s\n", (long long)job->runtimer, 444 job->queue, timestr); 445 } else { 446 pw = getpwuid(job->uid); 447 /* Rank hack shamelessly stolen from lpq */ 448 if (n / 10 == 1) 449 printf("%3d%-5s", n,"th"); 450 else 451 printf("%3d%-5s", n, ranks[n % 10]); 452 strftime(timestr, TIMESIZE, "%b %e, %Y %R", &runtime); 453 (void)printf("%-21.18s%-11.8s%10lld.%c %c%s\n", 454 timestr, pw ? pw->pw_name : "???", 455 (long long)job->runtimer, job->queue, job->queue, 456 (S_IXUSR & job->mode) ? "" : " (done)"); 457 } 458 } 459 460 /* 461 * List all of a user's jobs in the queue, by looping through 462 * AT_DIR, or all jobs if we are root. If argc is > 0, argv 463 * contains the list of users whose jobs shall be displayed. By 464 * default, the list is sorted by execution date and queue. If 465 * csort is non-zero jobs will be sorted by creation/submission date. 466 */ 467 static void 468 list_jobs(int argc, char **argv, int count_only, int csort) 469 { 470 struct passwd *pw; 471 struct dirent *dirent; 472 struct atjob **atjobs, **newatjobs, *job; 473 struct stat stbuf; 474 time_t runtimer; 475 uid_t *uids; 476 char queue, *ep; 477 DIR *spool; 478 int i, shortformat; 479 size_t numjobs, maxjobs; 480 481 if (argc) { 482 if ((uids = calloc(sizeof(uid_t), argc)) == NULL) 483 panic("Insufficient virtual memory"); 484 485 for (i = 0; i < argc; i++) { 486 if ((pw = getpwnam(argv[i])) == NULL) 487 panic2(argv[i], ": invalid user name"); 488 if (pw->pw_uid != real_uid && real_uid != 0) 489 panic("Only the superuser may display other users' jobs"); 490 uids[i] = pw->pw_uid; 491 } 492 } else 493 uids = NULL; 494 495 shortformat = strcmp(ProgramName, "at") == 0; 496 497 PRIV_START; 498 499 if (chdir(AT_DIR) != 0) 500 perr2("Cannot change to ", AT_DIR); 501 502 if ((spool = opendir(".")) == NULL) 503 perr2("Cannot open ", AT_DIR); 504 505 PRIV_END; 506 507 if (fstat(dirfd(spool), &stbuf) != 0) 508 perr2("Cannot stat ", AT_DIR); 509 510 /* 511 * The directory's link count should give us a good idea 512 * of how many files are in it. Fudge things a little just 513 * in case someone adds a job or two. 514 */ 515 numjobs = 0; 516 maxjobs = stbuf.st_nlink + 4; 517 atjobs = (struct atjob **)calloc(maxjobs, sizeof(struct atjob *)); 518 if (atjobs == NULL) 519 panic("Insufficient virtual memory"); 520 521 /* Loop over every file in the directory. */ 522 while ((dirent = readdir(spool)) != NULL) { 523 PRIV_START; 524 525 if (stat(dirent->d_name, &stbuf) != 0) 526 perr2("Cannot stat in ", AT_DIR); 527 528 PRIV_END; 529 530 /* 531 * See it's a regular file and has its x bit turned on and 532 * is the user's 533 */ 534 if (!S_ISREG(stbuf.st_mode) 535 || ((stbuf.st_uid != real_uid) && !(real_uid == 0)) 536 || !(S_IXUSR & stbuf.st_mode || vflag)) 537 continue; 538 539 if (strtot(dirent->d_name, &ep, &runtimer) == -1) 540 continue; 541 if (*ep != '.' || !isalpha((unsigned char)*(ep + 1)) || 542 *(ep + 2) != '\0') 543 continue; 544 queue = *(ep + 1); 545 546 if (atqueue && (queue != atqueue)) 547 continue; 548 549 /* Check against specified user(s). */ 550 if (argc) { 551 for (i = 0; i < argc; i++) { 552 if (uids[0] == stbuf.st_uid) 553 break; 554 } 555 if (i == argc) 556 continue; /* user doesn't match */ 557 } 558 559 if (count_only) { 560 numjobs++; 561 continue; 562 } 563 564 job = (struct atjob *)malloc(sizeof(struct atjob)); 565 if (job == NULL) 566 panic("Insufficient virtual memory"); 567 job->runtimer = runtimer; 568 job->ctime = stbuf.st_ctime; 569 job->uid = stbuf.st_uid; 570 job->mode = stbuf.st_mode; 571 job->queue = queue; 572 if (numjobs == maxjobs) { 573 size_t newjobs = maxjobs * 2; 574 newatjobs = reallocarray(atjobs, newjobs, sizeof(job)); 575 if (newatjobs == NULL) 576 panic("Insufficient virtual memory"); 577 atjobs = newatjobs; 578 maxjobs = newjobs; 579 } 580 atjobs[numjobs++] = job; 581 } 582 free(uids); 583 closedir(spool); 584 585 if (count_only || numjobs == 0) { 586 if (numjobs == 0 && !shortformat) 587 fprintf(stderr, "no files in queue.\n"); 588 else if (count_only) 589 printf("%zu\n", numjobs); 590 free(atjobs); 591 return; 592 } 593 594 /* Sort by job run time or by job creation time. */ 595 qsort(atjobs, numjobs, sizeof(struct atjob *), 596 csort ? byctime : byjobno); 597 598 if (!shortformat) 599 (void)puts(" Rank Execution Date Owner " 600 "Job Queue"); 601 602 for (i = 0; i < numjobs; i++) { 603 print_job(atjobs[i], i + 1, shortformat); 604 free(atjobs[i]); 605 } 606 free(atjobs); 607 } 608 609 static int 610 rmok(long long job) 611 { 612 int ch, junk; 613 614 printf("%lld: remove it? ", job); 615 ch = getchar(); 616 while ((junk = getchar()) != EOF && junk != '\n') 617 ; 618 return (ch == 'y' || ch == 'Y'); 619 } 620 621 /* 622 * Loop through all jobs in AT_DIR and display or delete ones 623 * that match argv (may be job or username), or all if argc == 0. 624 * Only the superuser may display/delete other people's jobs. 625 */ 626 static int 627 process_jobs(int argc, char **argv, int what) 628 { 629 struct stat stbuf; 630 struct dirent *dirent; 631 struct passwd *pw; 632 time_t runtimer; 633 uid_t *uids; 634 char **jobs, *ep; 635 long l; 636 FILE *fp; 637 DIR *spool; 638 int job_matches, jobs_len, uids_len; 639 int error, i, ch, changed; 640 641 PRIV_START; 642 643 if (chdir(AT_DIR) != 0) 644 perr2("Cannot change to ", AT_DIR); 645 646 if ((spool = opendir(".")) == NULL) 647 perr2("Cannot open ", AT_DIR); 648 649 PRIV_END; 650 651 /* Convert argv into a list of jobs and uids. */ 652 jobs = NULL; 653 uids = NULL; 654 jobs_len = uids_len = 0; 655 if (argc > 0) { 656 if ((jobs = calloc(sizeof(char *), argc)) == NULL || 657 (uids = calloc(sizeof(uid_t), argc)) == NULL) 658 panic("Insufficient virtual memory"); 659 660 for (i = 0; i < argc; i++) { 661 l = strtol(argv[i], &ep, 10); 662 if (*ep == '.' && isalpha((unsigned char)*(ep + 1)) && 663 *(ep + 2) == '\0' && l > 0 && l < INT_MAX) 664 jobs[jobs_len++] = argv[i]; 665 else if ((pw = getpwnam(argv[i])) != NULL) { 666 if (real_uid != pw->pw_uid && real_uid != 0) { 667 fprintf(stderr, "%s: Only the superuser" 668 " may %s other users' jobs\n", 669 ProgramName, what == ATRM 670 ? "remove" : "view"); 671 exit(EXIT_FAILURE); 672 } 673 uids[uids_len++] = pw->pw_uid; 674 } else 675 panic2(argv[i], ": invalid user name"); 676 } 677 } 678 679 /* Loop over every file in the directory */ 680 changed = 0; 681 while ((dirent = readdir(spool)) != NULL) { 682 683 PRIV_START; 684 if (stat(dirent->d_name, &stbuf) != 0) 685 perr2("Cannot stat in ", AT_DIR); 686 PRIV_END; 687 688 if (stbuf.st_uid != real_uid && real_uid != 0) 689 continue; 690 691 if (strtot(dirent->d_name, &ep, &runtimer) == -1) 692 continue; 693 if (*ep != '.' || !isalpha((unsigned char)*(ep + 1)) || 694 *(ep + 2) != '\0') 695 continue; 696 697 /* Check runtimer against argv; argc==0 means do all. */ 698 job_matches = (argc == 0) ? 1 : 0; 699 if (!job_matches) { 700 for (i = 0; i < jobs_len; i++) { 701 if (jobs[i] != NULL && 702 strcmp(dirent->d_name, jobs[i]) == 0) { 703 jobs[i] = NULL; 704 job_matches = 1; 705 break; 706 } 707 } 708 } 709 if (!job_matches) { 710 for (i = 0; i < uids_len; i++) { 711 if (uids[i] == stbuf.st_uid) { 712 job_matches = 1; 713 break; 714 } 715 } 716 } 717 718 if (job_matches) { 719 switch (what) { 720 case ATRM: 721 PRIV_START; 722 723 if (!interactive || 724 (interactive && rmok(runtimer))) { 725 if (unlink(dirent->d_name) == 0) 726 changed = 1; 727 else 728 perr(dirent->d_name); 729 if (!force && !interactive) 730 fprintf(stderr, 731 "%s removed\n", 732 dirent->d_name); 733 } 734 735 PRIV_END; 736 737 break; 738 739 case CAT: 740 PRIV_START; 741 742 fp = fopen(dirent->d_name, "r"); 743 744 PRIV_END; 745 746 if (!fp) 747 perr("Cannot open file"); 748 749 while ((ch = getc(fp)) != EOF) 750 putchar(ch); 751 752 fclose(fp); 753 break; 754 755 default: 756 panic("Internal error"); 757 break; 758 } 759 } 760 } 761 closedir(spool); 762 763 for (error = 0, i = 0; i < jobs_len; i++) { 764 if (jobs[i] != NULL) { 765 if (!force) 766 fprintf(stderr, "%s: %s: no such job\n", 767 ProgramName, jobs[i]); 768 error++; 769 } 770 } 771 free(jobs); 772 free(uids); 773 774 /* If we modied the spool, poke cron so it knows to reload. */ 775 if (changed) { 776 PRIV_START; 777 if (chdir(CRONDIR) != 0) 778 perror(CRONDIR); 779 else 780 poke_daemon(AT_DIR, RELOAD_AT); 781 PRIV_END; 782 } 783 784 return (error); 785 } 786 787 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) 788 789 /* 790 * Adapted from date(1) 791 */ 792 static time_t 793 ttime(char *arg) 794 { 795 time_t now, then; 796 struct tm *lt; 797 int yearset; 798 char *dot, *p; 799 800 if (time(&now) == (time_t)-1 || (lt = localtime(&now)) == NULL) 801 panic("Cannot get current time"); 802 803 /* Valid date format is [[CC]YY]MMDDhhmm[.SS] */ 804 for (p = arg, dot = NULL; *p != '\0'; p++) { 805 if (*p == '.' && dot == NULL) 806 dot = p; 807 else if (!isdigit((unsigned char)*p)) 808 goto terr; 809 } 810 if (dot == NULL) 811 lt->tm_sec = 0; 812 else { 813 *dot++ = '\0'; 814 if (strlen(dot) != 2) 815 goto terr; 816 lt->tm_sec = ATOI2(dot); 817 if (lt->tm_sec > 61) /* could be leap second */ 818 goto terr; 819 } 820 821 yearset = 0; 822 switch(strlen(arg)) { 823 case 12: /* CCYYMMDDhhmm */ 824 lt->tm_year = ATOI2(arg) * 100; 825 lt->tm_year -= 1900; /* Convert to Unix time */ 826 yearset = 1; 827 /* FALLTHROUGH */ 828 case 10: /* YYMMDDhhmm */ 829 if (yearset) { 830 yearset = ATOI2(arg); 831 lt->tm_year += yearset; 832 } else { 833 yearset = ATOI2(arg); 834 /* POSIX logic: [00,68]=>20xx, [69,99]=>19xx */ 835 lt->tm_year = yearset; 836 if (yearset < 69) 837 lt->tm_year += 100; 838 } 839 /* FALLTHROUGH */ 840 case 8: /* MMDDhhmm */ 841 lt->tm_mon = ATOI2(arg); 842 if (lt->tm_mon > 12 || lt->tm_mon == 0) 843 goto terr; 844 --lt->tm_mon; /* Convert from 01-12 to 00-11 */ 845 lt->tm_mday = ATOI2(arg); 846 if (lt->tm_mday > 31 || lt->tm_mday == 0) 847 goto terr; 848 lt->tm_hour = ATOI2(arg); 849 if (lt->tm_hour > 23) 850 goto terr; 851 lt->tm_min = ATOI2(arg); 852 if (lt->tm_min > 59) 853 goto terr; 854 break; 855 default: 856 goto terr; 857 } 858 859 lt->tm_isdst = -1; /* mktime will deduce DST. */ 860 then = mktime(lt); 861 if (then == (time_t)-1) { 862 terr: 863 panic("illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); 864 } 865 if (then < now) 866 panic("cannot schedule jobs in the past"); 867 return (then); 868 } 869 870 static int 871 check_permission(void) 872 { 873 int ok; 874 uid_t uid = geteuid(); 875 struct passwd *pw; 876 877 if ((pw = getpwuid(uid)) == NULL) { 878 perror("Cannot access password database"); 879 exit(EXIT_FAILURE); 880 } 881 882 PRIV_START; 883 884 ok = allowed(pw->pw_name, AT_ALLOW, AT_DENY); 885 886 PRIV_END; 887 888 return (ok); 889 } 890 891 static __dead void 892 usage(void) 893 { 894 /* Print usage and exit. */ 895 switch (program) { 896 case AT: 897 case CAT: 898 (void)fprintf(stderr, 899 "usage: at [-bm] [-f file] [-l [user ...]] [-q queue] " 900 "-t time_arg | timespec\n" 901 " at -c | -r job ...\n"); 902 break; 903 case ATQ: 904 (void)fprintf(stderr, 905 "usage: atq [-cnv] [-q queue] [name ...]\n"); 906 break; 907 case ATRM: 908 (void)fprintf(stderr, 909 "usage: atrm [-afi] [[job] [name] ...]\n"); 910 break; 911 case BATCH: 912 (void)fprintf(stderr, 913 "usage: batch [-m] [-f file] [-q queue] [timespec]\n"); 914 break; 915 } 916 exit(EXIT_FAILURE); 917 } 918 919 int 920 main(int argc, char **argv) 921 { 922 time_t timer = -1; 923 char *atinput = NULL; /* where to get input from */ 924 char queue = DEFAULT_AT_QUEUE; 925 char queue_set = 0; 926 char *options = "q:f:t:bcdlmrv"; /* default options for at */ 927 char cwd[PATH_MAX]; 928 int ch; 929 int aflag = 0; 930 int cflag = 0; 931 int nflag = 0; 932 933 if (argc < 1) 934 usage(); 935 936 if ((ProgramName = strrchr(argv[0], '/')) != NULL) 937 ProgramName++; 938 else 939 ProgramName = argv[0]; 940 941 RELINQUISH_PRIVS; 942 943 /* find out what this program is supposed to do */ 944 if (strcmp(ProgramName, "atq") == 0) { 945 program = ATQ; 946 options = "cnvq:"; 947 } else if (strcmp(ProgramName, "atrm") == 0) { 948 program = ATRM; 949 options = "afi"; 950 } else if (strcmp(ProgramName, "batch") == 0) { 951 program = BATCH; 952 options = "f:q:mv"; 953 } 954 955 /* process whatever options we can process */ 956 while ((ch = getopt(argc, argv, options)) != -1) { 957 switch (ch) { 958 case 'a': 959 aflag = 1; 960 break; 961 962 case 'i': 963 interactive = 1; 964 force = 0; 965 break; 966 967 case 'v': /* show completed but unremoved jobs */ 968 /* 969 * This option is only useful when we are invoked 970 * as atq but we accept (and ignore) this flag in 971 * the other programs for backwards compatibility. 972 */ 973 vflag = 1; 974 break; 975 976 case 'm': /* send mail when job is complete */ 977 send_mail = 1; 978 break; 979 980 case 'f': 981 if (program == ATRM) { 982 force = 1; 983 interactive = 0; 984 } else 985 atinput = optarg; 986 break; 987 988 case 'q': /* specify queue */ 989 if (strlen(optarg) > 1) 990 usage(); 991 992 atqueue = queue = *optarg; 993 if (!(islower((unsigned char)queue) || 994 isupper((unsigned char)queue))) 995 usage(); 996 997 queue_set = 1; 998 break; 999 1000 case 'd': /* for backwards compatibility */ 1001 case 'r': 1002 program = ATRM; 1003 options = ""; 1004 break; 1005 1006 case 't': 1007 timer = ttime(optarg); 1008 break; 1009 1010 case 'l': 1011 program = ATQ; 1012 options = "cnvq:"; 1013 break; 1014 1015 case 'b': 1016 program = BATCH; 1017 options = "f:q:mv"; 1018 break; 1019 1020 case 'c': 1021 if (program == ATQ) { 1022 cflag = 1; 1023 } else { 1024 program = CAT; 1025 options = ""; 1026 } 1027 break; 1028 1029 case 'n': 1030 nflag = 1; 1031 break; 1032 1033 default: 1034 usage(); 1035 break; 1036 } 1037 } 1038 argc -= optind; 1039 argv += optind; 1040 1041 switch (program) { 1042 case AT: 1043 case BATCH: 1044 if (atinput != NULL) { 1045 if (freopen(atinput, "r", stdin) == NULL) 1046 perr("Cannot open input file"); 1047 } 1048 break; 1049 default: 1050 ; 1051 } 1052 1053 if (getcwd(cwd, sizeof(cwd)) == NULL) 1054 perr("Cannot get current working directory"); 1055 1056 set_cron_cwd(); 1057 1058 if (!check_permission()) 1059 panic("You do not have permission to use at."); 1060 1061 /* select our program */ 1062 switch (program) { 1063 case ATQ: 1064 list_jobs(argc, argv, nflag, cflag); 1065 break; 1066 1067 case ATRM: 1068 case CAT: 1069 if ((aflag && argc) || (!aflag && !argc)) 1070 usage(); 1071 exit(process_jobs(argc, argv, program)); 1072 break; 1073 1074 case AT: 1075 /* Time may have been specified via the -t flag. */ 1076 if (timer == -1) { 1077 if (argc == 0) 1078 usage(); 1079 else if ((timer = parsetime(argc, argv)) == -1) 1080 exit(EXIT_FAILURE); 1081 } 1082 writefile(cwd, timer, queue); 1083 break; 1084 1085 case BATCH: 1086 if (queue_set) 1087 queue = toupper((unsigned char)queue); 1088 else 1089 queue = DEFAULT_BATCH_QUEUE; 1090 1091 if (argc == 0) 1092 timer = time(NULL); 1093 else if ((timer = parsetime(argc, argv)) == -1) 1094 exit(EXIT_FAILURE); 1095 1096 writefile(cwd, timer, queue); 1097 break; 1098 1099 default: 1100 panic("Internal error"); 1101 break; 1102 } 1103 exit(EXIT_SUCCESS); 1104 } 1105