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 }