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