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  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 /*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T     */
  26 /*        All Rights Reserved   */
  27 
  28 /*
  29  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
  30  */
  31 
  32 #include <sys/types.h>
  33 #include <sys/stat.h>
  34 #include <sys/types.h>
  35 #include <sys/wait.h>
  36 #include <errno.h>
  37 #include <signal.h>
  38 #include <stdio.h>
  39 #include <stdlib.h>
  40 #include <string.h>
  41 #include <fcntl.h>
  42 #include <ctype.h>
  43 #include <pwd.h>
  44 #include <unistd.h>
  45 #include <locale.h>
  46 #include <nl_types.h>
  47 #include <langinfo.h>
  48 #include <libintl.h>
  49 #include <security/pam_appl.h>
  50 #include <limits.h>
  51 #include <libzoneinfo.h>
  52 #include "cron.h"
  53 #include "getresponse.h"
  54 
  55 #if defined(XPG4)
  56 #define VIPATH  "/usr/xpg4/bin/vi"
  57 #elif defined(XPG6)
  58 #define VIPATH  "/usr/xpg6/bin/vi"
  59 #else
  60 #define _XPG_NOTDEFINED
  61 #define VIPATH  "vi"
  62 #endif
  63 
  64 #define TMPFILE         "_cron"         /* prefix for tmp file */
  65 #define CRMODE          0600    /* mode for creating crontabs */
  66 
  67 #define BADCREATE       \
  68         "can't create your crontab file in the crontab directory."
  69 #define BADOPEN         "can't open your crontab file."
  70 #define BADSHELL        \
  71         "because your login shell isn't /usr/bin/sh, you can't use cron."
  72 #define WARNSHELL       "warning: commands will be executed using /usr/bin/sh\n"
  73 #define BADUSAGE        \
  74         "usage:\n"                      \
  75         "\tcrontab [-u username] [file]\n"              \
  76         "\tcrontab [-u username] { -e | -l | -r }\n"    \
  77         "\tcrontab { -e | -l | -r } [username]"
  78 #define INVALIDUSER     "you are not a valid user (no entry in /etc/passwd)."
  79 #define NOTALLOWED      "you are not authorized to use cron.  Sorry."
  80 #define NOTROOT         \
  81         "you must be super-user to access another user's crontab file"
  82 #define AUDITREJECT     "The audit context for your shell has not been set."
  83 #define EOLN            "unexpected end of line."
  84 #define UNEXPECT        "unexpected character found in line."
  85 #define OUTOFBOUND      "number out of bounds."
  86 #define OVERFLOW        "too many elements."
  87 #define ERRSFND         "errors detected in input, no crontab file generated."
  88 #define ED_ERROR        \
  89         "     The editor indicates that an error occurred while you were\n"\
  90         "     editing the crontab data - usually a minor typing error.\n\n"
  91 #define BADREAD         "error reading your crontab file"
  92 #define ED_PROMPT       \
  93         "     Edit again, to ensure crontab information is intact (%s/%s)?\n"\
  94         "     ('%s' will discard edits.)"
  95 #define NAMETOOLONG     "login name too long"
  96 #define BAD_TZ  "Timezone unrecognized in: %s"
  97 #define BAD_SHELL       "Invalid shell specified: %s"
  98 #define BAD_HOME        "Unable to access directory: %s\t%s\n"
  99 
 100 extern int      per_errno;
 101 
 102 extern int      audit_crontab_modify(char *, char *, int);
 103 extern int      audit_crontab_delete(char *, int);
 104 extern int      audit_crontab_not_allowed(uid_t, char *);
 105 
 106 int             err;
 107 int             cursor;
 108 char            *cf;
 109 char            *tnam;
 110 char            edtemp[5+13+1];
 111 char            line[CTLINESIZE];
 112 static          char    login[UNAMESIZE];
 113 
 114 static void     catch(int);
 115 static void     crabort(char *);
 116 static void     cerror(char *);
 117 static void     copycron(FILE *);
 118 
 119 int
 120 main(int argc, char **argv)
 121 {
 122         int     c, r;
 123         int     rflag   = 0;
 124         int     lflag   = 0;
 125         int     eflag   = 0;
 126         int     errflg  = 0;
 127         char *pp;
 128         FILE *fp, *tmpfp;
 129         struct stat stbuf;
 130         struct passwd *pwp;
 131         time_t omodtime;
 132         char *editor;
 133         uid_t ruid;
 134         pid_t pid;
 135         int stat_loc;
 136         int ret;
 137         char real_login[UNAMESIZE];
 138         char *user = NULL;
 139         int tmpfd = -1;
 140         pam_handle_t *pamh;
 141         int pam_error;
 142         char *buf;
 143         size_t buflen;
 144 
 145         (void) setlocale(LC_ALL, "");
 146 #if !defined(TEXT_DOMAIN)       /* Should be defined by cc -D */
 147 #define TEXT_DOMAIN "SYS_TEST"  /* Use this only if it wasn't */
 148 #endif
 149         (void) textdomain(TEXT_DOMAIN);
 150 
 151         if (init_yes() < 0) {
 152                 (void) fprintf(stderr, gettext(ERR_MSG_INIT_YES),
 153                     strerror(errno));
 154                 exit(1);
 155         }
 156 
 157         while ((c = getopt(argc, argv, "elru:")) != EOF) {
 158                 switch (c) {
 159                         case 'e':
 160                                 eflag++;
 161                                 break;
 162                         case 'l':
 163                                 lflag++;
 164                                 break;
 165                         case 'r':
 166                                 rflag++;
 167                                 break;
 168                         case 'u':
 169                                 user = optarg;
 170                                 break;
 171                         case '?':
 172                                 errflg++;
 173                                 break;
 174                 }
 175         }
 176 
 177         argc -= optind;
 178         argv += optind;
 179 
 180         if (eflag + lflag + rflag > 1)
 181                 errflg++;
 182 
 183         if ((eflag || lflag || rflag) && argc > 0) {
 184                 if (user != NULL)
 185                         errflg++;
 186                 else
 187                         user = *argv;
 188         }
 189 
 190         if (errflg || argc > 1)
 191                 crabort(BADUSAGE);
 192 
 193         ruid = getuid();
 194         if ((pwp = getpwuid(ruid)) == NULL)
 195                 crabort(INVALIDUSER);
 196 
 197         if (strlcpy(real_login, pwp->pw_name, sizeof (real_login))
 198             >= sizeof (real_login)) {
 199                 crabort(NAMETOOLONG);
 200         }
 201 
 202         if (user != NULL) {
 203                 if ((pwp = getpwnam(user)) == NULL)
 204                         crabort(INVALIDUSER);
 205 
 206                 if (!cron_admin(real_login)) {
 207                         if (pwp->pw_uid != ruid)
 208                                 crabort(NOTROOT);
 209                         else
 210                                 pp = getuser(ruid);
 211                 } else {
 212                         pp = user;
 213                 }
 214         } else {
 215                 pp = getuser(ruid);
 216         }
 217 
 218         if (pp == NULL) {
 219                 if (per_errno == 2)
 220                         crabort(BADSHELL);
 221                 else
 222                         crabort(INVALIDUSER);
 223         }
 224         if (strlcpy(login, pp, sizeof (login)) >= sizeof (login))
 225                 crabort(NAMETOOLONG);
 226         if (!allowed(login, CRONALLOW, CRONDENY))
 227                 crabort(NOTALLOWED);
 228 
 229         /* Do account validation check */
 230         pam_error = pam_start("cron", pp, NULL, &pamh);
 231         if (pam_error != PAM_SUCCESS) {
 232                 crabort((char *)pam_strerror(pamh, pam_error));
 233         }
 234         pam_error = pam_acct_mgmt(pamh, PAM_SILENT);
 235         if (pam_error != PAM_SUCCESS) {
 236                 (void) fprintf(stderr, gettext("Warning - Invalid account: "
 237                     "'%s' not allowed to execute cronjobs\n"), pp);
 238         }
 239         (void) pam_end(pamh, PAM_SUCCESS);
 240 
 241 
 242         /* check for unaudited shell */
 243         if (audit_crontab_not_allowed(ruid, pp))
 244                 crabort(AUDITREJECT);
 245 
 246         cf = xmalloc(strlen(CRONDIR)+strlen(login)+2);
 247         strcat(strcat(strcpy(cf, CRONDIR), "/"), login);
 248 
 249         if (rflag) {
 250                 r = unlink(cf);
 251                 cron_sendmsg(DELETE, login, login, CRON);
 252                 audit_crontab_delete(cf, r);
 253                 exit(0);
 254         }
 255         if (lflag) {
 256                 if ((fp = fopen(cf, "r")) == NULL)
 257                         crabort(BADOPEN);
 258                 while (fgets(line, CTLINESIZE, fp) != NULL)
 259                         fputs(line, stdout);
 260                 fclose(fp);
 261                 exit(0);
 262         }
 263         if (eflag) {
 264                 if ((fp = fopen(cf, "r")) == NULL) {
 265                         if (errno != ENOENT)
 266                                 crabort(BADOPEN);
 267                 }
 268                 (void) strcpy(edtemp, "/tmp/crontabXXXXXX");
 269                 tmpfd = mkstemp(edtemp);
 270                 if (fchown(tmpfd, ruid, -1) == -1) {
 271                         (void) close(tmpfd);
 272                         crabort("fchown of temporary file failed");
 273                 }
 274                 (void) close(tmpfd);
 275                 /*
 276                  * Fork off a child with user's permissions,
 277                  * to edit the crontab file
 278                  */
 279                 if ((pid = fork()) == (pid_t)-1)
 280                         crabort("fork failed");
 281                 if (pid == 0) {         /* child process */
 282                         /* give up super-user privileges. */
 283                         setuid(ruid);
 284                         if ((tmpfp = fopen(edtemp, "w")) == NULL)
 285                                 crabort("can't create temporary file");
 286                         if (fp != NULL) {
 287                                 /*
 288                                  * Copy user's crontab file to temporary file.
 289                                  */
 290                                 while (fgets(line, CTLINESIZE, fp) != NULL) {
 291                                         fputs(line, tmpfp);
 292                                         if (ferror(tmpfp)) {
 293                                                 fclose(fp);
 294                                                 fclose(tmpfp);
 295                                                 crabort("write error on"
 296                                                     "temporary file");
 297                                         }
 298                                 }
 299                                 if (ferror(fp)) {
 300                                         fclose(fp);
 301                                         fclose(tmpfp);
 302                                         crabort(BADREAD);
 303                                 }
 304                                 fclose(fp);
 305                         }
 306                         if (fclose(tmpfp) == EOF)
 307                                 crabort("write error on temporary file");
 308                         if (stat(edtemp, &stbuf) < 0)
 309                                 crabort("can't stat temporary file");
 310                         omodtime = stbuf.st_mtime;
 311 #ifdef _XPG_NOTDEFINED
 312                         editor = getenv("VISUAL");
 313                         if (editor == NULL) {
 314 #endif
 315                                 editor = getenv("EDITOR");
 316                                 if (editor == NULL)
 317                                         editor = VIPATH;
 318 #ifdef _XPG_NOTDEFINED
 319                         }
 320 #endif
 321                         buflen = strlen(editor) + strlen(edtemp) + 2;
 322                         buf = xmalloc(buflen);
 323                         (void) snprintf(buf, buflen, "%s %s", editor, edtemp);
 324 
 325                         sleep(1);
 326 
 327                         while (1) {
 328                                 ret = system(buf);
 329 
 330                                 /* sanity checks */
 331                                 if ((tmpfp = fopen(edtemp, "r")) == NULL)
 332                                         crabort("can't open temporary file");
 333                                 if (fstat(fileno(tmpfp), &stbuf) < 0)
 334                                         crabort("can't stat temporary file");
 335                                 if (stbuf.st_size == 0)
 336                                         crabort("temporary file empty");
 337                                 if (omodtime == stbuf.st_mtime) {
 338                                         (void) unlink(edtemp);
 339                                         fprintf(stderr, gettext(
 340                                             "The crontab file was not"
 341                                             " changed.\n"));
 342                                         exit(1);
 343                                 }
 344                                 if ((ret) && (errno != EINTR)) {
 345                                         /*
 346                                          * Some editors (like 'vi') can return
 347                                          * a non-zero exit status even though
 348                                          * everything is okay. Need to check.
 349                                          */
 350                                         fprintf(stderr, gettext(ED_ERROR));
 351                                         fflush(stderr);
 352                                         if (isatty(fileno(stdin))) {
 353                                                 /* Interactive */
 354                                                 fprintf(stdout,
 355                                                     gettext(ED_PROMPT),
 356                                                     yesstr, nostr, nostr);
 357                                                 fflush(stdout);
 358 
 359                                                 if (yes()) {
 360                                                         /* Edit again */
 361                                                         continue;
 362                                                 } else {
 363                                                         /* Dump changes */
 364                                                         (void) unlink(edtemp);
 365                                                         exit(1);
 366                                                 }
 367                                         } else {
 368                                                 /*
 369                                                  * Non-interactive, dump changes
 370                                                  */
 371                                                 (void) unlink(edtemp);
 372                                                 exit(1);
 373                                         }
 374                                 }
 375                                 exit(0);
 376                         } /* while (1) */
 377                 }
 378 
 379                 /* fix for 1125555 - ignore common signals while waiting */
 380                 (void) signal(SIGINT, SIG_IGN);
 381                 (void) signal(SIGHUP, SIG_IGN);
 382                 (void) signal(SIGQUIT, SIG_IGN);
 383                 (void) signal(SIGTERM, SIG_IGN);
 384                 wait(&stat_loc);
 385                 if ((stat_loc & 0xFF00) != 0)
 386                         exit(1);
 387 
 388                 /*
 389                  * unlink edtemp as 'ruid'. The file contents will be held
 390                  * since we open the file descriptor 'tmpfp' before calling
 391                  * unlink.
 392                  */
 393                 if (((ret = seteuid(ruid)) < 0) ||
 394                     ((tmpfp = fopen(edtemp, "r")) == NULL) ||
 395                     (unlink(edtemp) == -1)) {
 396                         fprintf(stderr, "crontab: %s: %s\n",
 397                             edtemp, errmsg(errno));
 398                         if ((ret < 0) || (tmpfp == NULL))
 399                                 (void) unlink(edtemp);
 400                         exit(1);
 401                 } else
 402                         seteuid(0);
 403 
 404                 copycron(tmpfp);
 405         } else {
 406                 if (argc == 0)
 407                         copycron(stdin);
 408                 else if (seteuid(getuid()) != 0 || (fp = fopen(argv[0], "r"))
 409                     == NULL)
 410                         crabort(BADOPEN);
 411                 else {
 412                         seteuid(0);
 413                         copycron(fp);
 414                 }
 415         }
 416         cron_sendmsg(ADD, login, login, CRON);
 417 /*
 418  *      if (per_errno == 2)
 419  *              fprintf(stderr, gettext(WARNSHELL));
 420  */
 421         return (0);
 422 }
 423 
 424 static void
 425 copycron(FILE *fp)
 426 {
 427         FILE *tfp;
 428         char pid[6], *tnam_end;
 429         int t;
 430         char buf[LINE_MAX];
 431         cferror_t cferr;
 432 
 433         sprintf(pid, "%-5d", getpid());
 434         tnam = xmalloc(strlen(CRONDIR)+strlen(TMPFILE)+7);
 435         strcat(strcat(strcat(strcpy(tnam, CRONDIR), "/"), TMPFILE), pid);
 436         /* cut trailing blanks */
 437         tnam_end = strchr(tnam, ' ');
 438         if (tnam_end != NULL)
 439                 *tnam_end = 0;
 440         /* catch SIGINT, SIGHUP, SIGQUIT signals */
 441         if (signal(SIGINT, catch) == SIG_IGN)
 442                 signal(SIGINT, SIG_IGN);
 443         if (signal(SIGHUP, catch) == SIG_IGN) signal(SIGHUP, SIG_IGN);
 444         if (signal(SIGQUIT, catch) == SIG_IGN) signal(SIGQUIT, SIG_IGN);
 445         if (signal(SIGTERM, catch) == SIG_IGN) signal(SIGTERM, SIG_IGN);
 446         if ((t = creat(tnam, CRMODE)) == -1) crabort(BADCREATE);
 447         if ((tfp = fdopen(t, "w")) == NULL) {
 448                 unlink(tnam);
 449                 crabort(BADCREATE);
 450         }
 451         err = 0;        /* if errors found, err set to 1 */
 452         while (fgets(line, CTLINESIZE, fp) != NULL) {
 453                 cursor = 0;
 454                 while (line[cursor] == ' ' || line[cursor] == '\t')
 455                         cursor++;
 456                 /* fix for 1039689 - treat blank line like a comment */
 457                 if (line[cursor] == '#' || line[cursor] == '\n')
 458                         goto cont;
 459 
 460                 if (strncmp(&line[cursor], ENV_TZ, strlen(ENV_TZ)) == 0) {
 461                         char *x;
 462 
 463                         strncpy(buf, &line[cursor + strlen(ENV_TZ)],
 464                             sizeof (buf));
 465                         if ((x = strchr(buf, '\n')) != NULL)
 466                                 *x = '\0';
 467 
 468                         if (isvalid_tz(buf, NULL, _VTZ_ALL)) {
 469                                 goto cont;
 470                         } else {
 471                                 err = 1;
 472                                 fprintf(stderr, BAD_TZ, &line[cursor]);
 473                                 continue;
 474                         }
 475                 } else if (strncmp(&line[cursor], ENV_SHELL,
 476                     strlen(ENV_SHELL)) == 0) {
 477                         char *x;
 478 
 479                         strncpy(buf, &line[cursor + strlen(ENV_SHELL)],
 480                             sizeof (buf));
 481                         if ((x = strchr(buf, '\n')) != NULL)
 482                                 *x = '\0';
 483 
 484                         if (isvalid_shell(buf)) {
 485                                 goto cont;
 486                         } else {
 487                                 err = 1;
 488                                 fprintf(stderr, BAD_SHELL, &line[cursor]);
 489                                 continue;
 490                         }
 491                 } else if (strncmp(&line[cursor], ENV_HOME,
 492                     strlen(ENV_HOME)) == 0) {
 493                         char *x;
 494 
 495                         strncpy(buf, &line[cursor + strlen(ENV_HOME)],
 496                             sizeof (buf));
 497                         if ((x = strchr(buf, '\n')) != NULL)
 498                                 *x = '\0';
 499                         if (chdir(buf) == 0) {
 500                                 goto cont;
 501                         } else {
 502                                 err = 1;
 503                                 fprintf(stderr, BAD_HOME, &line[cursor],
 504                                     strerror(errno));
 505                                 continue;
 506                         }
 507                 }
 508 
 509                 if ((cferr = next_field(0, 59, line, &cursor, NULL)) != CFOK ||
 510                     (cferr = next_field(0, 23, line, &cursor, NULL)) != CFOK ||
 511                     (cferr = next_field(1, 31, line, &cursor, NULL)) != CFOK ||
 512                     (cferr = next_field(1, 12, line, &cursor, NULL)) != CFOK ||
 513                     (cferr = next_field(0, 6, line, &cursor, NULL)) != CFOK) {
 514                         switch (cferr) {
 515                         case CFEOLN:
 516                                 cerror(EOLN);
 517                                 break;
 518                         case CFUNEXPECT:
 519                                 cerror(UNEXPECT);
 520                                 break;
 521                         case CFOUTOFBOUND:
 522                                 cerror(OUTOFBOUND);
 523                                 break;
 524                         case CFEOVERFLOW:
 525                                 cerror(OVERFLOW);
 526                                 break;
 527                         case CFENOMEM:
 528                                 (void) fprintf(stderr, "Out of memory\n");
 529                                 exit(55);
 530                                 break;
 531                         default:
 532                                 break;
 533                         }
 534                         continue;
 535                 }
 536 
 537                 if (line[++cursor] == '\0') {
 538                         cerror(EOLN);
 539                         continue;
 540                 }
 541 cont:
 542                 if (fputs(line, tfp) == EOF) {
 543                         unlink(tnam);
 544                         crabort(BADCREATE);
 545                 }
 546         }
 547         fclose(fp);
 548         fclose(tfp);
 549 
 550         /* audit differences between old and new crontabs */
 551         audit_crontab_modify(cf, tnam, err);
 552 
 553         if (!err) {
 554                 /* make file tfp the new crontab */
 555                 unlink(cf);
 556                 if (link(tnam, cf) == -1) {
 557                         unlink(tnam);
 558                         crabort(BADCREATE);
 559                 }
 560         } else {
 561                 crabort(ERRSFND);
 562         }
 563         unlink(tnam);
 564 }
 565 
 566 static void
 567 cerror(char *msg)
 568 {
 569         fprintf(stderr, gettext("%scrontab: error on previous line; %s\n"),
 570             line, msg);
 571         err = 1;
 572 }
 573 
 574 
 575 static void
 576 catch(int x)
 577 {
 578         unlink(tnam);
 579         exit(1);
 580 }
 581 
 582 static void
 583 crabort(char *msg)
 584 {
 585         int sverrno;
 586 
 587         if (strcmp(edtemp, "") != 0) {
 588                 sverrno = errno;
 589                 (void) unlink(edtemp);
 590                 errno = sverrno;
 591         }
 592         if (tnam != NULL) {
 593                 sverrno = errno;
 594                 (void) unlink(tnam);
 595                 errno = sverrno;
 596         }
 597         fprintf(stderr, "crontab: %s\n", gettext(msg));
 598         exit(1);
 599 }