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