1 /* $OpenBSD: at.c,v 1.40 2003/09/26 21:26:40 tedu 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.40 2003/09/26 21:26:40 tedu 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, **newatjobs, *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 int newjobs = maxjobs * 2; 581 newatjobs = realloc(atjobs, newjobs * sizeof(job)); 582 if (newatjobs == NULL) 583 panic("Insufficient virtual memory"); 584 atjobs = newatjobs; 585 maxjobs = newjobs; 586 } 587 atjobs[numjobs++] = job; 588 } 589 free(uids); 590 591 if (count_only || numjobs == 0) { 592 if (numjobs == 0 && !shortformat) 593 fprintf(stderr, "no files in queue.\n"); 594 else if (count_only) 595 printf("%d\n", numjobs); 596 free(atjobs); 597 return; 598 } 599 600 /* Sort by job run time or by job creation time. */ 601 qsort(atjobs, numjobs, sizeof(struct atjob *), 602 csort ? byctime : byjobno); 603 604 if (!shortformat) 605 (void)puts(" Rank Execution Date Owner " 606 "Job Queue"); 607 608 for (i = 0; i < numjobs; i++) { 609 print_job(atjobs[i], i + 1, shortformat); 610 free(atjobs[i]); 611 } 612 free(atjobs); 613 } 614 615 static int 616 rmok(int job) 617 { 618 int ch, junk; 619 620 printf("%d: remove it? ", job); 621 ch = getchar(); 622 while ((junk = getchar()) != EOF && junk != '\n') 623 ; 624 return (ch == 'y' || ch == 'Y'); 625 } 626 627 /* 628 * Loop through all jobs in AT_DIR and display or delete ones 629 * that match argv (may be job or username), or all if argc == 0. 630 * Only the superuser may display/delete other people's jobs. 631 */ 632 static int 633 process_jobs(int argc, char **argv, int what) 634 { 635 struct stat stbuf; 636 struct dirent *dirent; 637 struct passwd *pw; 638 time_t runtimer; 639 uid_t *uids; 640 char **jobs, *ep, queue; 641 long l; 642 FILE *fp; 643 DIR *spool; 644 int job_matches, jobs_len, uids_len; 645 int error, i, ch, changed; 646 647 PRIV_START; 648 649 if (chdir(AT_DIR) != 0) 650 perr2("Cannot change to ", AT_DIR); 651 652 if ((spool = opendir(".")) == NULL) 653 perr2("Cannot open ", AT_DIR); 654 655 PRIV_END; 656 657 /* Convert argv into a list of jobs and uids. */ 658 jobs = NULL; 659 uids = NULL; 660 jobs_len = uids_len = 0; 661 if (argc > 0) { 662 if ((jobs = malloc(sizeof(char *) * argc)) == NULL || 663 (uids = malloc(sizeof(uid_t) * argc)) == NULL) 664 panic("Insufficient virtual memory"); 665 666 for (i = 0; i < argc; i++) { 667 l = strtol(argv[i], &ep, 10); 668 if (*ep == '.' && isalpha(*(ep + 1)) && 669 *(ep + 2) == '\0' && l > 0 && l < INT_MAX) 670 jobs[jobs_len++] = argv[i]; 671 else if ((pw = getpwnam(argv[i])) != NULL) { 672 if (real_uid != pw->pw_uid && real_uid != 0) { 673 fprintf(stderr, "%s: Only the superuser" 674 " may %s other users' jobs\n", 675 ProgramName, what == ATRM 676 ? "remove" : "view"); 677 exit(ERROR_EXIT); 678 } 679 uids[uids_len++] = pw->pw_uid; 680 } else 681 panic2(argv[i], ": invalid user name"); 682 } 683 } 684 685 /* Loop over every file in the directory */ 686 changed = 0; 687 while ((dirent = readdir(spool)) != NULL) { 688 689 PRIV_START; 690 if (stat(dirent->d_name, &stbuf) != 0) 691 perr2("Cannot stat in ", AT_DIR); 692 PRIV_END; 693 694 if (stbuf.st_uid != real_uid && real_uid != 0) 695 continue; 696 697 l = strtol(dirent->d_name, &ep, 10); 698 if (*ep != '.' || !isalpha(*(ep + 1)) || *(ep + 2) != '\0' || 699 l < 0 || l >= INT_MAX) 700 continue; 701 runtimer = (time_t)l; 702 queue = *(ep + 1); 703 704 /* Check runtimer against argv; argc==0 means do all. */ 705 job_matches = (argc == 0) ? 1 : 0; 706 if (!job_matches) { 707 for (i = 0; i < jobs_len; i++) { 708 if (jobs[i] != NULL && 709 strcmp(dirent->d_name, jobs[i]) == 0) { 710 jobs[i] = NULL; 711 job_matches = 1; 712 break; 713 } 714 } 715 } 716 if (!job_matches) { 717 for (i = 0; i < uids_len; i++) { 718 if (uids[i] == stbuf.st_uid) { 719 job_matches = 1; 720 break; 721 } 722 } 723 } 724 725 if (job_matches) { 726 switch (what) { 727 case ATRM: 728 PRIV_START; 729 730 if (!interactive || 731 (interactive && rmok(runtimer))) { 732 if (unlink(dirent->d_name) == 0) 733 changed = 1; 734 else 735 perr(dirent->d_name); 736 if (!force && !interactive) 737 fprintf(stderr, 738 "%s removed\n", 739 dirent->d_name); 740 } 741 742 PRIV_END; 743 744 break; 745 746 case CAT: 747 PRIV_START; 748 749 fp = fopen(dirent->d_name, "r"); 750 751 PRIV_END; 752 753 if (!fp) 754 perr("Cannot open file"); 755 756 while ((ch = getc(fp)) != EOF) 757 putchar(ch); 758 759 break; 760 761 default: 762 panic("Internal error"); 763 break; 764 } 765 } 766 } 767 for (error = 0, i = 0; i < jobs_len; i++) { 768 if (jobs[i] != NULL) { 769 if (!force) 770 fprintf(stderr, "%s: %s: no such job\n", 771 ProgramName, jobs[i]); 772 error++; 773 } 774 } 775 free(jobs); 776 free(uids); 777 778 /* If we modied the spool, poke cron so it knows to reload. */ 779 if (changed) { 780 PRIV_START; 781 if (chdir(CRONDIR) != 0) 782 perror(CRONDIR); 783 else 784 poke_daemon(AT_DIR, RELOAD_AT); 785 PRIV_END; 786 } 787 788 return (error); 789 } 790 791 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) 792 793 /* 794 * This is pretty much a copy of stime_arg1() from touch.c. 795 */ 796 static time_t 797 ttime(const char *arg) 798 { 799 struct timeval tv[2]; 800 time_t now; 801 struct tm *t; 802 int yearset; 803 char *p; 804 805 if (gettimeofday(&tv[0], NULL)) 806 panic("Cannot get current time"); 807 808 /* Start with the current time. */ 809 now = tv[0].tv_sec; 810 if ((t = localtime(&now)) == NULL) 811 panic("localtime"); 812 /* [[CC]YY]MMDDhhmm[.SS] */ 813 if ((p = strchr(arg, '.')) == NULL) 814 t->tm_sec = 0; /* Seconds defaults to 0. */ 815 else { 816 if (strlen(p + 1) != 2) 817 goto terr; 818 *p++ = '\0'; 819 t->tm_sec = ATOI2(p); 820 } 821 822 yearset = 0; 823 switch(strlen(arg)) { 824 case 12: /* CCYYMMDDhhmm */ 825 t->tm_year = ATOI2(arg); 826 t->tm_year *= 100; 827 yearset = 1; 828 /* FALLTHROUGH */ 829 case 10: /* YYMMDDhhmm */ 830 if (yearset) { 831 yearset = ATOI2(arg); 832 t->tm_year += yearset; 833 } else { 834 yearset = ATOI2(arg); 835 t->tm_year = yearset + 2000; 836 } 837 t->tm_year -= 1900; /* Convert to UNIX time. */ 838 /* FALLTHROUGH */ 839 case 8: /* MMDDhhmm */ 840 t->tm_mon = ATOI2(arg); 841 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 842 t->tm_mday = ATOI2(arg); 843 t->tm_hour = ATOI2(arg); 844 t->tm_min = ATOI2(arg); 845 break; 846 default: 847 goto terr; 848 } 849 850 t->tm_isdst = -1; /* Figure out DST. */ 851 tv[0].tv_sec = tv[1].tv_sec = mktime(t); 852 if (tv[0].tv_sec != -1) 853 return (tv[0].tv_sec); 854 else 855 terr: 856 panic("out of range or illegal time specification: " 857 "[[CC]YY]MMDDhhmm[.SS]"); 858 } 859 860 static int 861 check_permission(void) 862 { 863 int ok; 864 uid_t uid = geteuid(); 865 struct passwd *pw; 866 867 if ((pw = getpwuid(uid)) == NULL) { 868 perror("Cannot access password database"); 869 exit(ERROR_EXIT); 870 } 871 872 PRIV_START; 873 874 ok = allowed(pw->pw_name, AT_ALLOW, AT_DENY); 875 876 PRIV_END; 877 878 return (ok); 879 } 880 881 static void 882 usage(void) 883 { 884 /* Print usage and exit. */ 885 switch (program) { 886 case AT: 887 case CAT: 888 (void)fprintf(stderr, 889 "usage: at [-bm] [-f file] [-q queue] -t time_arg\n" 890 " at [-bm] [-f file] [-q queue] timespec\n" 891 " at -c job [job ...]\n" 892 " at -l [-q queue] [job ...]\n" 893 " at -r job [job ...]\n"); 894 break; 895 case ATQ: 896 (void)fprintf(stderr, 897 "usage: atq [-cnv] [-q queue] [name...]\n"); 898 break; 899 case ATRM: 900 (void)fprintf(stderr, 901 "usage: atrm [-afi] [[job] [name] ...]\n"); 902 break; 903 case BATCH: 904 (void)fprintf(stderr, 905 "usage: batch [-m] [-f file] [-q queue] [timespec]\n"); 906 break; 907 } 908 exit(ERROR_EXIT); 909 } 910 911 int 912 main(int argc, char **argv) 913 { 914 time_t timer = -1; 915 char queue = DEFAULT_AT_QUEUE; 916 char queue_set = 0; 917 char *options = "q:f:t:bcdlmrv"; /* default options for at */ 918 char cwd[PATH_MAX]; 919 int ch; 920 int aflag = 0; 921 int cflag = 0; 922 int nflag = 0; 923 924 if ((ProgramName = strrchr(argv[0], '/')) != NULL) 925 ProgramName++; 926 else 927 ProgramName = argv[0]; 928 929 RELINQUISH_PRIVS; 930 931 /* find out what this program is supposed to do */ 932 if (strcmp(ProgramName, "atq") == 0) { 933 program = ATQ; 934 options = "cnvq:"; 935 } else if (strcmp(ProgramName, "atrm") == 0) { 936 program = ATRM; 937 options = "afi"; 938 } else if (strcmp(ProgramName, "batch") == 0) { 939 program = BATCH; 940 options = "f:q:mv"; 941 } 942 943 /* process whatever options we can process */ 944 while ((ch = getopt(argc, argv, options)) != -1) { 945 switch (ch) { 946 case 'a': 947 aflag = 1; 948 break; 949 950 case 'i': 951 interactive = 1; 952 force = 0; 953 break; 954 955 case 'v': /* show completed but unremoved jobs */ 956 /* 957 * This option is only useful when we are invoked 958 * as atq but we accept (and ignore) this flag in 959 * the other programs for backwards compatibility. 960 */ 961 vflag = 1; 962 break; 963 964 case 'm': /* send mail when job is complete */ 965 send_mail = 1; 966 break; 967 968 case 'f': 969 if (program == ATRM) { 970 force = 1; 971 interactive = 0; 972 } else 973 atinput = optarg; 974 break; 975 976 case 'q': /* specify queue */ 977 if (strlen(optarg) > 1) 978 usage(); 979 980 atqueue = queue = *optarg; 981 if (!(islower(queue) || isupper(queue))) 982 usage(); 983 984 queue_set = 1; 985 break; 986 987 case 'd': /* for backwards compatibility */ 988 case 'r': 989 program = ATRM; 990 options = ""; 991 break; 992 993 case 't': 994 timer = ttime(optarg); 995 break; 996 997 case 'l': 998 program = ATQ; 999 options = "cnvq:"; 1000 break; 1001 1002 case 'b': 1003 program = BATCH; 1004 options = "f:q:mv"; 1005 break; 1006 1007 case 'c': 1008 if (program == ATQ) { 1009 cflag = 1; 1010 } else { 1011 program = CAT; 1012 options = ""; 1013 } 1014 break; 1015 1016 case 'n': 1017 nflag = 1; 1018 break; 1019 1020 default: 1021 usage(); 1022 break; 1023 } 1024 } 1025 argc -= optind; 1026 argv += optind; 1027 1028 if (getcwd(cwd, sizeof(cwd)) == NULL) 1029 perr("Cannot get current working directory"); 1030 1031 set_cron_cwd(); 1032 1033 if (!check_permission()) 1034 panic("You do not have permission to use at."); 1035 1036 /* select our program */ 1037 switch (program) { 1038 case ATQ: 1039 list_jobs(argc, argv, nflag, cflag); 1040 break; 1041 1042 case ATRM: 1043 case CAT: 1044 if ((aflag && argc) || (!aflag && !argc)) 1045 usage(); 1046 exit(process_jobs(argc, argv, program)); 1047 break; 1048 1049 case AT: 1050 /* Time may have been specified via the -t flag. */ 1051 if (timer == -1) { 1052 if (argc == 0) 1053 usage(); 1054 else if ((timer = parsetime(argc, argv)) == -1) 1055 exit(ERROR_EXIT); 1056 } 1057 writefile(cwd, timer, queue); 1058 break; 1059 1060 case BATCH: 1061 if (queue_set) 1062 queue = toupper(queue); 1063 else 1064 queue = DEFAULT_BATCH_QUEUE; 1065 1066 if (argc == 0) 1067 timer = time(NULL); 1068 else if ((timer = parsetime(argc, argv)) == -1) 1069 exit(ERROR_EXIT); 1070 1071 writefile(cwd, timer, queue); 1072 break; 1073 1074 default: 1075 panic("Internal error"); 1076 break; 1077 } 1078 exit(OK_EXIT); 1079 } 1080