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