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