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