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