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