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 static 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 } 600