1 /* $NetBSD: crontab.c,v 1.11 2014/09/07 13:34:12 joerg Exp $ */ 2 3 /* Copyright 1988,1990,1993,1994 by Paul Vixie 4 * All rights reserved 5 */ 6 7 /* 8 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") 9 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 10 * 11 * Permission to use, copy, modify, and distribute this software for any 12 * purpose with or without fee is hereby granted, provided that the above 13 * copyright notice and this permission notice appear in all copies. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES 16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR 18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 21 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 22 */ 23 #include <sys/cdefs.h> 24 #if !defined(lint) && !defined(LINT) 25 #if 0 26 static char rcsid[] = "Id: crontab.c,v 1.12 2004/01/23 18:56:42 vixie Exp"; 27 #else 28 __RCSID("$NetBSD: crontab.c,v 1.11 2014/09/07 13:34:12 joerg Exp $"); 29 #endif 30 #endif 31 32 /* crontab - install and manage per-user crontab files 33 * vix 02may87 [RCS has the rest of the log] 34 * vix 26jan87 [original] 35 */ 36 37 #define MAIN_PROGRAM 38 39 #include "cron.h" 40 41 #define NHEADER_LINES 3 42 43 enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace }; 44 45 #if DEBUGGING 46 static const char *Options[] = { 47 "???", "list", "delete", "edit", "replace" }; 48 static const char *getoptargs = "u:lerx:"; 49 #else 50 static const char *getoptargs = "u:ler"; 51 #endif 52 53 static PID_T Pid; 54 static char User[MAX_UNAME], RealUser[MAX_UNAME]; 55 static char Filename[MAX_FNAME], TempFilename[MAX_FNAME]; 56 static FILE *NewCrontab; 57 static int CheckErrorCount; 58 static enum opt_t Option; 59 static struct passwd *pw; 60 static void list_cmd(void), 61 delete_cmd(void), 62 edit_cmd(void), 63 poke_daemon(void), 64 check_error(const char *), 65 parse_args(int c, char *v[]); 66 static int replace_cmd(void); 67 static int allowed(const char *, const char *, const char *); 68 static int in_file(const char *, FILE *, int); 69 static int relinguish_priv(void); 70 static int regain_priv(void); 71 72 static __dead void 73 usage(const char *msg) { 74 (void)fprintf(stderr, "%s: usage error: %s\n", getprogname(), msg); 75 (void)fprintf(stderr, "usage:\t%s [-u user] file\n", getprogname()); 76 (void)fprintf(stderr, "\t%s [-u user] [ -e | -l | -r ]\n", getprogname()); 77 (void)fprintf(stderr, "\t\t(default operation is replace, per 1003.2)\n"); 78 (void)fprintf(stderr, "\t-e\t(edit user's crontab)\n"); 79 (void)fprintf(stderr, "\t-l\t(list user's crontab)\n"); 80 (void)fprintf(stderr, "\t-r\t(delete user's crontab)\n"); 81 exit(ERROR_EXIT); 82 } 83 84 static uid_t euid, ruid; 85 static gid_t egid, rgid; 86 87 int 88 main(int argc, char *argv[]) { 89 int exitstatus; 90 91 setprogname(argv[0]); 92 Pid = getpid(); 93 (void)setlocale(LC_ALL, ""); 94 95 euid = geteuid(); 96 egid = getegid(); 97 ruid = getuid(); 98 rgid = getgid(); 99 100 if (euid == ruid && ruid != 0) 101 errx(ERROR_EXIT, "Not installed setuid root"); 102 103 (void)setvbuf(stderr, NULL, _IOLBF, 0); 104 parse_args(argc, argv); /* sets many globals, opens a file */ 105 set_cron_cwd(); 106 if (!allowed(RealUser, CRON_ALLOW, CRON_DENY)) { 107 (void)fprintf(stderr, 108 "You `%s' are not allowed to use this program `%s'\n", 109 User, getprogname()); 110 (void)fprintf(stderr, "See crontab(1) for more information\n"); 111 log_it(RealUser, Pid, "AUTH", "crontab command not allowed"); 112 exit(ERROR_EXIT); 113 } 114 exitstatus = OK_EXIT; 115 switch (Option) { 116 case opt_unknown: 117 usage("unrecognized option"); 118 exitstatus = ERROR_EXIT; 119 break; 120 case opt_list: 121 list_cmd(); 122 break; 123 case opt_delete: 124 delete_cmd(); 125 break; 126 case opt_edit: 127 edit_cmd(); 128 break; 129 case opt_replace: 130 if (replace_cmd() < 0) 131 exitstatus = ERROR_EXIT; 132 break; 133 default: 134 abort(); 135 } 136 exit(exitstatus); 137 /*NOTREACHED*/ 138 } 139 140 static void 141 get_time(const struct stat *st, struct timespec *ts) 142 { 143 ts[0].tv_sec = st->st_atime; 144 ts[0].tv_nsec = st->st_atimensec; 145 ts[1].tv_sec = st->st_mtime; 146 ts[1].tv_nsec = st->st_mtimensec; 147 } 148 149 static int 150 change_time(const char *name, const struct timespec *ts) 151 { 152 #if defined(HAVE_UTIMENSAT) 153 return utimensat(AT_FDCWD, name, ts, 0); 154 #elif defined(HAVE_UTIMES) 155 struct timeval tv[2]; 156 TIMESPEC_TO_TIMEVAL(&tv[0], &ts[0]); 157 TIMESPEC_TO_TIMEVAL(&tv[1], &ts[1]); 158 return utimes(name, tv); 159 #else 160 struct utimbuf ut; 161 ut.actime = ts[0].tv_sec; 162 ut.modtime = ts[1].tv_sec; 163 return utime(name, &ut); 164 #endif 165 } 166 167 static int 168 compare_time(const struct stat *st, const struct timespec *ts2) 169 { 170 struct timespec ts1[2]; 171 get_time(st, ts1); 172 173 return ts1[1].tv_sec == ts2[1].tv_sec 174 #if defined(HAVE_UTIMENSAT) 175 && ts1[1].tv_nsec == ts2[1].tv_nsec 176 #elif defined(HAVE_UTIMES) 177 && ts1[1].tv_nsec / 1000 == ts2[1].tv_nsec / 1000 178 #endif 179 ; 180 } 181 182 static void 183 parse_args(int argc, char *argv[]) { 184 int argch; 185 186 if (!(pw = getpwuid(getuid()))) { 187 errx(ERROR_EXIT, 188 "your UID isn't in the passwd file. bailingo out"); 189 } 190 if (strlen(pw->pw_name) >= sizeof User) { 191 errx(ERROR_EXIT, "username too long"); 192 } 193 (void)strlcpy(User, pw->pw_name, sizeof(User)); 194 (void)strlcpy(RealUser, User, sizeof(RealUser)); 195 Filename[0] = '\0'; 196 Option = opt_unknown; 197 while (-1 != (argch = getopt(argc, argv, getoptargs))) { 198 switch (argch) { 199 #if DEBUGGING 200 case 'x': 201 if (!set_debug_flags(optarg)) 202 usage("bad debug option"); 203 break; 204 #endif 205 case 'u': 206 if (MY_UID(pw) != ROOT_UID) { 207 errx(ERROR_EXIT, 208 "must be privileged to use -u"); 209 } 210 if (!(pw = getpwnam(optarg))) { 211 errx(ERROR_EXIT, "user `%s' unknown", optarg); 212 } 213 if (strlen(optarg) >= sizeof User) 214 usage("username too long"); 215 (void) strlcpy(User, optarg, sizeof(User)); 216 break; 217 case 'l': 218 if (Option != opt_unknown) 219 usage("only one operation permitted"); 220 Option = opt_list; 221 break; 222 case 'r': 223 if (Option != opt_unknown) 224 usage("only one operation permitted"); 225 Option = opt_delete; 226 break; 227 case 'e': 228 if (Option != opt_unknown) 229 usage("only one operation permitted"); 230 Option = opt_edit; 231 break; 232 default: 233 usage("unrecognized option"); 234 } 235 } 236 237 endpwent(); 238 239 if (Option != opt_unknown) { 240 if (argv[optind] != NULL) 241 usage("no arguments permitted after this option"); 242 } else { 243 if (argv[optind] != NULL) { 244 Option = opt_replace; 245 if (strlen(argv[optind]) >= sizeof Filename) 246 usage("filename too long"); 247 (void)strlcpy(Filename, argv[optind], sizeof(Filename)); 248 } else 249 usage("file name must be specified for replace"); 250 } 251 252 if (Option == opt_replace) { 253 /* we have to open the file here because we're going to 254 * chdir(2) into /var/cron before we get around to 255 * reading the file. 256 */ 257 if (!strcmp(Filename, "-")) 258 NewCrontab = stdin; 259 else { 260 /* relinquish the setuid status of the binary during 261 * the open, lest nonroot users read files they should 262 * not be able to read. we can't use access() here 263 * since there's a race condition. thanks go out to 264 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting 265 * the race. 266 */ 267 268 if (relinguish_priv() < OK) { 269 err(ERROR_EXIT, "swapping uids"); 270 } 271 if (!(NewCrontab = fopen(Filename, "r"))) { 272 err(ERROR_EXIT, "cannot open `%s'", Filename); 273 } 274 if (regain_priv() < OK) { 275 err(ERROR_EXIT, "swapping uids back"); 276 } 277 } 278 } 279 280 Debug(DMISC, ("user=%s, file=%s, option=%s\n", 281 User, Filename, Options[(int)Option])); 282 } 283 284 static void 285 skip_header(int *pch, FILE *f) 286 { 287 int ch; 288 int x; 289 290 /* ignore the top few comments since we probably put them there. 291 */ 292 for (x = 0; x < NHEADER_LINES; x++) { 293 ch = get_char(f); 294 if (EOF == ch) 295 break; 296 if ('#' != ch) 297 break; 298 while (EOF != (ch = get_char(f))) 299 if (ch == '\n') 300 break; 301 if (EOF == ch) 302 break; 303 } 304 if (ch == '\n') 305 ch = get_char(f); 306 307 *pch = ch; 308 } 309 310 static void 311 list_cmd(void) { 312 char n[MAX_FNAME]; 313 FILE *f; 314 int ch; 315 316 log_it(RealUser, Pid, "LIST", User); 317 if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { 318 errx(ERROR_EXIT, "path too long"); 319 } 320 if (!(f = fopen(n, "r"))) { 321 if (errno == ENOENT) 322 errx(ERROR_EXIT, "no crontab for `%s'", User); 323 else 324 err(ERROR_EXIT, "Cannot open `%s'", n); 325 } 326 327 /* file is open. copy to stdout, close. 328 */ 329 Set_LineNum(1); 330 skip_header(&ch, f); 331 for (; EOF != ch; ch = get_char(f)) 332 (void)putchar(ch); 333 (void)fclose(f); 334 } 335 336 static void 337 delete_cmd(void) { 338 char n[MAX_FNAME]; 339 340 log_it(RealUser, Pid, "DELETE", User); 341 if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { 342 errx(ERROR_EXIT, "path too long"); 343 } 344 if (unlink(n) != 0) { 345 if (errno == ENOENT) 346 errx(ERROR_EXIT, "no crontab for `%s'", User); 347 else 348 err(ERROR_EXIT, "cannot unlink `%s'", n); 349 } 350 poke_daemon(); 351 } 352 353 static void 354 check_error(const char *msg) { 355 CheckErrorCount++; 356 (void)fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg); 357 } 358 359 static void 360 edit_cmd(void) { 361 char n[MAX_FNAME], q[MAX_TEMPSTR]; 362 const char *editor; 363 FILE *f; 364 int ch, t, x; 365 sig_t oint, oabrt, oquit, ohup; 366 struct stat statbuf; 367 WAIT_T waiter; 368 PID_T pid, xpid; 369 struct timespec ts[2]; 370 371 log_it(RealUser, Pid, "BEGIN EDIT", User); 372 if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { 373 errx(ERROR_EXIT, "path too long"); 374 } 375 if (!(f = fopen(n, "r"))) { 376 if (errno != ENOENT) { 377 err(ERROR_EXIT, "cannot open `%s'", n); 378 } 379 warnx("no crontab for `%s' - using an empty one", User); 380 if (!(f = fopen(_PATH_DEVNULL, "r"))) { 381 err(ERROR_EXIT, "cannot open `%s'", _PATH_DEVNULL); 382 } 383 } 384 385 if (fstat(fileno(f), &statbuf) < 0) { 386 warn("cannot stat crontab file"); 387 goto fatal; 388 } 389 get_time(&statbuf, ts); 390 391 /* Turn off signals. */ 392 ohup = signal(SIGHUP, SIG_IGN); 393 oint = signal(SIGINT, SIG_IGN); 394 oquit = signal(SIGQUIT, SIG_IGN); 395 oabrt = signal(SIGABRT, SIG_IGN); 396 397 if (!glue_strings(Filename, sizeof Filename, _PATH_TMP, 398 "crontab.XXXXXXXXXX", '/')) { 399 warnx("path too long"); 400 goto fatal; 401 } 402 if (-1 == (t = mkstemp(Filename))) { 403 warn("cannot create `%s'", Filename); 404 goto fatal; 405 } 406 #ifdef HAS_FCHOWN 407 x = fchown(t, MY_UID(pw), MY_GID(pw)); 408 #else 409 x = chown(Filename, MY_UID(pw), MY_GID(pw)); 410 #endif 411 if (x < 0) { 412 warn("cannot chown `%s'", Filename); 413 goto fatal; 414 } 415 if (!(NewCrontab = fdopen(t, "r+"))) { 416 warn("cannot open fd"); 417 goto fatal; 418 } 419 420 Set_LineNum(1); 421 422 skip_header(&ch, f); 423 424 /* copy the rest of the crontab (if any) to the temp file. 425 */ 426 for (; EOF != ch; ch = get_char(f)) 427 (void)putc(ch, NewCrontab); 428 (void)fclose(f); 429 if (fflush(NewCrontab) < OK) { 430 err(ERROR_EXIT, "cannot flush output for `%s'", Filename); 431 } 432 if (change_time(Filename, ts) == -1) 433 err(ERROR_EXIT, "cannot set time info for `%s'", Filename); 434 again: 435 rewind(NewCrontab); 436 if (ferror(NewCrontab)) { 437 warn("error while writing new crontab to `%s'", Filename); 438 fatal: 439 (void)unlink(Filename); 440 exit(ERROR_EXIT); 441 } 442 443 if (((editor = getenv("VISUAL")) == NULL || *editor == '\0') && 444 ((editor = getenv("EDITOR")) == NULL || *editor == '\0')) { 445 editor = EDITOR; 446 } 447 448 /* we still have the file open. editors will generally rewrite the 449 * original file rather than renaming/unlinking it and starting a 450 * new one; even backup files are supposed to be made by copying 451 * rather than by renaming. if some editor does not support this, 452 * then don't use it. the security problems are more severe if we 453 * close and reopen the file around the edit. 454 */ 455 456 switch (pid = fork()) { 457 case -1: 458 warn("cannot fork"); 459 goto fatal; 460 case 0: 461 /* child */ 462 if (setgid(MY_GID(pw)) < 0) { 463 err(ERROR_EXIT, "cannot setgid(getgid())"); 464 } 465 if (setuid(MY_UID(pw)) < 0) { 466 err(ERROR_EXIT, "cannot setuid(getuid())"); 467 } 468 if (chdir(_PATH_TMP) < 0) { 469 err(ERROR_EXIT, "cannot chdir to `%s'", _PATH_TMP); 470 } 471 if (!glue_strings(q, sizeof q, editor, Filename, ' ')) { 472 errx(ERROR_EXIT, "editor command line too long"); 473 } 474 (void)execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, (char *)0); 475 err(ERROR_EXIT, "cannot start `%s'", editor); 476 /*NOTREACHED*/ 477 default: 478 /* parent */ 479 break; 480 } 481 482 /* parent */ 483 for (;;) { 484 xpid = waitpid(pid, &waiter, WUNTRACED); 485 if (xpid == -1) { 486 if (errno != EINTR) 487 warn("waitpid() failed waiting for PID %ld " 488 "from `%s'", (long)pid, editor); 489 } else if (xpid != pid) { 490 warnx("wrong PID (%ld != %ld) from `%s'", 491 (long)xpid, (long)pid, editor); 492 goto fatal; 493 } else if (WIFSTOPPED(waiter)) { 494 (void)kill(getpid(), WSTOPSIG(waiter)); 495 } else if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { 496 warnx("`%s' exited with status %d\n", 497 editor, WEXITSTATUS(waiter)); 498 goto fatal; 499 } else if (WIFSIGNALED(waiter)) { 500 warnx("`%s' killed; signal %d (%score dumped)", 501 editor, WTERMSIG(waiter), 502 WCOREDUMP(waiter) ? "" : "no "); 503 goto fatal; 504 } else 505 break; 506 } 507 (void)signal(SIGHUP, ohup); 508 (void)signal(SIGINT, oint); 509 (void)signal(SIGQUIT, oquit); 510 (void)signal(SIGABRT, oabrt); 511 512 if (fstat(t, &statbuf) < 0) { 513 warn("cannot stat `%s'", Filename); 514 goto fatal; 515 } 516 if (compare_time(&statbuf, ts)) { 517 warnx("no changes made to crontab"); 518 goto remove; 519 } 520 warnx("installing new crontab"); 521 switch (replace_cmd()) { 522 case 0: 523 break; 524 case -1: 525 for (;;) { 526 (void)fpurge(stdin); 527 (void)printf("Do you want to retry the same edit? "); 528 (void)fflush(stdout); 529 q[0] = '\0'; 530 (void) fgets(q, (int)sizeof(q), stdin); 531 switch (q[0]) { 532 case 'y': 533 case 'Y': 534 goto again; 535 case 'n': 536 case 'N': 537 goto abandon; 538 default: 539 (void)printf("Enter Y or N\n"); 540 } 541 } 542 /*NOTREACHED*/ 543 case -2: 544 abandon: 545 warnx("edits left in `%s'", Filename); 546 goto done; 547 default: 548 warnx("panic: bad switch() in replace_cmd()"); 549 goto fatal; 550 } 551 remove: 552 (void)unlink(Filename); 553 done: 554 log_it(RealUser, Pid, "END EDIT", User); 555 } 556 557 /* returns 0 on success 558 * -1 on syntax error 559 * -2 on install error 560 */ 561 static int 562 replace_cmd(void) { 563 char n[MAX_FNAME], n2[MAX_FNAME], envstr[MAX_ENVSTR]; 564 int lastch; 565 FILE *tmp, *fmaxtabsize; 566 int ch, eof, fd; 567 int error = 0; 568 entry *e; 569 sig_t oint, oabrt, oquit, ohup; 570 uid_t file_owner; 571 time_t now = time(NULL); 572 char **envp = env_init(); 573 size_t maxtabsize; 574 struct stat statbuf; 575 576 if (envp == NULL) { 577 warn("Cannot allocate memory."); 578 return (-2); 579 } 580 581 if (!glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR, 582 "tmp.XXXXXXXXXX", '/')) { 583 TempFilename[0] = '\0'; 584 warnx("path too long"); 585 return (-2); 586 } 587 if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) { 588 warn("cannot create `%s'", TempFilename); 589 if (fd != -1) { 590 (void)close(fd); 591 (void)unlink(TempFilename); 592 } 593 TempFilename[0] = '\0'; 594 return (-2); 595 } 596 597 ohup = signal(SIGHUP, SIG_IGN); 598 oint = signal(SIGINT, SIG_IGN); 599 oquit = signal(SIGQUIT, SIG_IGN); 600 oabrt = signal(SIGABRT, SIG_IGN); 601 602 /* Make sure that the crontab is not an unreasonable size. 603 * 604 * XXX This is subject to a race condition--the user could 605 * add stuff to the file after we've checked the size but 606 * before we slurp it in and write it out. We can't just move 607 * the test to test the temp file we later create, because by 608 * that time we've already filled up the crontab disk. Probably 609 * the right thing to do is to do a bytecount in the copy loop 610 * rather than stating the file we're about to read. 611 */ 612 (void)snprintf(n2, sizeof(n2), "%s/%s", CRONDIR, MAXTABSIZE_FILE); 613 if ((fmaxtabsize = fopen(n2, "r")) != NULL) { 614 if (fgets(n2, (int)sizeof(n2), fmaxtabsize) == NULL) { 615 maxtabsize = 0; 616 } else { 617 maxtabsize = (size_t)atoi(n2); 618 } 619 (void)fclose(fmaxtabsize); 620 } else { 621 maxtabsize = MAXTABSIZE_DEFAULT; 622 } 623 624 if (fstat(fileno(NewCrontab), &statbuf)) { 625 warn("error stat'ing crontab input"); 626 error = -2; 627 goto done; 628 } 629 if ((uintmax_t)statbuf.st_size > (uintmax_t)maxtabsize) { 630 warnx("%ld bytes is larger than the maximum size of %ld bytes", 631 (long) statbuf.st_size, (long) maxtabsize); 632 error = -2; 633 goto done; 634 } 635 636 /* write a signature at the top of the file. 637 * 638 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. 639 */ 640 (void)fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); 641 (void)fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); 642 (void)fprintf(tmp, "# (Cron version %s -- %s)\n", CRON_VERSION, "$NetBSD: crontab.c,v 1.11 2014/09/07 13:34:12 joerg Exp $"); 643 644 /* copy the crontab to the tmp 645 */ 646 (void)rewind(NewCrontab); 647 Set_LineNum(1); 648 lastch = EOF; 649 while (EOF != (ch = get_char(NewCrontab))) 650 (void)putc(lastch = (char)ch, tmp); 651 652 if (lastch != EOF && lastch != '\n') { 653 warnx("missing trailing newline in `%s'", Filename); 654 error = -1; 655 goto done; 656 } 657 658 if (ferror(NewCrontab)) { 659 warn("error while reading `%s'", Filename); 660 error = -2; 661 goto done; 662 } 663 664 (void)ftruncate(fileno(tmp), ftell(tmp)); 665 /* XXX this should be a NOOP - is */ 666 (void)fflush(tmp); 667 668 if (ferror(tmp)) { 669 (void)fclose(tmp); 670 warn("error while writing new crontab to `%s'", TempFilename); 671 error = -2; 672 goto done; 673 } 674 675 /* check the syntax of the file being installed. 676 */ 677 678 /* BUG: was reporting errors after the EOF if there were any errors 679 * in the file proper -- kludged it by stopping after first error. 680 * vix 31mar87 681 */ 682 Set_LineNum(1 - NHEADER_LINES); 683 CheckErrorCount = 0; eof = FALSE; 684 rewind(tmp); 685 while (!CheckErrorCount && !eof) { 686 switch (load_env(envstr, tmp)) { 687 case ERR: 688 /* check for data before the EOF */ 689 if (envstr[0] != '\0') { 690 Set_LineNum(LineNumber + 1); 691 check_error("premature EOF"); 692 } 693 eof = TRUE; 694 break; 695 case FALSE: 696 e = load_entry(tmp, check_error, pw, envp); 697 if (e) 698 free(e); 699 break; 700 case TRUE: 701 break; 702 } 703 } 704 705 if (CheckErrorCount != 0) { 706 warnx("errors in crontab file, can't install."); 707 (void)fclose(tmp); 708 error = -1; 709 goto done; 710 } 711 712 file_owner = (getgid() == getegid()) ? ROOT_UID : pw->pw_uid; 713 714 #ifdef HAVE_FCHOWN 715 error = fchown(fileno(tmp), file_owner, (uid_t)-1); 716 #else 717 error = chown(TempFilename, file_owner, (gid_t)-1); 718 #endif 719 if (error < OK) { 720 warn("cannot chown `%s'", TempFilename); 721 (void)fclose(tmp); 722 error = -2; 723 goto done; 724 } 725 726 if (fclose(tmp) == EOF) { 727 warn("error closing file"); 728 error = -2; 729 goto done; 730 } 731 732 if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { 733 warnx("path too long"); 734 error = -2; 735 goto done; 736 } 737 if (rename(TempFilename, n)) { 738 warn("error renaming `%s' to `%s'", TempFilename, n); 739 error = -2; 740 goto done; 741 } 742 TempFilename[0] = '\0'; 743 log_it(RealUser, Pid, "REPLACE", User); 744 745 poke_daemon(); 746 747 done: 748 (void)signal(SIGHUP, ohup); 749 (void)signal(SIGINT, oint); 750 (void)signal(SIGQUIT, oquit); 751 (void)signal(SIGABRT, oabrt); 752 if (TempFilename[0]) { 753 (void) unlink(TempFilename); 754 TempFilename[0] = '\0'; 755 } 756 return (error); 757 } 758 759 static void 760 poke_daemon(void) { 761 struct timespec ts[2]; 762 (void) clock_gettime(CLOCK_REALTIME, ts); 763 ts[1] = ts[0]; 764 if (change_time(SPOOL_DIR, ts) == -1) 765 warn("can't update times on spooldir %s", SPOOL_DIR); 766 } 767 768 /* int allowed(const char *username, const char *allow_file, const char *deny_file) 769 * returns TRUE if (allow_file exists and user is listed) 770 * or (deny_file exists and user is NOT listed). 771 * root is always allowed. 772 */ 773 static int 774 allowed(const char *username, const char *allow_file, const char *deny_file) { 775 FILE *fp; 776 int isallowed; 777 778 if (strcmp(username, ROOT_USER) == 0) 779 return (TRUE); 780 #ifdef ALLOW_ONLY_ROOT 781 isallowed = FALSE; 782 #else 783 isallowed = TRUE; 784 #endif 785 if ((fp = fopen(allow_file, "r")) != NULL) { 786 isallowed = in_file(username, fp, FALSE); 787 (void)fclose(fp); 788 } else if ((fp = fopen(deny_file, "r")) != NULL) { 789 isallowed = !in_file(username, fp, FALSE); 790 (void)fclose(fp); 791 } 792 return (isallowed); 793 } 794 /* int in_file(const char *string, FILE *file, int error) 795 * return TRUE if one of the lines in file matches string exactly, 796 * FALSE if no lines match, and error on error. 797 */ 798 static int 799 in_file(const char *string, FILE *file, int error) 800 { 801 char line[MAX_TEMPSTR]; 802 char *endp; 803 804 if (fseek(file, 0L, SEEK_SET)) 805 return (error); 806 while (fgets(line, MAX_TEMPSTR, file)) { 807 if (line[0] != '\0') { 808 endp = &line[strlen(line) - 1]; 809 if (*endp != '\n') 810 return (error); 811 *endp = '\0'; 812 if (0 == strcmp(line, string)) 813 return (TRUE); 814 } 815 } 816 if (ferror(file)) 817 return (error); 818 return (FALSE); 819 } 820 821 #ifdef HAVE_SAVED_UIDS 822 823 static int relinguish_priv(void) { 824 return (setegid(rgid) || seteuid(ruid)) ? -1 : 0; 825 } 826 827 static int regain_priv(void) { 828 return (setegid(egid) || seteuid(euid)) ? -1 : 0; 829 } 830 831 #else /*HAVE_SAVED_UIDS*/ 832 833 static int relinguish_priv(void) { 834 return (setregid(egid, rgid) || setreuid(euid, ruid)) ? -1 : 0; 835 } 836 837 static int regain_priv(void) { 838 return (setregid(rgid, egid) || setreuid(ruid, euid)) ? -1 : 0; 839 } 840 #endif /*HAVE_SAVED_UIDS*/ 841