1 /* $OpenBSD: at.c,v 1.62 2013/11/25 18:02:50 deraadt 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)fwrite(*atenv, sizeof(char), eqp - *atenv, fp); 323 for (ap = eqp; *ap != '\0'; ap++) { 324 if (*ap == '\n') 325 (void)fprintf(fp, "\"\n\""); 326 else { 327 if (!isalnum((unsigned char)*ap)) { 328 switch (*ap) { 329 case '%': case '/': case '{': 330 case '[': case ']': case '=': 331 case '}': case '@': case '+': 332 case '#': case ',': case '.': 333 case ':': case '-': case '_': 334 break; 335 default: 336 (void)fputc('\\', fp); 337 break; 338 } 339 } 340 (void)fputc(*ap, fp); 341 } 342 } 343 (void)fputs("; export ", fp); 344 (void)fwrite(*atenv, sizeof(char), eqp - *atenv - 1, fp); 345 (void)fputc('\n', fp); 346 } 347 } 348 /* 349 * Cd to the directory at the time and write out all the 350 * commands the user supplies from stdin. 351 */ 352 (void)fputs("cd ", fp); 353 for (ap = cwd; *ap != '\0'; ap++) { 354 if (*ap == '\n') 355 fprintf(fp, "\"\n\""); 356 else { 357 if (*ap != '/' && !isalnum((unsigned char)*ap)) 358 (void)fputc('\\', fp); 359 360 (void)fputc(*ap, fp); 361 } 362 } 363 /* 364 * Test cd's exit status: die if the original directory has been 365 * removed, become unreadable or whatever. 366 */ 367 (void)fprintf(fp, " || {\n\t echo 'Execution directory inaccessible'" 368 " >&2\n\t exit 1\n}\n"); 369 370 if ((ch = getchar()) == EOF) 371 panic("Input error"); 372 373 /* We want the job to run under the user's shell. */ 374 fprintf(fp, "%s << '_END_OF_AT_JOB'\n", shell); 375 376 do { 377 (void)fputc(ch, fp); 378 } while ((ch = getchar()) != EOF); 379 380 (void)fprintf(fp, "\n_END_OF_AT_JOB\n"); 381 if (ferror(fp)) 382 panic("Output error"); 383 384 if (ferror(stdin)) 385 panic("Input error"); 386 387 (void)fclose(fp); 388 389 /* 390 * Set the x bit so that we're ready to start executing 391 */ 392 if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0) 393 perr("Cannot give away file"); 394 395 (void)close(fd2); 396 397 /* Poke cron so it knows to reload the at spool. */ 398 PRIV_START; 399 poke_daemon(AT_DIR, RELOAD_AT); 400 PRIV_END; 401 402 runtime = *localtime(&runtimer); 403 strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime); 404 (void)fprintf(stderr, "commands will be executed using %s\n", shell); 405 (void)fprintf(stderr, "job %s at %s\n", &atfile[sizeof(AT_DIR)], 406 timestr); 407 } 408 409 /* Sort by creation time. */ 410 static int 411 byctime(const void *v1, const void *v2) 412 { 413 const struct atjob *j1 = *(const struct atjob **)v1; 414 const struct atjob *j2 = *(const struct atjob **)v2; 415 416 return (j1->ctime - j2->ctime); 417 } 418 419 /* Sort by job number (and thus execution time). */ 420 static int 421 byjobno(const void *v1, const void *v2) 422 { 423 const struct atjob *j1 = *(struct atjob **)v1; 424 const struct atjob *j2 = *(struct atjob **)v2; 425 426 if (j1->runtimer == j2->runtimer) 427 return (j1->queue - j2->queue); 428 return (j1->runtimer - j2->runtimer); 429 } 430 431 static void 432 print_job(struct atjob *job, int n, int shortformat) 433 { 434 struct passwd *pw; 435 struct tm runtime; 436 char timestr[TIMESIZE]; 437 static char *ranks[] = { 438 "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" 439 }; 440 441 runtime = *localtime(&job->runtimer); 442 if (shortformat) { 443 strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime); 444 (void)printf("%lld.%c\t%s\n", (long long)job->runtimer, 445 job->queue, timestr); 446 } else { 447 pw = getpwuid(job->uid); 448 /* Rank hack shamelessly stolen from lpq */ 449 if (n / 10 == 1) 450 printf("%3d%-5s", n,"th"); 451 else 452 printf("%3d%-5s", n, ranks[n % 10]); 453 strftime(timestr, TIMESIZE, "%b %e, %Y %R", &runtime); 454 (void)printf("%-21.18s%-11.8s%10lld.%c %c%s\n", 455 timestr, pw ? pw->pw_name : "???", 456 (long long)job->runtimer, job->queue, job->queue, 457 (S_IXUSR & job->mode) ? "" : " (done)"); 458 } 459 } 460 461 /* 462 * List all of a user's jobs in the queue, by looping through 463 * AT_DIR, or all jobs if we are root. If argc is > 0, argv 464 * contains the list of users whose jobs shall be displayed. By 465 * default, the list is sorted by execution date and queue. If 466 * csort is non-zero jobs will be sorted by creation/submission date. 467 */ 468 static void 469 list_jobs(int argc, char **argv, int count_only, int csort) 470 { 471 struct passwd *pw; 472 struct dirent *dirent; 473 struct atjob **atjobs, **newatjobs, *job; 474 struct stat stbuf; 475 time_t runtimer; 476 uid_t *uids; 477 char queue, *ep; 478 DIR *spool; 479 int i, shortformat; 480 size_t numjobs, maxjobs; 481 482 if (argc) { 483 if ((uids = calloc(sizeof(uid_t), argc)) == NULL) 484 panic("Insufficient virtual memory"); 485 486 for (i = 0; i < argc; i++) { 487 if ((pw = getpwnam(argv[i])) == NULL) 488 panic2(argv[i], ": invalid user name"); 489 if (pw->pw_uid != real_uid && real_uid != 0) 490 panic("Only the superuser may display other users' jobs"); 491 uids[i] = pw->pw_uid; 492 } 493 } else 494 uids = NULL; 495 496 shortformat = strcmp(ProgramName, "at") == 0; 497 498 PRIV_START; 499 500 if (chdir(AT_DIR) != 0) 501 perr2("Cannot change to ", AT_DIR); 502 503 if ((spool = opendir(".")) == NULL) 504 perr2("Cannot open ", AT_DIR); 505 506 PRIV_END; 507 508 if (fstat(dirfd(spool), &stbuf) != 0) 509 perr2("Cannot stat ", AT_DIR); 510 511 /* 512 * The directory's link count should give us a good idea 513 * of how many files are in it. Fudge things a little just 514 * in case someone adds a job or two. 515 */ 516 numjobs = 0; 517 maxjobs = stbuf.st_nlink + 4; 518 atjobs = (struct atjob **)calloc(maxjobs, sizeof(struct atjob *)); 519 if (atjobs == NULL) 520 panic("Insufficient virtual memory"); 521 522 /* Loop over every file in the directory. */ 523 while ((dirent = readdir(spool)) != NULL) { 524 PRIV_START; 525 526 if (stat(dirent->d_name, &stbuf) != 0) 527 perr2("Cannot stat in ", AT_DIR); 528 529 PRIV_END; 530 531 /* 532 * See it's a regular file and has its x bit turned on and 533 * is the user's 534 */ 535 if (!S_ISREG(stbuf.st_mode) 536 || ((stbuf.st_uid != real_uid) && !(real_uid == 0)) 537 || !(S_IXUSR & stbuf.st_mode || vflag)) 538 continue; 539 540 if (strtot(dirent->d_name, &ep, &runtimer) == -1) 541 continue; 542 if (*ep != '.' || !isalpha((unsigned char)*(ep + 1)) || 543 *(ep + 2) != '\0') 544 continue; 545 queue = *(ep + 1); 546 547 if (atqueue && (queue != atqueue)) 548 continue; 549 550 /* Check against specified user(s). */ 551 if (argc) { 552 for (i = 0; i < argc; i++) { 553 if (uids[0] == stbuf.st_uid) 554 break; 555 } 556 if (i == argc) 557 continue; /* user doesn't match */ 558 } 559 560 if (count_only) { 561 numjobs++; 562 continue; 563 } 564 565 job = (struct atjob *)malloc(sizeof(struct atjob)); 566 if (job == NULL) 567 panic("Insufficient virtual memory"); 568 job->runtimer = runtimer; 569 job->ctime = stbuf.st_ctime; 570 job->uid = stbuf.st_uid; 571 job->mode = stbuf.st_mode; 572 job->queue = queue; 573 if (numjobs == maxjobs) { 574 size_t newjobs = maxjobs * 2; 575 newatjobs = realloc(atjobs, newjobs * sizeof(job)); 576 if (newatjobs == NULL) 577 panic("Insufficient virtual memory"); 578 atjobs = newatjobs; 579 maxjobs = newjobs; 580 } 581 atjobs[numjobs++] = job; 582 } 583 free(uids); 584 closedir(spool); 585 586 if (count_only || numjobs == 0) { 587 if (numjobs == 0 && !shortformat) 588 fprintf(stderr, "no files in queue.\n"); 589 else if (count_only) 590 printf("%zu\n", numjobs); 591 free(atjobs); 592 return; 593 } 594 595 /* Sort by job run time or by job creation time. */ 596 qsort(atjobs, numjobs, sizeof(struct atjob *), 597 csort ? byctime : byjobno); 598 599 if (!shortformat) 600 (void)puts(" Rank Execution Date Owner " 601 "Job Queue"); 602 603 for (i = 0; i < numjobs; i++) { 604 print_job(atjobs[i], i + 1, shortformat); 605 free(atjobs[i]); 606 } 607 free(atjobs); 608 } 609 610 static int 611 rmok(long long job) 612 { 613 int ch, junk; 614 615 printf("%lld: remove it? ", job); 616 ch = getchar(); 617 while ((junk = getchar()) != EOF && junk != '\n') 618 ; 619 return (ch == 'y' || ch == 'Y'); 620 } 621 622 /* 623 * Loop through all jobs in AT_DIR and display or delete ones 624 * that match argv (may be job or username), or all if argc == 0. 625 * Only the superuser may display/delete other people's jobs. 626 */ 627 static int 628 process_jobs(int argc, char **argv, int what) 629 { 630 struct stat stbuf; 631 struct dirent *dirent; 632 struct passwd *pw; 633 time_t runtimer; 634 uid_t *uids; 635 char **jobs, *ep; 636 long l; 637 FILE *fp; 638 DIR *spool; 639 int job_matches, jobs_len, uids_len; 640 int error, i, ch, changed; 641 642 PRIV_START; 643 644 if (chdir(AT_DIR) != 0) 645 perr2("Cannot change to ", AT_DIR); 646 647 if ((spool = opendir(".")) == NULL) 648 perr2("Cannot open ", AT_DIR); 649 650 PRIV_END; 651 652 /* Convert argv into a list of jobs and uids. */ 653 jobs = NULL; 654 uids = NULL; 655 jobs_len = uids_len = 0; 656 if (argc > 0) { 657 if ((jobs = calloc(sizeof(char *), argc)) == NULL || 658 (uids = calloc(sizeof(uid_t), argc)) == NULL) 659 panic("Insufficient virtual memory"); 660 661 for (i = 0; i < argc; i++) { 662 l = strtol(argv[i], &ep, 10); 663 if (*ep == '.' && isalpha((unsigned char)*(ep + 1)) && 664 *(ep + 2) == '\0' && l > 0 && l < INT_MAX) 665 jobs[jobs_len++] = argv[i]; 666 else if ((pw = getpwnam(argv[i])) != NULL) { 667 if (real_uid != pw->pw_uid && real_uid != 0) { 668 fprintf(stderr, "%s: Only the superuser" 669 " may %s other users' jobs\n", 670 ProgramName, what == ATRM 671 ? "remove" : "view"); 672 exit(EXIT_FAILURE); 673 } 674 uids[uids_len++] = pw->pw_uid; 675 } else 676 panic2(argv[i], ": invalid user name"); 677 } 678 } 679 680 /* Loop over every file in the directory */ 681 changed = 0; 682 while ((dirent = readdir(spool)) != NULL) { 683 684 PRIV_START; 685 if (stat(dirent->d_name, &stbuf) != 0) 686 perr2("Cannot stat in ", AT_DIR); 687 PRIV_END; 688 689 if (stbuf.st_uid != real_uid && real_uid != 0) 690 continue; 691 692 if (strtot(dirent->d_name, &ep, &runtimer) == -1) 693 continue; 694 if (*ep != '.' || !isalpha((unsigned char)*(ep + 1)) || 695 *(ep + 2) != '\0') 696 continue; 697 698 /* Check runtimer against argv; argc==0 means do all. */ 699 job_matches = (argc == 0) ? 1 : 0; 700 if (!job_matches) { 701 for (i = 0; i < jobs_len; i++) { 702 if (jobs[i] != NULL && 703 strcmp(dirent->d_name, jobs[i]) == 0) { 704 jobs[i] = NULL; 705 job_matches = 1; 706 break; 707 } 708 } 709 } 710 if (!job_matches) { 711 for (i = 0; i < uids_len; i++) { 712 if (uids[i] == stbuf.st_uid) { 713 job_matches = 1; 714 break; 715 } 716 } 717 } 718 719 if (job_matches) { 720 switch (what) { 721 case ATRM: 722 PRIV_START; 723 724 if (!interactive || 725 (interactive && rmok(runtimer))) { 726 if (unlink(dirent->d_name) == 0) 727 changed = 1; 728 else 729 perr(dirent->d_name); 730 if (!force && !interactive) 731 fprintf(stderr, 732 "%s removed\n", 733 dirent->d_name); 734 } 735 736 PRIV_END; 737 738 break; 739 740 case CAT: 741 PRIV_START; 742 743 fp = fopen(dirent->d_name, "r"); 744 745 PRIV_END; 746 747 if (!fp) 748 perr("Cannot open file"); 749 750 while ((ch = getc(fp)) != EOF) 751 putchar(ch); 752 753 fclose(fp); 754 break; 755 756 default: 757 panic("Internal error"); 758 break; 759 } 760 } 761 } 762 closedir(spool); 763 764 for (error = 0, i = 0; i < jobs_len; i++) { 765 if (jobs[i] != NULL) { 766 if (!force) 767 fprintf(stderr, "%s: %s: no such job\n", 768 ProgramName, jobs[i]); 769 error++; 770 } 771 } 772 free(jobs); 773 free(uids); 774 775 /* If we modied the spool, poke cron so it knows to reload. */ 776 if (changed) { 777 PRIV_START; 778 if (chdir(CRONDIR) != 0) 779 perror(CRONDIR); 780 else 781 poke_daemon(AT_DIR, RELOAD_AT); 782 PRIV_END; 783 } 784 785 return (error); 786 } 787 788 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) 789 790 /* 791 * Adapted from date(1) 792 */ 793 static time_t 794 ttime(char *arg) 795 { 796 time_t now, then; 797 struct tm *lt; 798 int yearset; 799 char *dot, *p; 800 801 if (time(&now) == (time_t)-1 || (lt = localtime(&now)) == NULL) 802 panic("Cannot get current time"); 803 804 /* Valid date format is [[CC]YY]MMDDhhmm[.SS] */ 805 for (p = arg, dot = NULL; *p != '\0'; p++) { 806 if (*p == '.' && dot == NULL) 807 dot = p; 808 else if (!isdigit((unsigned char)*p)) 809 goto terr; 810 } 811 if (dot == NULL) 812 lt->tm_sec = 0; 813 else { 814 *dot++ = '\0'; 815 if (strlen(dot) != 2) 816 goto terr; 817 lt->tm_sec = ATOI2(dot); 818 if (lt->tm_sec > 61) /* could be leap second */ 819 goto terr; 820 } 821 822 yearset = 0; 823 switch(strlen(arg)) { 824 case 12: /* CCYYMMDDhhmm */ 825 lt->tm_year = ATOI2(arg) * 100; 826 lt->tm_year -= 1900; /* Convert to Unix time */ 827 yearset = 1; 828 /* FALLTHROUGH */ 829 case 10: /* YYMMDDhhmm */ 830 if (yearset) { 831 yearset = ATOI2(arg); 832 lt->tm_year += yearset; 833 } else { 834 yearset = ATOI2(arg); 835 /* POSIX logic: [00,68]=>20xx, [69,99]=>19xx */ 836 lt->tm_year = yearset; 837 if (yearset < 69) 838 lt->tm_year += 100; 839 } 840 /* FALLTHROUGH */ 841 case 8: /* MMDDhhmm */ 842 lt->tm_mon = ATOI2(arg); 843 if (lt->tm_mon > 12 || lt->tm_mon == 0) 844 goto terr; 845 --lt->tm_mon; /* Convert from 01-12 to 00-11 */ 846 lt->tm_mday = ATOI2(arg); 847 if (lt->tm_mday > 31 || lt->tm_mday == 0) 848 goto terr; 849 lt->tm_hour = ATOI2(arg); 850 if (lt->tm_hour > 23) 851 goto terr; 852 lt->tm_min = ATOI2(arg); 853 if (lt->tm_min > 59) 854 goto terr; 855 break; 856 default: 857 goto terr; 858 } 859 860 lt->tm_isdst = -1; /* mktime will deduce DST. */ 861 then = mktime(lt); 862 if (then == (time_t)-1) { 863 terr: 864 panic("illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); 865 } 866 if (then < now) 867 panic("cannot schedule jobs in the past"); 868 return (then); 869 } 870 871 static int 872 check_permission(void) 873 { 874 int ok; 875 uid_t uid = geteuid(); 876 struct passwd *pw; 877 878 if ((pw = getpwuid(uid)) == NULL) { 879 perror("Cannot access password database"); 880 exit(EXIT_FAILURE); 881 } 882 883 PRIV_START; 884 885 ok = allowed(pw->pw_name, AT_ALLOW, AT_DENY); 886 887 PRIV_END; 888 889 return (ok); 890 } 891 892 static __dead void 893 usage(void) 894 { 895 /* Print usage and exit. */ 896 switch (program) { 897 case AT: 898 case CAT: 899 (void)fprintf(stderr, 900 "usage: at [-bm] [-f file] [-l [user ...]] [-q queue] " 901 "-t time_arg | timespec\n" 902 " at -c | -r job ...\n"); 903 break; 904 case ATQ: 905 (void)fprintf(stderr, 906 "usage: atq [-cnv] [-q queue] [name ...]\n"); 907 break; 908 case ATRM: 909 (void)fprintf(stderr, 910 "usage: atrm [-afi] [[job] [name] ...]\n"); 911 break; 912 case BATCH: 913 (void)fprintf(stderr, 914 "usage: batch [-m] [-f file] [-q queue] [timespec]\n"); 915 break; 916 } 917 exit(EXIT_FAILURE); 918 } 919 920 int 921 main(int argc, char **argv) 922 { 923 time_t timer = -1; 924 char *atinput = NULL; /* where to get input from */ 925 char queue = DEFAULT_AT_QUEUE; 926 char queue_set = 0; 927 char *options = "q:f:t:bcdlmrv"; /* default options for at */ 928 char cwd[PATH_MAX]; 929 int ch; 930 int aflag = 0; 931 int cflag = 0; 932 int nflag = 0; 933 934 if (argc < 1) 935 usage(); 936 937 if ((ProgramName = strrchr(argv[0], '/')) != NULL) 938 ProgramName++; 939 else 940 ProgramName = argv[0]; 941 942 RELINQUISH_PRIVS; 943 944 /* find out what this program is supposed to do */ 945 if (strcmp(ProgramName, "atq") == 0) { 946 program = ATQ; 947 options = "cnvq:"; 948 } else if (strcmp(ProgramName, "atrm") == 0) { 949 program = ATRM; 950 options = "afi"; 951 } else if (strcmp(ProgramName, "batch") == 0) { 952 program = BATCH; 953 options = "f:q:mv"; 954 } 955 956 /* process whatever options we can process */ 957 while ((ch = getopt(argc, argv, options)) != -1) { 958 switch (ch) { 959 case 'a': 960 aflag = 1; 961 break; 962 963 case 'i': 964 interactive = 1; 965 force = 0; 966 break; 967 968 case 'v': /* show completed but unremoved jobs */ 969 /* 970 * This option is only useful when we are invoked 971 * as atq but we accept (and ignore) this flag in 972 * the other programs for backwards compatibility. 973 */ 974 vflag = 1; 975 break; 976 977 case 'm': /* send mail when job is complete */ 978 send_mail = 1; 979 break; 980 981 case 'f': 982 if (program == ATRM) { 983 force = 1; 984 interactive = 0; 985 } else 986 atinput = optarg; 987 break; 988 989 case 'q': /* specify queue */ 990 if (strlen(optarg) > 1) 991 usage(); 992 993 atqueue = queue = *optarg; 994 if (!(islower((unsigned char)queue) || 995 isupper((unsigned char)queue))) 996 usage(); 997 998 queue_set = 1; 999 break; 1000 1001 case 'd': /* for backwards compatibility */ 1002 case 'r': 1003 program = ATRM; 1004 options = ""; 1005 break; 1006 1007 case 't': 1008 timer = ttime(optarg); 1009 break; 1010 1011 case 'l': 1012 program = ATQ; 1013 options = "cnvq:"; 1014 break; 1015 1016 case 'b': 1017 program = BATCH; 1018 options = "f:q:mv"; 1019 break; 1020 1021 case 'c': 1022 if (program == ATQ) { 1023 cflag = 1; 1024 } else { 1025 program = CAT; 1026 options = ""; 1027 } 1028 break; 1029 1030 case 'n': 1031 nflag = 1; 1032 break; 1033 1034 default: 1035 usage(); 1036 break; 1037 } 1038 } 1039 argc -= optind; 1040 argv += optind; 1041 1042 switch (program) { 1043 case AT: 1044 case BATCH: 1045 if (atinput != NULL) { 1046 if (freopen(atinput, "r", stdin) == NULL) 1047 perr("Cannot open input file"); 1048 } 1049 break; 1050 default: 1051 ; 1052 } 1053 1054 if (getcwd(cwd, sizeof(cwd)) == NULL) 1055 perr("Cannot get current working directory"); 1056 1057 set_cron_cwd(); 1058 1059 if (!check_permission()) 1060 panic("You do not have permission to use at."); 1061 1062 /* select our program */ 1063 switch (program) { 1064 case ATQ: 1065 list_jobs(argc, argv, nflag, cflag); 1066 break; 1067 1068 case ATRM: 1069 case CAT: 1070 if ((aflag && argc) || (!aflag && !argc)) 1071 usage(); 1072 exit(process_jobs(argc, argv, program)); 1073 break; 1074 1075 case AT: 1076 /* Time may have been specified via the -t flag. */ 1077 if (timer == -1) { 1078 if (argc == 0) 1079 usage(); 1080 else if ((timer = parsetime(argc, argv)) == -1) 1081 exit(EXIT_FAILURE); 1082 } 1083 writefile(cwd, timer, queue); 1084 break; 1085 1086 case BATCH: 1087 if (queue_set) 1088 queue = toupper((unsigned char)queue); 1089 else 1090 queue = DEFAULT_BATCH_QUEUE; 1091 1092 if (argc == 0) 1093 timer = time(NULL); 1094 else if ((timer = parsetime(argc, argv)) == -1) 1095 exit(EXIT_FAILURE); 1096 1097 writefile(cwd, timer, queue); 1098 break; 1099 1100 default: 1101 panic("Internal error"); 1102 break; 1103 } 1104 exit(EXIT_SUCCESS); 1105 } 1106