1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 #include <sys/resource.h> 31 #include <sys/stat.h> 32 #include <sys/types.h> 33 34 #include <dirent.h> 35 #include <string.h> 36 #include <stdlib.h> 37 #include <fcntl.h> 38 #include <pwd.h> 39 #include <stdio.h> 40 #include <ctype.h> 41 #include <time.h> 42 #include <signal.h> 43 #include <errno.h> 44 #include <limits.h> 45 #include <ulimit.h> 46 #include <unistd.h> 47 #include <locale.h> 48 #include <libintl.h> 49 #include <tzfile.h> 50 #include <project.h> 51 52 #include "cron.h" 53 54 #define TMPFILE "_at" /* prefix for temporary files */ 55 /* 56 * Mode for creating files in ATDIR. 57 * Setuid bit on so that if an owner of a file gives that file 58 * away to someone else, the setuid bit will no longer be set. 59 * If this happens, atrun will not execute the file 60 */ 61 #define ATMODE (S_ISUID | S_IRUSR | S_IRGRP | S_IROTH) 62 #define ROOT 0 /* user-id of super-user */ 63 #define MAXTRYS 100 /* max trys to create at job file */ 64 65 #define BADTIME "bad time specification" 66 #define BADQUEUE "queue name must be a single character a-z" 67 #define NOTCQUEUE "queue c is reserved for cron entries" 68 #define BADSHELL "because your login shell isn't /usr/bin/sh,"\ 69 "you can't use at" 70 #define WARNSHELL "commands will be executed using %s\n" 71 #define CANTCD "can't change directory to the at directory" 72 #define CANTCHOWN "can't change the owner of your job to you" 73 #define CANTCREATE "can't create a job for you" 74 #define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)" 75 #define NOOPENDIR "can't open the at directory" 76 #define NOTALLOWED "you are not authorized to use at. Sorry." 77 #define USAGE\ 78 "usage: at [-c|-k|-s] [-m] [-f file] [-p project] [-q queuename] "\ 79 "-t time\n"\ 80 " at [-c|-k|-s] [-m] [-f file] [-p project] [-q queuename] "\ 81 "timespec\n"\ 82 " at -l [-p project] [-q queuename] [at_job_id...]\n"\ 83 " at -r at_job_id ...\n" 84 85 #define FORMAT "%a %b %e %H:%M:%S %Y" 86 87 /* Macros used in format for fscanf */ 88 #define AQ(x) #x 89 #define BUFFMT(p) AQ(p) 90 91 static int leap(int); 92 static int atoi_for2(char *); 93 static int check_queue(char *, int); 94 static int list_jobs(int, char **, int, int); 95 static int remove_jobs(int, char **, char *); 96 static void usage(void); 97 static void catch(int); 98 static void copy(char *, FILE *, int); 99 static void atime(struct tm *, struct tm *); 100 static int not_this_project(char *); 101 static char *mkjobname(time_t); 102 static time_t parse_time(char *); 103 static time_t gtime(struct tm *); 104 void atabort(char *)__NORETURN; 105 void yyerror(void); 106 extern int yyparse(void); 107 108 extern void audit_at_delete(char *, char *, int); 109 extern int audit_at_create(char *, int); 110 extern int audit_cron_is_anc_name(char *); 111 extern int audit_cron_delete_anc_file(char *, char *); 112 113 /* 114 * Error in getdate(3G) 115 */ 116 static char *errlist[] = { 117 /* 0 */ "", 118 /* 1 */ "getdate: The DATEMSK environment variable is not set", 119 /* 2 */ "getdate: Error on \"open\" of the template file", 120 /* 3 */ "getdate: Error on \"stat\" of the template file", 121 /* 4 */ "getdate: The template file is not a regular file", 122 /* 5 */ "getdate: An error is encountered while reading the template", 123 /* 6 */ "getdate: Malloc(3C) failed", 124 /* 7 */ "getdate: There is no line in the template that matches the input", 125 /* 8 */ "getdate: Invalid input specification" 126 }; 127 128 int gmtflag = 0; 129 int mday[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 130 uid_t user; 131 struct tm *tp, at, rt; 132 static int cshflag = 0; 133 static int kshflag = 0; 134 static int shflag = 0; 135 static int mflag = 0; 136 static int pflag = 0; 137 static char *Shell; 138 static char *tfname; 139 static char pname[80]; 140 static char pname1[80]; 141 static short jobtype = ATEVENT; /* set to 1 if batch job */ 142 extern char *argp; 143 extern int per_errno; 144 static projid_t project; 145 146 int 147 main(int argc, char **argv) 148 { 149 FILE *inputfile; 150 int i, fd; 151 int try = 0; 152 int fflag = 0; 153 int lflag = 0; 154 int qflag = 0; 155 int rflag = 0; 156 int tflag = 0; 157 int c; 158 int tflen; 159 char *file; 160 char *login; 161 char *job; 162 char *jobfile = NULL; /* file containing job to be run */ 163 char argpbuf[LINE_MAX], timebuf[80]; 164 time_t now; 165 time_t when = 0; 166 struct tm *ct; 167 char *proj; 168 struct project prj, *pprj; 169 char mybuf[PROJECT_BUFSZ]; 170 char ipbuf[PROJECT_BUFSZ]; 171 172 (void) setlocale(LC_ALL, ""); 173 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 174 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 175 #endif 176 (void) textdomain(TEXT_DOMAIN); 177 178 user = getuid(); 179 login = getuser(user); 180 if (login == NULL) { 181 if (per_errno == 2) 182 atabort(BADSHELL); 183 else 184 atabort(INVALIDUSER); 185 } 186 187 if (!allowed(login, ATALLOW, ATDENY)) 188 atabort(NOTALLOWED); 189 190 while ((c = getopt(argc, argv, "cklmsrf:p:q:t:")) != EOF) 191 switch (c) { 192 case 'c': 193 cshflag++; 194 break; 195 case 'f': 196 fflag++; 197 jobfile = optarg; 198 break; 199 case 'k': 200 kshflag++; 201 break; 202 case 'l': 203 lflag++; 204 break; 205 case 'm': 206 mflag++; 207 break; 208 case 'p': 209 proj = optarg; 210 pprj = &prj; 211 if ((pprj = getprojbyname(proj, pprj, 212 (void *)&mybuf, sizeof (mybuf))) != NULL) { 213 project = pprj->pj_projid; 214 if (inproj(login, pprj->pj_name, 215 (void *)&ipbuf, sizeof (ipbuf))) 216 pflag++; 217 else { 218 (void) fprintf(stderr, 219 gettext("at: user %s is " 220 "not a member of " 221 "project %s (%d)\n"), 222 login, pprj->pj_name, 223 project); 224 exit(2); 225 } 226 break; 227 } 228 pprj = &prj; 229 if (isdigit(proj[0]) && 230 (pprj = getprojbyid(atoi(proj), pprj, 231 (void *)&mybuf, sizeof (mybuf))) != NULL) { 232 project = pprj->pj_projid; 233 if (inproj(login, pprj->pj_name, 234 (void *)&ipbuf, sizeof (ipbuf))) 235 pflag++; 236 else { 237 (void) fprintf(stderr, 238 gettext("at: user %s is " 239 "not a member of " 240 "project %s (%d)\n"), 241 login, pprj->pj_name, 242 project); 243 exit(2); 244 } 245 break; 246 } 247 (void) fprintf(stderr, gettext("at: project " 248 "%s not found.\n"), proj); 249 exit(2); 250 break; 251 case 'q': 252 qflag++; 253 if (optarg[1] != '\0') 254 atabort(BADQUEUE); 255 jobtype = *optarg - 'a'; 256 if ((jobtype < 0) || (jobtype > 25)) 257 atabort(BADQUEUE); 258 if (jobtype == 2) 259 atabort(NOTCQUEUE); 260 break; 261 case 'r': 262 rflag++; 263 break; 264 case 's': 265 shflag++; 266 break; 267 case 't': 268 tflag++; 269 when = parse_time(optarg); 270 break; 271 default: 272 usage(); 273 } 274 275 argc -= optind; 276 argv += optind; 277 278 if (lflag + rflag > 1) 279 usage(); 280 281 if (lflag) { 282 if (cshflag || kshflag || shflag || mflag || 283 fflag || tflag || rflag) 284 usage(); 285 return (list_jobs(argc, argv, qflag, jobtype)); 286 } 287 288 if (rflag) { 289 if (cshflag || kshflag || shflag || mflag || 290 fflag || tflag || qflag) 291 usage(); 292 return (remove_jobs(argc, argv, login)); 293 } 294 295 if ((argc + tflag == 0) && (jobtype != BATCHEVENT)) 296 usage(); 297 298 if (cshflag + kshflag + shflag > 1) 299 atabort("ambiguous shell request"); 300 301 time(&now); 302 303 if (jobtype == BATCHEVENT) 304 when = now; 305 306 if (when == 0) { /* figure out what time to run the job */ 307 int argplen = sizeof (argpbuf) - 1; 308 309 argpbuf[0] = '\0'; 310 argp = argpbuf; 311 i = 0; 312 while (i < argc) { 313 /* guard against buffer overflow */ 314 argplen -= strlen(argv[i]) + 1; 315 if (argplen < 0) 316 atabort(BADTIME); 317 318 strcat(argp, argv[i]); 319 strcat(argp, " "); 320 i++; 321 } 322 if ((file = getenv("DATEMSK")) == 0 || file[0] == '\0') { 323 tp = localtime(&now); 324 /* 325 * Fix for 1047182 - we have to let yyparse 326 * check bounds on mday[] first, then fixup 327 * the leap year case. 328 */ 329 yyparse(); 330 331 mday[1] = 28 + leap(at.tm_year); 332 333 if (at.tm_mday > mday[at.tm_mon]) 334 atabort("bad date"); 335 336 atime(&at, &rt); 337 when = gtime(&at); 338 if (!gmtflag) { 339 when += timezone; 340 if (localtime(&when)->tm_isdst) 341 when -= (timezone-altzone); 342 } 343 } else { /* DATEMSK is set */ 344 if ((ct = getdate(argpbuf)) == NULL) 345 atabort(errlist[getdate_err]); 346 else 347 when = mktime(ct); 348 } 349 } 350 351 if (when < now) /* time has already past */ 352 atabort("too late"); 353 354 tflen = strlen(ATDIR) + 1 + strlen(TMPFILE) + 355 10 + 1; /* 10 for an INT_MAX pid */ 356 tfname = xmalloc(tflen); 357 snprintf(tfname, tflen, "%s/%s%d", ATDIR, TMPFILE, getpid()); 358 359 /* catch INT, HUP, TERM and QUIT signals */ 360 if (signal(SIGINT, catch) == SIG_IGN) 361 signal(SIGINT, SIG_IGN); 362 if (signal(SIGHUP, catch) == SIG_IGN) 363 signal(SIGHUP, SIG_IGN); 364 if (signal(SIGQUIT, catch) == SIG_IGN) 365 signal(SIGQUIT, SIG_IGN); 366 if (signal(SIGTERM, catch) == SIG_IGN) 367 signal(SIGTERM, SIG_IGN); 368 if ((fd = open(tfname, O_CREAT|O_EXCL|O_WRONLY, ATMODE)) < 0) 369 atabort(CANTCREATE); 370 if (chown(tfname, user, getgid()) == -1) { 371 unlink(tfname); 372 atabort(CANTCHOWN); 373 } 374 close(1); 375 dup(fd); 376 close(fd); 377 sprintf(pname, "%s", PROTO); 378 sprintf(pname1, "%s.%c", PROTO, 'a'+jobtype); 379 380 /* 381 * Open the input file with the user's permissions. 382 */ 383 if (jobfile != NULL) { 384 if ((seteuid(user) < 0) || 385 (inputfile = fopen(jobfile, "r")) == NULL) { 386 unlink(tfname); 387 fprintf(stderr, "at: %s: %s\n", jobfile, errmsg(errno)); 388 exit(1); 389 } 390 else 391 seteuid(0); 392 } else 393 inputfile = stdin; 394 395 copy(jobfile, inputfile, when); 396 while (rename(tfname, job = mkjobname(when)) == -1) { 397 sleep(1); 398 if (++try > MAXTRYS / 10) { 399 unlink(tfname); 400 atabort(CANTCREATE); 401 } 402 } 403 unlink(tfname); 404 if (audit_at_create(job, 0)) 405 atabort(CANTCREATE); 406 407 cron_sendmsg(ADD, login, strrchr(job, '/')+1, AT); 408 if (per_errno == 2) 409 fprintf(stderr, gettext(WARNSHELL), Shell); 410 cftime(timebuf, FORMAT, &when); 411 fprintf(stderr, gettext("job %s at %s\n"), 412 strrchr(job, '/')+1, timebuf); 413 if (when - MINUTE < HOUR) 414 fprintf(stderr, gettext( 415 "at: this job may not be executed at the proper time.\n")); 416 return (0); 417 } 418 419 420 static char * 421 mkjobname(t) 422 time_t t; 423 { 424 int i, fd; 425 char *name; 426 427 name = xmalloc(200); 428 for (i = 0; i < MAXTRYS; i++) { 429 sprintf(name, "%s/%ld.%c", ATDIR, t, 'a'+jobtype); 430 /* fix for 1099183, 1116833 - create file here, avoid race */ 431 if ((fd = open(name, O_CREAT | O_EXCL, ATMODE)) > 0) { 432 close(fd); 433 return (name); 434 } 435 t += 1; 436 } 437 atabort("queue full"); 438 /* NOTREACHED */ 439 } 440 441 442 static void 443 catch(int x) 444 { 445 unlink(tfname); 446 exit(1); 447 } 448 449 450 void 451 atabort(msg) 452 char *msg; 453 { 454 fprintf(stderr, "at: %s\n", gettext(msg)); 455 456 exit(1); 457 } 458 459 int 460 yywrap(void) 461 { 462 return (1); 463 } 464 465 void 466 yyerror(void) 467 { 468 atabort(BADTIME); 469 } 470 471 /* 472 * add time structures logically 473 */ 474 static void 475 atime(struct tm *a, struct tm *b) 476 { 477 if ((a->tm_sec += b->tm_sec) >= 60) { 478 b->tm_min += a->tm_sec / 60; 479 a->tm_sec %= 60; 480 } 481 if ((a->tm_min += b->tm_min) >= 60) { 482 b->tm_hour += a->tm_min / 60; 483 a->tm_min %= 60; 484 } 485 if ((a->tm_hour += b->tm_hour) >= 24) { 486 b->tm_mday += a->tm_hour / 24; 487 a->tm_hour %= 24; 488 } 489 a->tm_year += b->tm_year; 490 if ((a->tm_mon += b->tm_mon) >= 12) { 491 a->tm_year += a->tm_mon / 12; 492 a->tm_mon %= 12; 493 } 494 a->tm_mday += b->tm_mday; 495 mday[1] = 28 + leap(a->tm_year); 496 while (a->tm_mday > mday[a->tm_mon]) { 497 a->tm_mday -= mday[a->tm_mon++]; 498 if (a->tm_mon > 11) { 499 a->tm_mon = 0; 500 mday[1] = 28 + leap(++a->tm_year); 501 } 502 } 503 504 } 505 506 static int 507 leap(int year) 508 { 509 return (isleap(year + TM_YEAR_BASE)); 510 } 511 512 /* 513 * return time from time structure 514 */ 515 static time_t 516 gtime(tptr) 517 struct tm *tptr; 518 { 519 int i; 520 long tv; 521 int dmsize[12] = 522 {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 523 524 525 tv = 0; 526 for (i = 1970; i != tptr->tm_year+TM_YEAR_BASE; i++) 527 tv += (365 + isleap(i)); 528 /* 529 * We call isleap since leap() adds 530 * 1900 onto any value passed 531 */ 532 533 if (!leap(tptr->tm_year) && at.tm_mday == 29 && at.tm_mon == 1) 534 atabort("bad date - not a leap year"); 535 536 if ((leap(tptr->tm_year)) && tptr->tm_mon >= 2) 537 ++tv; 538 539 for (i = 0; i < tptr->tm_mon; ++i) 540 tv += dmsize[i]; 541 tv += tptr->tm_mday - 1; 542 tv = 24 * tv + tptr->tm_hour; 543 tv = 60 * tv + tptr->tm_min; 544 tv = 60 * tv + tptr->tm_sec; 545 return (tv); 546 } 547 548 /* 549 * make job file from proto + stdin 550 */ 551 static void 552 copy(char *jobfile, FILE *inputfile, int when) 553 { 554 int c; 555 FILE *pfp; 556 FILE *xfp; 557 char *shell; 558 char dirbuf[PATH_MAX + 1]; 559 char line[LINE_MAX]; 560 char **ep; 561 mode_t um; 562 char *val; 563 extern char **environ; 564 int pfd[2]; 565 pid_t pid; 566 uid_t realusr; 567 int ttyinput; 568 int ulimit_flag = 0; 569 struct rlimit rlp; 570 struct project prj, *pprj; 571 char pbuf[PROJECT_BUFSZ]; 572 char pbuf2[PROJECT_BUFSZ]; 573 char *user; 574 575 /* 576 * Fix for 1099381: 577 * If the inputfile is from a tty, then turn on prompting, and 578 * put out a prompt now, instead of waiting for a lot of file 579 * activity to complete. 580 */ 581 ttyinput = isatty(fileno(inputfile)); 582 if (ttyinput) { 583 fputs("at> ", stderr); 584 fflush(stderr); 585 } 586 587 /* 588 * Fix for 1053807: 589 * Determine what shell we should use to run the job. If the user 590 * didn't explicitly request that his/her current shell be over- 591 * ridden (shflag or cshflag), then we use the current shell. 592 */ 593 if (cshflag) 594 Shell = shell = "/bin/csh"; 595 else if (kshflag) { 596 Shell = shell = "/bin/ksh"; 597 ulimit_flag = 1; 598 } else if (shflag) { 599 Shell = shell = "/bin/sh"; 600 ulimit_flag = 1; 601 } else if (((Shell = val = getenv("SHELL")) != NULL) && 602 (*val != '\0')) { 603 shell = "$SHELL"; 604 if ((strstr(val, "/sh") != NULL) || 605 (strstr(val, "/ksh") != NULL)) 606 ulimit_flag = 1; 607 } else { 608 /* SHELL is NULL or unset, therefore use default */ 609 #ifdef XPG4 610 Shell = shell = "/usr/xpg4/bin/sh"; 611 #else 612 Shell = shell = "/bin/sh"; 613 #endif /* XPG4 */ 614 ulimit_flag = 1; 615 } 616 617 printf(": %s job\n", jobtype ? "batch" : "at"); 618 printf(": jobname: %.127s\n", (jobfile == NULL) ? "stdin" : jobfile); 619 printf(": notify by mail: %s\n", (mflag) ? "yes" : "no"); 620 621 if (pflag) { 622 (void) printf(": project: %d\n", project); 623 } else { 624 /* 625 * Check if current user is a member of current project. 626 * This check is done here to avoid setproject() failure 627 * later when the job gets executed. If current user does 628 * not belong to current project, user's default project 629 * will be used instead. This is achieved by not specifying 630 * the project (": project: <project>\n") in the job file. 631 */ 632 if ((user = getuser(getuid())) == NULL) 633 atabort(INVALIDUSER); 634 project = getprojid(); 635 pprj = getprojbyid(project, &prj, pbuf, sizeof (pbuf)); 636 if (pprj != NULL) { 637 if (inproj(user, pprj->pj_name, pbuf2, sizeof (pbuf2))) 638 (void) printf(": project: %d\n", project); 639 } 640 } 641 642 for (ep = environ; *ep; ep++) { 643 if (strchr(*ep, '\'') != NULL) 644 continue; 645 if ((val = strchr(*ep, '=')) == NULL) 646 continue; 647 *val++ = '\0'; 648 printf("export %s; %s='%s'\n", *ep, *ep, val); 649 *--val = '='; 650 } 651 if ((pfp = fopen(pname1, "r")) == NULL && 652 (pfp = fopen(pname, "r")) == NULL) 653 atabort("no prototype"); 654 /* 655 * Put in a line to run the proper shell using the rest of 656 * the file as input. Note that 'exec'ing the shell will 657 * cause sh() to leave a /tmp/sh### file around. (1053807) 658 */ 659 printf("%s << '...the rest of this file is shell input'\n", shell); 660 661 um = umask(0); 662 while ((c = getc(pfp)) != EOF) { 663 if (c != '$') 664 putchar(c); 665 else switch (c = getc(pfp)) { 666 case EOF: 667 goto out; 668 case 'd': 669 /* 670 * fork off a child with submitter's permissions, 671 * otherwise, when IFS=/, /usr/bin/pwd would be parsed 672 * by the shell as file "bin". The shell would 673 * then search according to the submitter's PATH 674 * and run the file bin with root permission 675 */ 676 677 (void) fflush(stdout); 678 dirbuf[0] = NULL; 679 if (pipe(pfd) != 0) 680 atabort("pipe open failed"); 681 realusr = getuid(); /* get realusr before the fork */ 682 if ((pid = fork()) == (pid_t)-1) 683 atabort("fork failed"); 684 if (pid == 0) { /* child process */ 685 (void) close(pfd[0]); 686 /* remove setuid for pwd */ 687 (void) setuid(realusr); 688 if ((xfp = popen("/usr/bin/pwd", "r")) 689 != NULL) { 690 fscanf(xfp, "%" BUFFMT(PATH_MAX) "s", 691 dirbuf); 692 (void) pclose(xfp); 693 xfp = fdopen(pfd[1], "w"); 694 fprintf(xfp, "%s", dirbuf); 695 (void) fclose(xfp); 696 } 697 _exit(0); 698 } 699 (void) close(pfd[1]); /* parent process */ 700 xfp = fdopen(pfd[0], "r"); 701 fscanf(xfp, "%" BUFFMT(PATH_MAX) "s", dirbuf); 702 printf("%s", dirbuf); 703 (void) fclose(xfp); 704 break; 705 case 'm': 706 printf("%o", um); 707 break; 708 case '<': 709 if (ulimit_flag) { 710 if (getrlimit(RLIMIT_FSIZE, &rlp) == 0) { 711 if (rlp.rlim_cur == RLIM_INFINITY) 712 printf("ulimit unlimited\n"); 713 else 714 printf("ulimit %lld\n", 715 rlp.rlim_cur / 512); 716 } 717 } 718 /* 719 * fix for 1113572 - use fputs() so that a 720 * newline isn't appended to the one returned 721 * with fgets(); 1099381 - prompt for input. 722 */ 723 while (fgets(line, LINE_MAX, inputfile) != NULL) { 724 fputs(line, stdout); 725 if (ttyinput) 726 fputs("at> ", stderr); 727 } 728 if (ttyinput) /* clean up the final output */ 729 fputs("<EOT>\n", stderr); 730 break; 731 case 't': 732 printf(":%lu", when); 733 break; 734 default: 735 putchar(c); 736 } 737 } 738 out: 739 fclose(pfp); 740 fflush(NULL); 741 } 742 743 static int 744 remove_jobs(int argc, char **argv, char *login) 745 /* remove jobs that are specified */ 746 { 747 int i, r; 748 int error = 0; 749 struct stat buf; 750 struct passwd *pw; 751 752 pw = getpwuid(user); 753 if (pw == NULL) { 754 atabort("Invalid user.\n"); 755 } 756 757 if (argc == 0) 758 usage(); 759 if (chdir(ATDIR) == -1) 760 atabort(CANTCD); 761 for (i = 0; i < argc; i++) 762 if (strchr(argv[i], '/') != NULL) { 763 fprintf(stderr, "at: %s: not a valid job-id\n", 764 argv[i]); 765 } else if (stat(argv[i], &buf)) { 766 fprintf(stderr, "at: %s: ", argv[i]); 767 perror(""); 768 } else if ((user != buf.st_uid) && 769 (!cron_admin(pw->pw_name))) { 770 fprintf(stderr, "at: you don't own %s\n", 771 argv[i]); 772 error = 1; 773 } else { 774 if (cron_admin(pw->pw_name)) { 775 login = getuser((uid_t)buf.st_uid); 776 if (login == NULL) { 777 if (per_errno == 2) 778 atabort(BADSHELL); 779 else 780 atabort(INVALIDUSER); 781 } 782 } 783 cron_sendmsg(DELETE, login, argv[i], AT); 784 r = unlink(argv[i]); 785 audit_at_delete(argv[i], ATDIR, r); 786 } 787 return (error); 788 } 789 790 791 792 static int 793 list_jobs(int argc, char **argv, int qflag, int queue) 794 { 795 DIR *dir; 796 int i; 797 int error = 0; 798 char *patdir, *atdir, *ptr; 799 char timebuf[80]; 800 time_t t; 801 struct stat buf, st1, st2; 802 struct dirent *dentry; 803 struct passwd *pw; 804 unsigned int atdirlen; 805 int r; 806 struct passwd *pwd, pwds; 807 char buf_pwd[1024]; 808 char job_file[PATH_MAX]; 809 810 pwd = getpwuid_r(user, &pwds, buf_pwd, sizeof (buf_pwd)); 811 if (pwd == NULL) { 812 atabort("Invalid user.\n"); 813 } 814 815 /* list jobs for user */ 816 if (chdir(ATDIR) == -1) 817 atabort(CANTCD); 818 819 atdirlen = strlen(ATDIR); 820 atdir = xmalloc(atdirlen + 1); 821 strcpy(atdir, ATDIR); 822 patdir = strrchr(atdir, '/'); 823 *patdir = '\0'; 824 if (argc == 0) { 825 /* list all jobs for a user */ 826 if (stat(ATDIR, &st1) != 0 || stat(atdir, &st2) != 0) 827 atabort("Can not get status of spooling" 828 "directory for at"); 829 if ((dir = opendir(ATDIR)) == NULL) 830 atabort(NOOPENDIR); 831 while (1) { 832 if ((dentry = readdir(dir)) == NULL) 833 break; 834 if ((dentry->d_ino == st1.st_ino) || 835 (dentry->d_ino == st2.st_ino)) 836 continue; 837 if ((r = audit_cron_is_anc_name(dentry->d_name)) == 1) 838 continue; 839 if (stat(dentry->d_name, &buf)) { 840 unlink(dentry->d_name); 841 audit_cron_delete_anc_file(dentry->d_name, 842 NULL); 843 continue; 844 } 845 if ((!cron_admin(pwd->pw_name)) && 846 (buf.st_uid != user)) 847 continue; 848 ptr = dentry->d_name; 849 if (((t = num(&ptr)) == 0) || (*ptr != '.')) 850 continue; 851 strcpy(job_file, patdir); 852 strcat(job_file, dentry->d_name); 853 if (pflag && not_this_project(job_file)) 854 continue; 855 ascftime(timebuf, FORMAT, localtime(&t)); 856 if ((cron_admin(pwd->pw_name)) && 857 ((pw = getpwuid(buf.st_uid)) != NULL)) { 858 if (!qflag || (qflag && 859 check_queue(ptr, queue))) 860 printf("user = %s\t%s\t%s\n", 861 pw->pw_name, dentry->d_name, 862 timebuf); 863 } else 864 if (!qflag || (qflag && 865 check_queue(ptr, queue))) 866 printf("%s\t%s\n", 867 dentry->d_name, timebuf); 868 } 869 (void) closedir(dir); 870 } else /* list particular jobs for user */ 871 for (i = 0; i < argc; i++) { 872 ptr = argv[i]; 873 strlcpy(job_file, patdir, PATH_MAX); 874 strlcat(job_file, ptr, PATH_MAX); 875 if (((t = num(&ptr)) == 0) || (*ptr != '.')) { 876 fprintf(stderr, gettext( 877 "at: invalid job name %s\n"), argv[i]); 878 error = 1; 879 } else if (stat(argv[i], &buf)) { 880 fprintf(stderr, "at: %s: ", argv[i]); 881 perror(""); 882 error = 1; 883 } else if ((user != buf.st_uid) && 884 (!cron_admin(pwd->pw_name))) { 885 fprintf(stderr, gettext( 886 "at: you don't own %s\n"), argv[i]); 887 error = 1; 888 } else if (pflag && not_this_project(job_file)) { 889 continue; 890 } else { 891 if (!qflag || (qflag && 892 check_queue(ptr, queue))) { 893 ascftime(timebuf, FORMAT, 894 localtime(&t)); 895 printf("%s\t%s\n", argv[i], timebuf); 896 } 897 } 898 } 899 return (error); 900 } 901 902 /* 903 * open the command file and read the project id line 904 * compare to the project number provided via -p on the command line 905 * return 0 if they match, 1 if they don't match or an error occurs. 906 */ 907 #define SKIPCOUNT 3 /* lines to skip to get to project line in file */ 908 909 static int 910 not_this_project(char *filename) 911 { 912 FILE *fp; 913 projid_t sproj; 914 int i; 915 916 if ((fp = fopen(filename, "r")) == NULL) 917 return (1); 918 919 for (i = 0; i < SKIPCOUNT; i++) 920 fscanf(fp, "%*[^\n]\n"); 921 922 fscanf(fp, ": project: %d\n", &sproj); 923 fclose(fp); 924 925 return (sproj == project ? 0 : 1); 926 } 927 928 static int 929 check_queue(char *name, int queue) 930 { 931 if ((name[strlen(name) - 1] - 'a') == queue) 932 return (1); 933 else 934 return (0); 935 } 936 937 static time_t 938 parse_time(char *t) 939 { 940 int century = 0; 941 int seconds = 0; 942 char *p; 943 time_t when = 0; 944 struct tm tm; 945 946 /* 947 * time in the following format (defined by the touch(1) spec): 948 * [[CC]YY]MMDDhhmm[.SS] 949 */ 950 if ((p = strchr(t, '.')) != NULL) { 951 if (strchr(p+1, '.') != NULL) 952 atabort(BADTIME); 953 seconds = atoi_for2(p+1); 954 *p = '\0'; 955 } 956 957 memset(&tm, 0, sizeof (struct tm)); 958 when = time(0); 959 tm.tm_year = localtime(&when)->tm_year; 960 961 switch (strlen(t)) { 962 case 12: /* CCYYMMDDhhmm */ 963 century = atoi_for2(t); 964 t += 2; 965 case 10: /* YYMMDDhhmm */ 966 tm.tm_year = atoi_for2(t); 967 t += 2; 968 if (century == 0) { 969 if (tm.tm_year < 69) 970 tm.tm_year += 100; 971 } else 972 tm.tm_year += (century - 19) * 100; 973 case 8: /* MMDDhhmm */ 974 tm.tm_mon = atoi_for2(t) - 1; 975 t += 2; 976 tm.tm_mday = atoi_for2(t); 977 t += 2; 978 tm.tm_hour = atoi_for2(t); 979 t += 2; 980 tm.tm_min = atoi_for2(t); 981 t += 2; 982 tm.tm_sec = seconds; 983 break; 984 default: 985 atabort(BADTIME); 986 } 987 988 if ((when = mktime(&tm)) == -1) 989 atabort(BADTIME); 990 if (tm.tm_isdst) 991 when -= (timezone-altzone); 992 return (when); 993 } 994 995 static int 996 atoi_for2(char *p) { 997 int value; 998 999 value = (*p - '0') * 10 + *(p+1) - '0'; 1000 if ((value < 0) || (value > 99)) 1001 atabort(BADTIME); 1002 return (value); 1003 } 1004 1005 static void 1006 usage(void) 1007 { 1008 fprintf(stderr, USAGE); 1009 exit(1); 1010 } 1011