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