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