1 /* $OpenBSD: at.c,v 1.58 2011/03/03 15:06:43 millert 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 time_t parsetime(int, char **); 71 72 /* 73 * Something fatal has happened, print error message and exit. 74 */ 75 static __dead void 76 panic(const char *a) 77 { 78 (void)fprintf(stderr, "%s: %s\n", ProgramName, a); 79 if (fcreated) { 80 PRIV_START; 81 unlink(atfile); 82 PRIV_END; 83 } 84 85 exit(ERROR_EXIT); 86 } 87 88 /* 89 * Two-parameter version of panic(). 90 */ 91 static __dead void 92 panic2(const char *a, const char *b) 93 { 94 (void)fprintf(stderr, "%s: %s%s\n", ProgramName, a, b); 95 if (fcreated) { 96 PRIV_START; 97 unlink(atfile); 98 PRIV_END; 99 } 100 101 exit(ERROR_EXIT); 102 } 103 104 /* 105 * Some operating system error; print error message and exit. 106 */ 107 static __dead void 108 perr(const char *a) 109 { 110 if (!force) 111 perror(a); 112 if (fcreated) { 113 PRIV_START; 114 unlink(atfile); 115 PRIV_END; 116 } 117 118 exit(ERROR_EXIT); 119 } 120 121 /* 122 * Two-parameter version of perr(). 123 */ 124 static __dead void 125 perr2(const char *a, const char *b) 126 { 127 if (!force) 128 (void)fputs(a, stderr); 129 perr(b); 130 } 131 132 /* ARGSUSED */ 133 static void 134 sigc(int signo) 135 { 136 /* If the user presses ^C, remove the spool file and exit. */ 137 if (fcreated) { 138 PRIV_START; 139 (void)unlink(atfile); 140 PRIV_END; 141 } 142 143 _exit(ERROR_EXIT); 144 } 145 146 /* ARGSUSED */ 147 static void 148 alarmc(int signo) 149 { 150 /* just return */ 151 } 152 153 static int 154 newjob(time_t runtimer, int queue) 155 { 156 int fd, i; 157 158 /* 159 * If we have a collision, try shifting the time by up to 160 * two minutes. Perhaps it would be better to try different 161 * queues instead... 162 */ 163 for (i = 0; i < 120; i++) { 164 snprintf(atfile, sizeof(atfile), "%s/%ld.%c", AT_DIR, 165 (long)runtimer, queue); 166 fd = open(atfile, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR); 167 if (fd >= 0) 168 return (fd); 169 runtimer++; 170 } 171 return (-1); 172 } 173 174 /* 175 * This does most of the work if at or batch are invoked for 176 * writing a job. 177 */ 178 static void 179 writefile(const char *cwd, time_t runtimer, char queue) 180 { 181 const char *ap; 182 char *mailname, *shell; 183 char timestr[TIMESIZE]; 184 struct passwd *pass_entry; 185 struct tm runtime; 186 int fdes, lockdes, fd2; 187 FILE *fp; 188 struct sigaction act; 189 char **atenv; 190 int ch; 191 mode_t cmask; 192 extern char **environ; 193 194 (void)setlocale(LC_TIME, ""); 195 196 /* 197 * Install the signal handler for SIGINT; terminate after removing the 198 * spool file if necessary 199 */ 200 bzero(&act, sizeof act); 201 act.sa_handler = sigc; 202 sigemptyset(&act.sa_mask); 203 act.sa_flags = 0; 204 sigaction(SIGINT, &act, NULL); 205 206 PRIV_START; 207 208 if ((lockdes = open(AT_DIR, O_RDONLY, 0)) < 0) 209 perr("Cannot open jobs dir"); 210 211 /* 212 * Lock the jobs dir so we don't have to worry about someone 213 * else grabbing a file name out from under us. 214 * Set an alarm so we don't sleep forever waiting on the lock. 215 * If we don't succeed with ALARMC seconds, something is wrong... 216 */ 217 bzero(&act, sizeof act); 218 act.sa_handler = alarmc; 219 sigemptyset(&act.sa_mask); 220 #ifdef SA_INTERRUPT 221 act.sa_flags = SA_INTERRUPT; 222 #endif 223 sigaction(SIGALRM, &act, NULL); 224 alarm(ALARMC); 225 ch = flock(lockdes, LOCK_EX); 226 alarm(0); 227 if (ch != 0) 228 panic("Unable to lock jobs dir"); 229 230 /* 231 * Create the file. The x bit is only going to be set after it has 232 * been completely written out, to make sure it is not executed in 233 * the meantime. To make sure they do not get deleted, turn off 234 * their r bit. Yes, this is a kluge. 235 */ 236 cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR); 237 if ((fdes = newjob(runtimer, queue)) == -1) 238 perr("Cannot create atjob file"); 239 240 if ((fd2 = dup(fdes)) < 0) 241 perr("Error in dup() of job file"); 242 243 if (fchown(fd2, real_uid, real_gid) != 0) 244 perr("Cannot give away file"); 245 246 PRIV_END; 247 248 /* 249 * We've successfully created the file; let's set the flag so it 250 * gets removed in case of an interrupt or error. 251 */ 252 fcreated = 1; 253 254 /* Now we can release the lock, so other people can access it */ 255 (void)close(lockdes); 256 257 if ((fp = fdopen(fdes, "w")) == NULL) 258 panic("Cannot reopen atjob file"); 259 260 /* 261 * Get the userid to mail to, first by trying getlogin(), which asks 262 * the kernel, then from $LOGNAME or $USER, finally from getpwuid(). 263 */ 264 mailname = getlogin(); 265 if (mailname == NULL && (mailname = getenv("LOGNAME")) == NULL) 266 mailname = getenv("USER"); 267 268 if ((mailname == NULL) || (mailname[0] == '\0') || 269 (strlen(mailname) > MAX_UNAME) || (getpwnam(mailname) == NULL)) { 270 pass_entry = getpwuid(real_uid); 271 if (pass_entry != NULL) 272 mailname = pass_entry->pw_name; 273 } 274 275 /* 276 * Get the shell to run the job under. First check $SHELL, falling 277 * back to the user's shell in the password database or, failing 278 * that, /bin/sh. 279 */ 280 if ((shell = getenv("SHELL")) == NULL || *shell == '\0') { 281 pass_entry = getpwuid(real_uid); 282 if (pass_entry != NULL && *pass_entry->pw_shell != '\0') 283 shell = pass_entry->pw_shell; 284 else 285 shell = _PATH_BSHELL; 286 } 287 288 (void)fprintf(fp, "#!/bin/sh\n# atrun uid=%lu gid=%lu\n# mail %*s %d\n", 289 (unsigned long)real_uid, (unsigned long)real_gid, 290 MAX_UNAME, mailname, send_mail); 291 292 /* Write out the umask at the time of invocation */ 293 (void)fprintf(fp, "umask %o\n", cmask); 294 295 /* 296 * Write out the environment. Anything that may look like a special 297 * character to the shell is quoted, except for \n, which is done 298 * with a pair of "'s. Don't export the no_export list (such as 299 * TERM or DISPLAY) because we don't want these. 300 */ 301 for (atenv = environ; *atenv != NULL; atenv++) { 302 int export = 1; 303 char *eqp; 304 305 eqp = strchr(*atenv, '='); 306 if (eqp == NULL) 307 eqp = *atenv; 308 else { 309 int i; 310 311 for (i = 0;i < sizeof(no_export) / 312 sizeof(no_export[0]); i++) { 313 export = export 314 && (strncmp(*atenv, no_export[i], 315 (size_t) (eqp - *atenv)) != 0); 316 } 317 eqp++; 318 } 319 320 if (export) { 321 (void)fwrite(*atenv, sizeof(char), eqp - *atenv, fp); 322 for (ap = eqp; *ap != '\0'; ap++) { 323 if (*ap == '\n') 324 (void)fprintf(fp, "\"\n\""); 325 else { 326 if (!isalnum(*ap)) { 327 switch (*ap) { 328 case '%': case '/': case '{': 329 case '[': case ']': case '=': 330 case '}': case '@': case '+': 331 case '#': case ',': case '.': 332 case ':': case '-': case '_': 333 break; 334 default: 335 (void)fputc('\\', fp); 336 break; 337 } 338 } 339 (void)fputc(*ap, fp); 340 } 341 } 342 (void)fputs("; export ", fp); 343 (void)fwrite(*atenv, sizeof(char), eqp - *atenv - 1, fp); 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(*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("%ld.%c\t%s\n", (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%10ld.%c %c%s\n", 454 timestr, pw ? pw->pw_name : "???", 455 (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 long l; 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 l = strtol(dirent->d_name, &ep, 10); 541 if (*ep != '.' || !isalpha(*(ep + 1)) || *(ep + 2) != '\0' || 542 l < 0 || l >= INT_MAX) 543 continue; 544 runtimer = (time_t)l; 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(int job) 612 { 613 int ch, junk; 614 615 printf("%d: 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(*(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(ERROR_EXIT); 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 l = strtol(dirent->d_name, &ep, 10); 693 if (*ep != '.' || !isalpha(*(ep + 1)) || *(ep + 2) != '\0' || 694 l < 0 || l >= INT_MAX) 695 continue; 696 runtimer = (time_t)l; 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 /* current century + specified year */ 835 yearset = ATOI2(arg); 836 lt->tm_year = ((lt->tm_year / 100) * 100); 837 lt->tm_year += yearset; 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(ERROR_EXIT); 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(ERROR_EXIT); 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(queue) || isupper(queue))) 994 usage(); 995 996 queue_set = 1; 997 break; 998 999 case 'd': /* for backwards compatibility */ 1000 case 'r': 1001 program = ATRM; 1002 options = ""; 1003 break; 1004 1005 case 't': 1006 timer = ttime(optarg); 1007 break; 1008 1009 case 'l': 1010 program = ATQ; 1011 options = "cnvq:"; 1012 break; 1013 1014 case 'b': 1015 program = BATCH; 1016 options = "f:q:mv"; 1017 break; 1018 1019 case 'c': 1020 if (program == ATQ) { 1021 cflag = 1; 1022 } else { 1023 program = CAT; 1024 options = ""; 1025 } 1026 break; 1027 1028 case 'n': 1029 nflag = 1; 1030 break; 1031 1032 default: 1033 usage(); 1034 break; 1035 } 1036 } 1037 argc -= optind; 1038 argv += optind; 1039 1040 switch (program) { 1041 case AT: 1042 case BATCH: 1043 if (atinput != NULL) { 1044 if (freopen(atinput, "r", stdin) == NULL) 1045 perr("Cannot open input file"); 1046 } 1047 break; 1048 default: 1049 ; 1050 } 1051 1052 if (getcwd(cwd, sizeof(cwd)) == NULL) 1053 perr("Cannot get current working directory"); 1054 1055 set_cron_cwd(); 1056 1057 if (!check_permission()) 1058 panic("You do not have permission to use at."); 1059 1060 /* select our program */ 1061 switch (program) { 1062 case ATQ: 1063 list_jobs(argc, argv, nflag, cflag); 1064 break; 1065 1066 case ATRM: 1067 case CAT: 1068 if ((aflag && argc) || (!aflag && !argc)) 1069 usage(); 1070 exit(process_jobs(argc, argv, program)); 1071 break; 1072 1073 case AT: 1074 /* Time may have been specified via the -t flag. */ 1075 if (timer == -1) { 1076 if (argc == 0) 1077 usage(); 1078 else if ((timer = parsetime(argc, argv)) == -1) 1079 exit(ERROR_EXIT); 1080 } 1081 writefile(cwd, timer, queue); 1082 break; 1083 1084 case BATCH: 1085 if (queue_set) 1086 queue = toupper(queue); 1087 else 1088 queue = DEFAULT_BATCH_QUEUE; 1089 1090 if (argc == 0) 1091 timer = time(NULL); 1092 else if ((timer = parsetime(argc, argv)) == -1) 1093 exit(ERROR_EXIT); 1094 1095 writefile(cwd, timer, queue); 1096 break; 1097 1098 default: 1099 panic("Internal error"); 1100 break; 1101 } 1102 exit(OK_EXIT); 1103 } 1104