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