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 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         char *shell;
 557         char    dirbuf[PATH_MAX + 1];
 558         char    line[LINE_MAX];
 559         char **ep;
 560         mode_t um;
 561         char *val;
 562         extern char **environ;
 563         uid_t realusr, effeusr;
 564         int ttyinput;
 565         int ulimit_flag = 0;
 566         struct rlimit rlp;
 567         struct project prj, *pprj;
 568         char pbuf[PROJECT_BUFSZ];
 569         char pbuf2[PROJECT_BUFSZ];
 570         char *user;
 571 
 572         /*
 573          * Fix for 1099381:
 574          * If the inputfile is from a tty, then turn on prompting, and
 575          * put out a prompt now, instead of waiting for a lot of file
 576          * activity to complete.
 577          */
 578         ttyinput = isatty(fileno(inputfile));
 579         if (ttyinput) {
 580                 fputs("at> ", stderr);
 581                 fflush(stderr);
 582         }
 583 
 584         /*
 585          * Fix for 1053807:
 586          * Determine what shell we should use to run the job. If the user
 587          * didn't explicitly request that his/her current shell be over-
 588          * ridden (shflag or cshflag), then we use the current shell.
 589          */
 590         if (cshflag)
 591                 Shell = shell = "/bin/csh";
 592         else if (kshflag) {
 593                 Shell = shell = "/bin/ksh";
 594                 ulimit_flag = 1;
 595         } else if (shflag) {
 596                 Shell = shell = "/bin/sh";
 597                 ulimit_flag = 1;
 598         } else if (((Shell = val = getenv("SHELL")) != NULL) &&
 599             (*val != '\0')) {
 600                 shell = "$SHELL";
 601                 if ((strstr(val, "/sh") != NULL) ||
 602                     (strstr(val, "/ksh") != NULL))
 603                         ulimit_flag = 1;
 604         } else {
 605                 /* SHELL is NULL or unset, therefore use default */
 606                 Shell = shell = _PATH_BSHELL;
 607                 ulimit_flag = 1;
 608         }
 609 
 610         printf(": %s job\n", jobtype ? "batch" : "at");
 611         printf(": jobname: %.127s\n", (jobfile == NULL) ? "stdin" : jobfile);
 612         printf(": notify by mail: %s\n", (mflag) ? "yes" : "no");
 613 
 614         if (pflag) {
 615                 (void) printf(": project: %d\n", project);
 616         } else {
 617                 /*
 618                  * Check if current user is a member of current project.
 619                  * This check is done here to avoid setproject() failure
 620                  * later when the job gets executed.  If current user does
 621                  * not belong to current project, user's default project
 622                  * will be used instead.  This is achieved by not specifying
 623                  * the project (": project: <project>\n") in the job file.
 624                  */
 625                 if ((user = getuser(getuid())) == NULL)
 626                         atabort(INVALIDUSER);
 627                 project = getprojid();
 628                 pprj = getprojbyid(project, &prj, pbuf, sizeof (pbuf));
 629                 if (pprj != NULL) {
 630                         if (inproj(user, pprj->pj_name, pbuf2, sizeof (pbuf2)))
 631                                 (void) printf(": project: %d\n", project);
 632                 }
 633         }
 634 
 635         for (ep = environ; *ep; ep++) {
 636                 if (strchr(*ep, '\'') != NULL)
 637                         continue;
 638                 if ((val = strchr(*ep, '=')) == NULL)
 639                         continue;
 640                 *val++ = '\0';
 641                 printf("export %s; %s='%s'\n", *ep, *ep, val);
 642                 *--val = '=';
 643         }
 644         if ((pfp = fopen(pname1, "r")) == NULL &&
 645             (pfp = fopen(pname, "r")) == NULL)
 646                 atabort("no prototype");
 647         /*
 648          * Put in a line to run the proper shell using the rest of
 649          * the file as input.  Note that 'exec'ing the shell will
 650          * cause sh() to leave a /tmp/sh### file around. (1053807)
 651          */
 652         printf("%s << '...the rest of this file is shell input'\n", shell);
 653 
 654         um = umask(0);
 655         while ((c = getc(pfp)) != EOF) {
 656                 if (c != '$')
 657                         putchar(c);
 658                 else switch (c =  getc(pfp)) {
 659                 case EOF:
 660                         goto out;
 661                 case 'd':
 662                         /*
 663                          * Must obtain current working directory as the user
 664                          */
 665 
 666                         dirbuf[0] = '\0';
 667                         realusr = getuid();
 668                         effeusr = geteuid();
 669                         /* change euid for getcwd */
 670                         if (seteuid(realusr) < 0) {
 671                                 atabort(CANTCHUID);
 672                         }
 673                         if (getcwd(dirbuf, sizeof (dirbuf)) == NULL) {
 674                                 atabort(
 675                                     "can't obtain current working directory");
 676                         }
 677                         /* change back afterwards */
 678                         if (seteuid(effeusr) < 0) {
 679                                 atabort(CANTCHUID);
 680                         }
 681                         printf("%s", dirbuf);
 682                         break;
 683                 case 'm':
 684                         printf("%o", um);
 685                         break;
 686                 case '<':
 687                         if (ulimit_flag) {
 688                                 if (getrlimit(RLIMIT_FSIZE, &rlp) == 0) {
 689                                         if (rlp.rlim_cur == RLIM_INFINITY)
 690                                                 printf("ulimit unlimited\n");
 691                                         else
 692                                                 printf("ulimit %lld\n",
 693                                                     rlp.rlim_cur / 512);
 694                                 }
 695                         }
 696                         /*
 697                          * fix for 1113572 - use fputs() so that a
 698                          * newline isn't appended to the one returned
 699                          * with fgets(); 1099381 - prompt for input.
 700                          */
 701                         while (fgets(line, LINE_MAX, inputfile) != NULL) {
 702                                 fputs(line, stdout);
 703                                 if (ttyinput)
 704                                         fputs("at> ", stderr);
 705                         }
 706                         if (ttyinput) /* clean up the final output */
 707                                 fputs("<EOT>\n", stderr);
 708                         break;
 709                 case 't':
 710                         printf(":%lu", when);
 711                         break;
 712                 default:
 713                         putchar(c);
 714                 }
 715         }
 716 out:
 717         fclose(pfp);
 718         fflush(NULL);
 719 }
 720 
 721 static int
 722 remove_jobs(int argc, char **argv, char *login)
 723 /* remove jobs that are specified */
 724 {
 725         int             i, r;
 726         int             error = 0;
 727         struct stat     buf;
 728         struct passwd *pw;
 729 
 730         pw = getpwuid(user);
 731         if (pw == NULL) {
 732                 atabort("Invalid user.\n");
 733         }
 734 
 735         if (argc == 0)
 736                 usage();
 737         if (chdir(ATDIR) == -1)
 738                 atabort(CANTCD);
 739         for (i = 0; i < argc; i++)
 740                 if (strchr(argv[i], '/') != NULL) {
 741                         fprintf(stderr, "at: %s: not a valid job-id\n",
 742                                         argv[i]);
 743                 } else if (stat(argv[i], &buf)) {
 744                         fprintf(stderr, "at: %s: ", argv[i]);
 745                         perror("");
 746                 } else if ((user != buf.st_uid) &&
 747                     (!cron_admin(pw->pw_name))) {
 748                         fprintf(stderr, "at: you don't own %s\n",
 749                             argv[i]);
 750                         error = 1;
 751                 } else {
 752                         if (cron_admin(pw->pw_name)) {
 753                                 login = getuser((uid_t)buf.st_uid);
 754                                 if (login == NULL) {
 755                                         if (per_errno == 2)
 756                                                 atabort(BADSHELL);
 757                                         else
 758                                                 atabort(INVALIDUSER);
 759                                         }
 760                         }
 761                         cron_sendmsg(DELETE, login, argv[i], AT);
 762                         r = unlink(argv[i]);
 763                         audit_at_delete(argv[i], ATDIR, r);
 764                 }
 765         return (error);
 766 }
 767 
 768 
 769 
 770 static int
 771 list_jobs(int argc, char **argv, int qflag, int queue)
 772 {
 773         DIR             *dir;
 774         int             i;
 775         int             error = 0;
 776         char            *patdir, *atdir, *ptr;
 777         char            timebuf[80];
 778         time_t          t;
 779         struct stat     buf, st1, st2;
 780         struct dirent   *dentry;
 781         struct passwd   *pw;
 782         unsigned int    atdirlen;
 783         int r;
 784         struct passwd   *pwd, pwds;
 785         char buf_pwd[1024];
 786         char job_file[PATH_MAX];
 787 
 788         pwd = getpwuid_r(user, &pwds, buf_pwd, sizeof (buf_pwd));
 789         if (pwd == NULL) {
 790                 atabort("Invalid user.\n");
 791         }
 792 
 793         /* list jobs for user */
 794         if (chdir(ATDIR) == -1)
 795                 atabort(CANTCD);
 796 
 797         atdirlen = strlen(ATDIR);
 798         atdir = xmalloc(atdirlen + 1);
 799         strcpy(atdir, ATDIR);
 800         patdir = strrchr(atdir, '/');
 801         *patdir = '\0';
 802         if (argc == 0) {
 803                 /* list all jobs for a user */
 804                 if (stat(ATDIR, &st1) != 0 || stat(atdir, &st2) != 0)
 805                         atabort("Can not get status of spooling"
 806                             "directory for at");
 807                 if ((dir = opendir(ATDIR)) == NULL)
 808                         atabort(NOOPENDIR);
 809                 while (1) {
 810                         if ((dentry = readdir(dir)) == NULL)
 811                                 break;
 812                         if ((dentry->d_ino == st1.st_ino) ||
 813                             (dentry->d_ino == st2.st_ino))
 814                                 continue;
 815                         if ((r = audit_cron_is_anc_name(dentry->d_name)) == 1)
 816                                 continue;
 817                         if (stat(dentry->d_name, &buf)) {
 818                                 unlink(dentry->d_name);
 819                                 audit_cron_delete_anc_file(dentry->d_name,
 820                                     NULL);
 821                                 continue;
 822                         }
 823                         if ((!cron_admin(pwd->pw_name)) &&
 824                             (buf.st_uid != user))
 825                                 continue;
 826                         ptr = dentry->d_name;
 827                         if (((t = num(&ptr)) == 0) || (*ptr != '.'))
 828                                 continue;
 829                         strcpy(job_file, patdir);
 830                         strcat(job_file, dentry->d_name);
 831                         if (pflag && not_this_project(job_file))
 832                                 continue;
 833                         ascftime(timebuf, FORMAT, localtime(&t));
 834                         if ((cron_admin(pwd->pw_name)) &&
 835                             ((pw = getpwuid(buf.st_uid)) != NULL)) {
 836                                 if (!qflag || (qflag &&
 837                                     check_queue(ptr, queue)))
 838                                         printf("user = %s\t%s\t%s\n",
 839                                             pw->pw_name, dentry->d_name,
 840                                             timebuf);
 841                         } else
 842                                 if (!qflag || (qflag &&
 843                                     check_queue(ptr, queue)))
 844                                         printf("%s\t%s\n",
 845                                             dentry->d_name, timebuf);
 846                 }
 847                 (void) closedir(dir);
 848         } else  /* list particular jobs for user */
 849                 for (i = 0; i < argc; i++) {
 850                         ptr = argv[i];
 851                         strlcpy(job_file, patdir, PATH_MAX);
 852                         strlcat(job_file, ptr, PATH_MAX);
 853                         if (((t = num(&ptr)) == 0) || (*ptr != '.')) {
 854                                 fprintf(stderr, gettext(
 855                                     "at: invalid job name %s\n"), argv[i]);
 856                                 error = 1;
 857                         } else if (stat(argv[i], &buf)) {
 858                                 fprintf(stderr, "at: %s: ", argv[i]);
 859                                 perror("");
 860                                 error = 1;
 861                         } else if ((user != buf.st_uid) &&
 862                             (!cron_admin(pwd->pw_name))) {
 863                                 fprintf(stderr, gettext(
 864                                     "at: you don't own %s\n"), argv[i]);
 865                                 error = 1;
 866                         } else if (pflag && not_this_project(job_file)) {
 867                                 continue;
 868                         } else {
 869                                 if (!qflag || (qflag &&
 870                                     check_queue(ptr, queue))) {
 871                                         ascftime(timebuf, FORMAT,
 872                                             localtime(&t));
 873                                         printf("%s\t%s\n", argv[i], timebuf);
 874                                 }
 875                         }
 876                 }
 877         return (error);
 878 }
 879 
 880 /*
 881  * open the command file and read the project id line
 882  * compare to the project number provided via -p on the command line
 883  * return 0 if they match, 1 if they don't match or an error occurs.
 884  */
 885 #define SKIPCOUNT 3     /* lines to skip to get to project line in file */
 886 
 887 static int
 888 not_this_project(char *filename)
 889 {
 890         FILE *fp;
 891         projid_t sproj;
 892         int i;
 893 
 894         if ((fp = fopen(filename, "r")) == NULL)
 895                 return (1);
 896 
 897         for (i = 0; i < SKIPCOUNT; i++)
 898                 fscanf(fp, "%*[^\n]\n");
 899 
 900         fscanf(fp, ": project: %d\n", &sproj);
 901         fclose(fp);
 902 
 903         return (sproj == project ? 0 : 1);
 904 }
 905 
 906 static int
 907 check_queue(char *name, int queue)
 908 {
 909         if ((name[strlen(name) - 1] - 'a') == queue)
 910                 return (1);
 911         else
 912                 return (0);
 913 }
 914 
 915 static time_t
 916 parse_time(char *t)
 917 {
 918         int             century = 0;
 919         int             seconds = 0;
 920         char            *p;
 921         time_t          when    = 0;
 922         struct tm       tm;
 923 
 924         /*
 925          * time in the following format (defined by the touch(1) spec):
 926          *      [[CC]YY]MMDDhhmm[.SS]
 927          */
 928         if ((p = strchr(t, '.')) != NULL) {
 929                 if (strchr(p+1, '.') != NULL)
 930                         atabort(BADTIME);
 931                 seconds = atoi_for2(p+1);
 932                 *p = '\0';
 933         }
 934 
 935         memset(&tm, 0, sizeof (struct tm));
 936         when = time(0);
 937         tm.tm_year = localtime(&when)->tm_year;
 938 
 939         switch (strlen(t)) {
 940                 case 12:        /* CCYYMMDDhhmm */
 941                         century = atoi_for2(t);
 942                         t += 2;
 943                 case 10:        /* YYMMDDhhmm */
 944                         tm.tm_year = atoi_for2(t);
 945                         t += 2;
 946                         if (century == 0) {
 947                                 if (tm.tm_year < 69)
 948                                         tm.tm_year += 100;
 949                         } else
 950                                 tm.tm_year += (century - 19) * 100;
 951                 case 8:         /* MMDDhhmm */
 952                         tm.tm_mon = atoi_for2(t) - 1;
 953                         t += 2;
 954                         tm.tm_mday = atoi_for2(t);
 955                         t += 2;
 956                         tm.tm_hour = atoi_for2(t);
 957                         t += 2;
 958                         tm.tm_min = atoi_for2(t);
 959                         t += 2;
 960                         tm.tm_sec = seconds;
 961                         break;
 962                 default:
 963                         atabort(BADTIME);
 964         }
 965 
 966         if ((when = mktime(&tm)) == -1)
 967                 atabort(BADTIME);
 968         if (tm.tm_isdst)
 969                 when -= (timezone-altzone);
 970         return (when);
 971 }
 972 
 973 static int
 974 atoi_for2(char *p) {
 975         int value;
 976 
 977         value = (*p - '0') * 10 + *(p+1) - '0';
 978         if ((value < 0) || (value > 99))
 979                 atabort(BADTIME);
 980         return (value);
 981 }
 982 
 983 static void
 984 usage(void)
 985 {
 986         fprintf(stderr, USAGE);
 987         exit(1);
 988 }