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 }