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 main(argc, argv) 105 int argc; 106 char **argv; 107 { 108 int c, r; 109 int rflag = 0; 110 int lflag = 0; 111 int eflag = 0; 112 int errflg = 0; 113 char *pp; 114 FILE *fp, *tmpfp; 115 struct stat stbuf; 116 struct passwd *pwp; 117 time_t omodtime; 118 char *editor; 119 char buf[BUFSIZ]; 120 uid_t ruid; 121 pid_t pid; 122 int stat_loc; 123 int ret; 124 char real_login[UNAMESIZE]; 125 int tmpfd = -1; 126 pam_handle_t *pamh; 127 int pam_error; 128 129 (void) setlocale(LC_ALL, ""); 130 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 131 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 132 #endif 133 (void) textdomain(TEXT_DOMAIN); 134 yeschr = *nl_langinfo(YESSTR); 135 nochr = *nl_langinfo(NOSTR); 136 137 while ((c = getopt(argc, argv, "elr")) != EOF) 138 switch (c) { 139 case 'e': 140 eflag++; 141 break; 142 case 'l': 143 lflag++; 144 break; 145 case 'r': 146 rflag++; 147 break; 148 case '?': 149 errflg++; 150 break; 151 } 152 153 if (eflag + lflag + rflag > 1) 154 errflg++; 155 156 argc -= optind; 157 argv += optind; 158 if (errflg || argc > 1) 159 crabort(BADUSAGE); 160 161 ruid = getuid(); 162 if ((pwp = getpwuid(ruid)) == NULL) 163 crabort(INVALIDUSER); 164 165 if (strlcpy(real_login, pwp->pw_name, sizeof (real_login)) 166 >= sizeof (real_login)) 167 crabort(NAMETOOLONG); 168 169 if ((eflag || lflag || rflag) && argc == 1) { 170 if ((pwp = getpwnam(*argv)) == NULL) 171 crabort(INVALIDUSER); 172 173 if (!chkauthattr(CRONADMIN_AUTH, real_login)) { 174 if (pwp->pw_uid != ruid) 175 crabort(NOTROOT); 176 else 177 pp = getuser(ruid); 178 } else 179 pp = *argv++; 180 } else { 181 pp = getuser(ruid); 182 } 183 184 if (pp == NULL) { 185 if (per_errno == 2) 186 crabort(BADSHELL); 187 else 188 crabort(INVALIDUSER); 189 } 190 if (strlcpy(login, pp, sizeof (login)) >= sizeof (login)) 191 crabort(NAMETOOLONG); 192 if (!allowed(login, CRONALLOW, CRONDENY)) 193 crabort(NOTALLOWED); 194 195 /* Do account validation check */ 196 pam_error = pam_start("cron", pp, NULL, &pamh); 197 if (pam_error != PAM_SUCCESS) { 198 crabort((char *)pam_strerror(pamh, pam_error)); 199 } 200 pam_error = pam_acct_mgmt(pamh, PAM_SILENT); 201 if (pam_error != PAM_SUCCESS) { 202 (void) fprintf(stderr, gettext("Warning - Invalid account: " 203 "'%s' not allowed to execute cronjobs\n"), pp); 204 } 205 (void) pam_end(pamh, PAM_SUCCESS); 206 207 208 /* check for unaudited shell */ 209 if (audit_crontab_not_allowed(ruid, pp)) 210 crabort(AUDITREJECT); 211 212 cf = xmalloc(strlen(CRONDIR)+strlen(login)+2); 213 strcat(strcat(strcpy(cf, CRONDIR), "/"), login); 214 215 if (rflag) { 216 r = unlink(cf); 217 cron_sendmsg(DELETE, login, login, CRON); 218 audit_crontab_delete(cf, r); 219 exit(0); 220 } 221 if (lflag) { 222 if ((fp = fopen(cf, "r")) == NULL) 223 crabort(BADOPEN); 224 while (fgets(line, CTLINESIZE, fp) != NULL) 225 fputs(line, stdout); 226 fclose(fp); 227 exit(0); 228 } 229 if (eflag) { 230 if ((fp = fopen(cf, "r")) == NULL) { 231 if (errno != ENOENT) 232 crabort(BADOPEN); 233 } 234 (void) strcpy(edtemp, "/tmp/crontabXXXXXX"); 235 tmpfd = mkstemp(edtemp); 236 if (fchown(tmpfd, ruid, -1) == -1) { 237 (void) close(tmpfd); 238 crabort("fchown of temporary file failed"); 239 } 240 (void) close(tmpfd); 241 /* 242 * Fork off a child with user's permissions, 243 * to edit the crontab file 244 */ 245 if ((pid = fork()) == (pid_t)-1) 246 crabort("fork failed"); 247 if (pid == 0) { /* child process */ 248 /* give up super-user privileges. */ 249 setuid(ruid); 250 if ((tmpfp = fopen(edtemp, "w")) == NULL) 251 crabort("can't create temporary file"); 252 if (fp != NULL) { 253 /* 254 * Copy user's crontab file to temporary file. 255 */ 256 while (fgets(line, CTLINESIZE, fp) != NULL) { 257 fputs(line, tmpfp); 258 if (ferror(tmpfp)) { 259 fclose(fp); 260 fclose(tmpfp); 261 crabort("write error on" 262 "temporary file"); 263 } 264 } 265 if (ferror(fp)) { 266 fclose(fp); 267 fclose(tmpfp); 268 crabort(BADREAD); 269 } 270 fclose(fp); 271 } 272 if (fclose(tmpfp) == EOF) 273 crabort("write error on temporary file"); 274 if (stat(edtemp, &stbuf) < 0) 275 crabort("can't stat temporary file"); 276 omodtime = stbuf.st_mtime; 277 editor = getenv("VISUAL"); 278 if (editor == NULL) 279 editor = getenv("EDITOR"); 280 if (editor == NULL) 281 editor = "ed"; 282 (void) snprintf(buf, sizeof (buf), 283 "%s %s", editor, edtemp); 284 sleep(1); 285 286 while (1) { 287 ret = system(buf); 288 /* sanity checks */ 289 if ((tmpfp = fopen(edtemp, "r")) == NULL) 290 crabort("can't open temporary file"); 291 if (fstat(fileno(tmpfp), &stbuf) < 0) 292 crabort("can't stat temporary file"); 293 if (stbuf.st_size == 0) 294 crabort("temporary file empty"); 295 if (omodtime == stbuf.st_mtime) { 296 (void) unlink(edtemp); 297 fprintf(stderr, gettext( 298 "The crontab file was not changed.\n")); 299 exit(1); 300 } 301 if ((ret) && (errno != EINTR)) { 302 /* 303 * Some editors (like 'vi') can return 304 * a non-zero exit status even though 305 * everything is okay. Need to check. 306 */ 307 fprintf(stderr, gettext(ED_ERROR)); 308 fflush(stderr); 309 if (isatty(fileno(stdin))) { 310 /* Interactive */ 311 fprintf(stdout, gettext(ED_PROMPT), 312 yeschr, nochr, nochr); 313 fflush(stdout); 314 315 if (yes()) { 316 /* Edit again */ 317 continue; 318 } else { 319 /* Dump changes */ 320 (void) unlink(edtemp); 321 exit(1); 322 } 323 } else { 324 /* Non-interactive, dump changes */ 325 (void) unlink(edtemp); 326 exit(1); 327 } 328 } 329 exit(0); 330 } /* while (1) */ 331 } 332 333 /* fix for 1125555 - ignore common signals while waiting */ 334 (void) signal(SIGINT, SIG_IGN); 335 (void) signal(SIGHUP, SIG_IGN); 336 (void) signal(SIGQUIT, SIG_IGN); 337 (void) signal(SIGTERM, SIG_IGN); 338 wait(&stat_loc); 339 if ((stat_loc & 0xFF00) != 0) 340 exit(1); 341 342 if ((seteuid(ruid) < 0) || 343 ((tmpfp = fopen(edtemp, "r")) == NULL)) { 344 fprintf(stderr, "crontab: %s: %s\n", 345 edtemp, errmsg(errno)); 346 (void) unlink(edtemp); 347 exit(1); 348 } else 349 seteuid(0); 350 351 copycron(tmpfp); 352 (void) unlink(edtemp); 353 } else { 354 if (argc == 0) 355 copycron(stdin); 356 else if (seteuid(getuid()) != 0 || (fp = fopen(argv[0], "r")) 357 == NULL) 358 crabort(BADOPEN); 359 else { 360 seteuid(0); 361 copycron(fp); 362 } 363 } 364 cron_sendmsg(ADD, login, login, CRON); 365 /* 366 * if (per_errno == 2) 367 * fprintf(stderr, gettext(WARNSHELL)); 368 */ 369 return (0); 370 } 371 372 static void 373 copycron(fp) 374 FILE *fp; 375 { 376 FILE *tfp; 377 char pid[6], *tnam_end; 378 int t; 379 380 sprintf(pid, "%-5d", getpid()); 381 tnam = xmalloc(strlen(CRONDIR)+strlen(TMPFILE)+7); 382 strcat(strcat(strcat(strcpy(tnam, CRONDIR), "/"), TMPFILE), pid); 383 /* cut trailing blanks */ 384 tnam_end = strchr(tnam, ' '); 385 if (tnam_end != NULL) 386 *tnam_end = 0; 387 /* catch SIGINT, SIGHUP, SIGQUIT signals */ 388 if (signal(SIGINT, catch) == SIG_IGN) 389 signal(SIGINT, SIG_IGN); 390 if (signal(SIGHUP, catch) == SIG_IGN) signal(SIGHUP, SIG_IGN); 391 if (signal(SIGQUIT, catch) == SIG_IGN) signal(SIGQUIT, SIG_IGN); 392 if (signal(SIGTERM, catch) == SIG_IGN) signal(SIGTERM, SIG_IGN); 393 if ((t = creat(tnam, CRMODE)) == -1) crabort(BADCREATE); 394 if ((tfp = fdopen(t, "w")) == NULL) { 395 unlink(tnam); 396 crabort(BADCREATE); 397 } 398 err = 0; /* if errors found, err set to 1 */ 399 while (fgets(line, CTLINESIZE, fp) != NULL) { 400 cursor = 0; 401 while (line[cursor] == ' ' || line[cursor] == '\t') 402 cursor++; 403 /* fix for 1039689 - treat blank line like a comment */ 404 if (line[cursor] == '#' || line[cursor] == '\n') 405 goto cont; 406 if (next_field(0, 59)) continue; 407 if (next_field(0, 23)) continue; 408 if (next_field(1, 31)) continue; 409 if (next_field(1, 12)) continue; 410 if (next_field(0, 06)) continue; 411 if (line[++cursor] == '\0') { 412 cerror(EOLN); 413 continue; 414 } 415 cont: 416 if (fputs(line, tfp) == EOF) { 417 unlink(tnam); 418 crabort(BADCREATE); 419 } 420 } 421 fclose(fp); 422 fclose(tfp); 423 424 /* audit differences between old and new crontabs */ 425 audit_crontab_modify(cf, tnam, err); 426 427 if (!err) { 428 /* make file tfp the new crontab */ 429 unlink(cf); 430 if (link(tnam, cf) == -1) { 431 unlink(tnam); 432 crabort(BADCREATE); 433 } 434 } else 435 fprintf(stderr, "crontab: %s\n", gettext(ERRSFND)); 436 unlink(tnam); 437 } 438 439 static int 440 next_field(lower, upper) 441 int lower, upper; 442 { 443 int num, num2; 444 445 while ((line[cursor] == ' ') || (line[cursor] == '\t')) cursor++; 446 if (line[cursor] == '\0') { 447 cerror(EOLN); 448 return (1); 449 } 450 if (line[cursor] == '*') { 451 cursor++; 452 if ((line[cursor] != ' ') && (line[cursor] != '\t')) { 453 cerror(UNEXPECT); 454 return (1); 455 } 456 return (0); 457 } 458 while (TRUE) { 459 if (!isdigit(line[cursor])) { 460 cerror(UNEXPECT); 461 return (1); 462 } 463 num = 0; 464 do { 465 num = num*10 + (line[cursor]-'0'); 466 } while (isdigit(line[++cursor])); 467 if ((num < lower) || (num > upper)) { 468 cerror(OUTOFBOUND); 469 return (1); 470 } 471 if (line[cursor] == '-') { 472 if (!isdigit(line[++cursor])) { 473 cerror(UNEXPECT); 474 return (1); 475 } 476 num2 = 0; 477 do { 478 num2 = num2*10 + (line[cursor]-'0'); 479 } while (isdigit(line[++cursor])); 480 if ((num2 < lower) || (num2 > upper)) { 481 cerror(OUTOFBOUND); 482 return (1); 483 } 484 } 485 if ((line[cursor] == ' ') || (line[cursor] == '\t')) break; 486 if (line[cursor] == '\0') { 487 cerror(EOLN); 488 return (1); 489 } 490 if (line[cursor++] != ',') { 491 cerror(UNEXPECT); 492 return (1); 493 } 494 } 495 return (0); 496 } 497 498 static void 499 cerror(msg) 500 char *msg; 501 { 502 fprintf(stderr, gettext("%scrontab: error on previous line; %s\n"), 503 line, msg); 504 err = 1; 505 } 506 507 508 static void 509 catch(int x) 510 { 511 unlink(tnam); 512 exit(1); 513 } 514 515 static void 516 crabort(msg) 517 char *msg; 518 { 519 int sverrno; 520 521 if (strcmp(edtemp, "") != 0) { 522 sverrno = errno; 523 (void) unlink(edtemp); 524 errno = sverrno; 525 } 526 if (tnam != NULL) { 527 sverrno = errno; 528 (void) unlink(tnam); 529 errno = sverrno; 530 } 531 fprintf(stderr, "crontab: %s\n", gettext(msg)); 532 exit(1); 533 } 534 535 static int 536 yes(void) 537 { 538 int first_char; 539 int dummy_char; 540 541 first_char = dummy_char = getchar(); 542 while ((dummy_char != '\n') && 543 (dummy_char != '\0') && 544 (dummy_char != EOF)) 545 dummy_char = getchar(); 546 return (first_char == yeschr); 547 } 548