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