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