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