1/* $NetBSD: crontab.c,v 1.15 2018/03/06 21:21:27 htodd 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 26static 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.15 2018/03/06 21:21:27 htodd 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 43enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace }; 44 45#if DEBUGGING 46static const char *Options[] = { 47 "???", "list", "delete", "edit", "replace" }; 48static const char *getoptargs = "u:lerx:"; 49#else 50static const char *getoptargs = "u:ler"; 51#endif 52 53static PID_T Pid; 54static char User[MAX_UNAME], RealUser[MAX_UNAME]; 55static char Filename[MAX_FNAME], TempFilename[MAX_FNAME]; 56static FILE *NewCrontab; 57static int CheckErrorCount; 58static enum opt_t Option; 59static struct passwd *pw; 60static 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[]); 66static int replace_cmd(void); 67static int allowed(const char *, const char *, const char *); 68static int in_file(const char *, FILE *, int); 69static int relinquish_priv(void); 70static int regain_priv(void); 71 72static __dead void 73usage(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 84static uid_t euid, ruid; 85static gid_t egid, rgid; 86 87int 88main(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 140static void 141get_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 149static int 150change_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 167static int 168compare_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 182static void 183parse_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 (relinquish_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 284static void 285skip_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 310static void 311list_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 336static void 337delete_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 353static void 354check_error(const char *msg) { 355 CheckErrorCount++; 356 (void)fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg); 357} 358 359static void 360edit_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#ifdef HAVE_FUTIMENS 433 if (futimens(t, ts) == -1) 434#else 435 if (change_time(Filename, ts) == -1) 436#endif 437 err(ERROR_EXIT, "cannot set time info for `%s'", Filename); 438 again: 439 rewind(NewCrontab); 440 if (ferror(NewCrontab)) { 441 warn("error while writing new crontab to `%s'", Filename); 442 fatal: 443 (void)unlink(Filename); 444 exit(ERROR_EXIT); 445 } 446 447 if (((editor = getenv("VISUAL")) == NULL || *editor == '\0') && 448 ((editor = getenv("EDITOR")) == NULL || *editor == '\0')) { 449 editor = EDITOR; 450 } 451 452 /* we still have the file open. editors will generally rewrite the 453 * original file rather than renaming/unlinking it and starting a 454 * new one; even backup files are supposed to be made by copying 455 * rather than by renaming. if some editor does not support this, 456 * then don't use it. the security problems are more severe if we 457 * close and reopen the file around the edit. 458 */ 459 460 switch (pid = fork()) { 461 case -1: 462 warn("cannot fork"); 463 goto fatal; 464 case 0: 465 /* child */ 466 if (setgid(MY_GID(pw)) < 0) { 467 err(ERROR_EXIT, "cannot setgid(getgid())"); 468 } 469 if (setuid(MY_UID(pw)) < 0) { 470 err(ERROR_EXIT, "cannot setuid(getuid())"); 471 } 472 if (close_all(3)) { 473 err(ERROR_EXIT, "cannot close files"); 474 } 475 if (chdir(_PATH_TMP) < 0) { 476 err(ERROR_EXIT, "cannot chdir to `%s'", _PATH_TMP); 477 } 478 if (!glue_strings(q, sizeof q, editor, Filename, ' ')) { 479 errx(ERROR_EXIT, "editor command line too long"); 480 } 481 (void)execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, (char *)0); 482 err(ERROR_EXIT, "cannot start `%s'", editor); 483 /*NOTREACHED*/ 484 default: 485 /* parent */ 486 break; 487 } 488 489 /* parent */ 490 for (;;) { 491 xpid = waitpid(pid, &waiter, WUNTRACED); 492 if (xpid == -1) { 493 if (errno != EINTR) 494 warn("waitpid() failed waiting for PID %ld " 495 "from `%s'", (long)pid, editor); 496 } else if (xpid != pid) { 497 warnx("wrong PID (%ld != %ld) from `%s'", 498 (long)xpid, (long)pid, editor); 499 goto fatal; 500 } else if (WIFSTOPPED(waiter)) { 501 (void)kill(getpid(), WSTOPSIG(waiter)); 502 } else if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { 503 warnx("`%s' exited with status %d\n", 504 editor, WEXITSTATUS(waiter)); 505 goto fatal; 506 } else if (WIFSIGNALED(waiter)) { 507 warnx("`%s' killed; signal %d (%score dumped)", 508 editor, WTERMSIG(waiter), 509 WCOREDUMP(waiter) ? "" : "no "); 510 goto fatal; 511 } else 512 break; 513 } 514 (void)signal(SIGHUP, ohup); 515 (void)signal(SIGINT, oint); 516 (void)signal(SIGQUIT, oquit); 517 (void)signal(SIGABRT, oabrt); 518 519 if (fstat(t, &statbuf) < 0) { 520 warn("cannot stat `%s'", Filename); 521 goto fatal; 522 } 523 if (compare_time(&statbuf, ts)) { 524 warnx("no changes made to crontab"); 525 goto remove; 526 } 527 warnx("installing new crontab"); 528 switch (replace_cmd()) { 529 case 0: 530 break; 531 case -1: 532 for (;;) { 533 (void)fpurge(stdin); 534 (void)printf("Do you want to retry the same edit? "); 535 (void)fflush(stdout); 536 q[0] = '\0'; 537 (void) fgets(q, (int)sizeof(q), stdin); 538 switch (q[0]) { 539 case 'y': 540 case 'Y': 541 goto again; 542 case 'n': 543 case 'N': 544 goto abandon; 545 default: 546 (void)printf("Enter Y or N\n"); 547 } 548 } 549 /*NOTREACHED*/ 550 case -2: 551 abandon: 552 warnx("edits left in `%s'", Filename); 553 goto done; 554 default: 555 warnx("panic: bad switch() in replace_cmd()"); 556 goto fatal; 557 } 558 remove: 559 (void)unlink(Filename); 560 done: 561 log_it(RealUser, Pid, "END EDIT", User); 562} 563 564static size_t 565getmaxtabsize(void) 566{ 567 char n2[MAX_FNAME]; 568 FILE *fmaxtabsize; 569 size_t maxtabsize; 570 571 /* Make sure that the crontab is not an unreasonable size. 572 * 573 * XXX This is subject to a race condition--the user could 574 * add stuff to the file after we've checked the size but 575 * before we slurp it in and write it out. We can't just move 576 * the test to test the temp file we later create, because by 577 * that time we've already filled up the crontab disk. Probably 578 * the right thing to do is to do a bytecount in the copy loop 579 * rather than stating the file we're about to read. 580 */ 581 (void)snprintf(n2, sizeof(n2), "%s/%s", CRONDIR, MAXTABSIZE_FILE); 582 if ((fmaxtabsize = fopen(n2, "r")) != NULL) { 583 if (fgets(n2, (int)sizeof(n2), fmaxtabsize) == NULL) { 584 maxtabsize = 0; 585 } else { 586 maxtabsize = (size_t)atoi(n2); 587 } 588 (void)fclose(fmaxtabsize); 589 } else { 590 maxtabsize = MAXTABSIZE_DEFAULT; 591 } 592 return maxtabsize; 593} 594 595static int 596checkmaxtabsize(FILE *fp, size_t maxtabsize) 597{ 598 struct stat statbuf; 599 600 if (fstat(fileno(fp), &statbuf)) { 601 warn("error stat'ing crontab input"); 602 return 0; 603 } 604 if ((uintmax_t)statbuf.st_size > (uintmax_t)maxtabsize) { 605 warnx("%ju bytes is larger than the maximum size of %ju bytes", 606 (uintmax_t)statbuf.st_size, (uintmax_t)maxtabsize); 607 return 0; 608 } 609 return 1; 610} 611 612static void 613cleanTempFile(void) 614{ 615 616 if (TempFilename[0]) { 617 (void) unlink(TempFilename); 618 TempFilename[0] = '\0'; 619 } 620} 621 622static __dead void 623bail(int signo) 624{ 625 626 cleanTempFile(); 627 errx(ERROR_EXIT, "Exiting on signal %d", signo); 628} 629 630/* returns 0 on success 631 * -1 on syntax error 632 * -2 on install error 633 */ 634static int 635replace_cmd(void) { 636 char n[MAX_FNAME], envstr[MAX_ENVSTR]; 637 int lastch; 638 FILE *tmp = NULL; 639 int ch, eof, fd; 640 int error = 0; 641 entry *e; 642 sig_t oint, oabrt, oquit, ohup; 643 uid_t file_owner; 644 time_t now = time(NULL); 645 char **envp = env_init(); 646 size_t i, maxtabsize; 647 648 if (envp == NULL) { 649 warn("Cannot allocate memory."); 650 return (-2); 651 } 652 653 if (!glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR, 654 "tmp.XXXXXXXXXX", '/')) { 655 TempFilename[0] = '\0'; 656 warnx("path too long"); 657 return (-2); 658 } 659 660 /* Interruptible while doing I/O */ 661 ohup = signal(SIGHUP, bail); 662 oint = signal(SIGINT, bail); 663 oquit = signal(SIGQUIT, bail); 664 oabrt = signal(SIGABRT, bail); 665 666 if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) { 667 warn("cannot create `%s'", TempFilename); 668 if (fd != -1) 669 (void)close(fd); 670 error = -2; 671 goto done; 672 } 673 674 maxtabsize = getmaxtabsize(); 675 676 /* This does not work for stdin, so we'll also check later */ 677 if (!checkmaxtabsize(NewCrontab, maxtabsize)) { 678 error = -2; 679 goto done; 680 } 681 682 /* write a signature at the top of the file. 683 * 684 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. 685 */ 686 (void)fprintf(tmp, 687 "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); 688 (void)fprintf(tmp, 689 "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); 690 (void)fprintf(tmp, 691 "# (Cron version %s -- %s)\n", CRON_VERSION, 692 "$NetBSD: crontab.c,v 1.15 2018/03/06 21:21:27 htodd Exp $"); 693 694 /* copy the crontab to the tmp 695 */ 696 (void)rewind(NewCrontab); 697 Set_LineNum(1); 698 lastch = EOF; 699 for (i = 0; i < maxtabsize && EOF != (ch = get_char(NewCrontab)); i++) 700 (void)putc(lastch = (char)ch, tmp); 701 702 /* Non-Interruptible while installing */ 703 (void)signal(SIGHUP, SIG_IGN); 704 (void)signal(SIGINT, SIG_IGN); 705 (void)signal(SIGQUIT, SIG_IGN); 706 (void)signal(SIGABRT, SIG_IGN); 707 708 if (i == maxtabsize) { 709 warnx("is larger than the maximum size of %ju bytes", 710 (uintmax_t)maxtabsize); 711 error = -2; 712 goto done; 713 } 714 715 if (lastch != EOF && lastch != '\n') { 716 warnx("missing trailing newline in `%s'", Filename); 717 error = -1; 718 goto done; 719 } 720 721 if (ferror(NewCrontab)) { 722 warn("error while reading `%s'", Filename); 723 error = -2; 724 goto done; 725 } 726 727 (void)ftruncate(fileno(tmp), ftell(tmp)); 728 /* XXX this should be a NOOP - is */ 729 (void)fflush(tmp); 730 731 if (ferror(tmp)) { 732 warn("error while writing new crontab to `%s'", TempFilename); 733 error = -2; 734 goto done; 735 } 736 737 /* check the syntax of the file being installed. 738 */ 739 740 /* BUG: was reporting errors after the EOF if there were any errors 741 * in the file proper -- kludged it by stopping after first error. 742 * vix 31mar87 743 */ 744 Set_LineNum(1 - NHEADER_LINES); 745 CheckErrorCount = 0; eof = FALSE; 746 rewind(tmp); 747 while (!CheckErrorCount && !eof) { 748 switch (load_env(envstr, tmp)) { 749 case ERR: 750 /* check for data before the EOF */ 751 if (envstr[0] != '\0') { 752 Set_LineNum(LineNumber + 1); 753 check_error("premature EOF"); 754 } 755 eof = TRUE; 756 break; 757 case FALSE: 758 e = load_entry(tmp, check_error, pw, envp); 759 if (e) 760 free(e); 761 break; 762 case TRUE: 763 break; 764 } 765 } 766 767 if (CheckErrorCount != 0) { 768 warnx("errors in crontab file, can't install."); 769 error = -1; 770 goto done; 771 } 772 773 file_owner = (getgid() == getegid()) ? ROOT_UID : pw->pw_uid; 774 775#ifdef HAVE_FCHOWN 776 error = fchown(fileno(tmp), file_owner, (uid_t)-1); 777#else 778 error = chown(TempFilename, file_owner, (gid_t)-1); 779#endif 780 781 if (fclose(tmp) == EOF) { 782 warn("error closing file"); 783 error = -2; 784 tmp = NULL; 785 goto done; 786 } 787 tmp = NULL; 788 789 if (error < OK) { 790 warn("cannot chown `%s'", TempFilename); 791 error = -2; 792 goto done; 793 } 794 795 if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { 796 warnx("path too long"); 797 error = -2; 798 goto done; 799 } 800 if (rename(TempFilename, n)) { 801 warn("error renaming `%s' to `%s'", TempFilename, n); 802 error = -2; 803 goto done; 804 } 805 TempFilename[0] = '\0'; 806 log_it(RealUser, Pid, "REPLACE", User); 807 808 poke_daemon(); 809 810done: 811 (void)signal(SIGHUP, ohup); 812 (void)signal(SIGINT, oint); 813 (void)signal(SIGQUIT, oquit); 814 (void)signal(SIGABRT, oabrt); 815 if (tmp != NULL) 816 (void)fclose(tmp); 817 cleanTempFile(); 818 return (error); 819} 820 821static void 822poke_daemon(void) { 823 struct timespec ts[2]; 824 (void) clock_gettime(CLOCK_REALTIME, ts); 825 ts[1] = ts[0]; 826 if (change_time(SPOOL_DIR, ts) == -1) 827 warn("can't update times on spooldir %s", SPOOL_DIR); 828} 829 830/* int allowed(const char *username, const char *allow_file, const char *deny_file) 831 * returns TRUE if (allow_file exists and user is listed) 832 * or (deny_file exists and user is NOT listed). 833 * root is always allowed. 834 */ 835static int 836allowed(const char *username, const char *allow_file, const char *deny_file) { 837 FILE *fp; 838 int isallowed; 839 840 if (strcmp(username, ROOT_USER) == 0) 841 return (TRUE); 842#ifdef ALLOW_ONLY_ROOT 843 isallowed = FALSE; 844#else 845 isallowed = TRUE; 846#endif 847 if ((fp = fopen(allow_file, "r")) != NULL) { 848 isallowed = in_file(username, fp, FALSE); 849 (void)fclose(fp); 850 } else if ((fp = fopen(deny_file, "r")) != NULL) { 851 isallowed = !in_file(username, fp, FALSE); 852 (void)fclose(fp); 853 } 854 return (isallowed); 855} 856/* int in_file(const char *string, FILE *file, int error) 857 * return TRUE if one of the lines in file matches string exactly, 858 * FALSE if no lines match, and error on error. 859 */ 860static int 861in_file(const char *string, FILE *file, int error) 862{ 863 char line[MAX_TEMPSTR]; 864 char *endp; 865 866 if (fseek(file, 0L, SEEK_SET)) 867 return (error); 868 while (fgets(line, MAX_TEMPSTR, file)) { 869 if (line[0] != '\0') { 870 endp = &line[strlen(line) - 1]; 871 if (*endp != '\n') 872 return (error); 873 *endp = '\0'; 874 if (0 == strcmp(line, string)) 875 return (TRUE); 876 } 877 } 878 if (ferror(file)) 879 return (error); 880 return (FALSE); 881} 882 883#ifdef HAVE_SAVED_UIDS 884 885static int relinquish_priv(void) { 886 return (setegid(rgid) || seteuid(ruid)) ? -1 : 0; 887} 888 889static int regain_priv(void) { 890 return (setegid(egid) || seteuid(euid)) ? -1 : 0; 891} 892 893#else /*HAVE_SAVED_UIDS*/ 894 895static int relinquish_priv(void) { 896 return (setregid(egid, rgid) || setreuid(euid, ruid)) ? -1 : 0; 897} 898 899static int regain_priv(void) { 900 return (setregid(rgid, egid) || setreuid(ruid, euid)) ? -1 : 0; 901} 902#endif /*HAVE_SAVED_UIDS*/ 903