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