1 /* $NetBSD: crontab.c,v 1.13 2015/01/04 18:45:17 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.13 2015/01/04 18:45:17 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 static size_t 558 getmaxtabsize(void) 559 { 560 char n2[MAX_FNAME]; 561 FILE *fmaxtabsize; 562 size_t maxtabsize; 563 564 /* Make sure that the crontab is not an unreasonable size. 565 * 566 * XXX This is subject to a race condition--the user could 567 * add stuff to the file after we've checked the size but 568 * before we slurp it in and write it out. We can't just move 569 * the test to test the temp file we later create, because by 570 * that time we've already filled up the crontab disk. Probably 571 * the right thing to do is to do a bytecount in the copy loop 572 * rather than stating the file we're about to read. 573 */ 574 (void)snprintf(n2, sizeof(n2), "%s/%s", CRONDIR, MAXTABSIZE_FILE); 575 if ((fmaxtabsize = fopen(n2, "r")) != NULL) { 576 if (fgets(n2, (int)sizeof(n2), fmaxtabsize) == NULL) { 577 maxtabsize = 0; 578 } else { 579 maxtabsize = (size_t)atoi(n2); 580 } 581 (void)fclose(fmaxtabsize); 582 } else { 583 maxtabsize = MAXTABSIZE_DEFAULT; 584 } 585 return maxtabsize; 586 } 587 588 static int 589 checkmaxtabsize(FILE *fp, size_t maxtabsize) 590 { 591 struct stat statbuf; 592 593 if (fstat(fileno(fp), &statbuf)) { 594 warn("error stat'ing crontab input"); 595 return 0; 596 } 597 if ((uintmax_t)statbuf.st_size > (uintmax_t)maxtabsize) { 598 warnx("%ju bytes is larger than the maximum size of %ju bytes", 599 (uintmax_t)statbuf.st_size, (uintmax_t)maxtabsize); 600 return 0; 601 } 602 return 1; 603 } 604 605 static void 606 cleanTempFile(void) 607 { 608 609 if (TempFilename[0]) { 610 (void) unlink(TempFilename); 611 TempFilename[0] = '\0'; 612 } 613 } 614 615 static __dead void 616 bail(int signo) 617 { 618 619 cleanTempFile(); 620 errx(ERROR_EXIT, "Exiting on signal %d", signo); 621 } 622 623 /* returns 0 on success 624 * -1 on syntax error 625 * -2 on install error 626 */ 627 static int 628 replace_cmd(void) { 629 char n[MAX_FNAME], envstr[MAX_ENVSTR]; 630 int lastch; 631 FILE *tmp = NULL; 632 int ch, eof, fd; 633 int error = 0; 634 entry *e; 635 sig_t oint, oabrt, oquit, ohup; 636 uid_t file_owner; 637 time_t now = time(NULL); 638 char **envp = env_init(); 639 size_t i, maxtabsize; 640 641 if (envp == NULL) { 642 warn("Cannot allocate memory."); 643 return (-2); 644 } 645 646 if (!glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR, 647 "tmp.XXXXXXXXXX", '/')) { 648 TempFilename[0] = '\0'; 649 warnx("path too long"); 650 return (-2); 651 } 652 653 /* Interruptible while doing I/O */ 654 ohup = signal(SIGHUP, bail); 655 oint = signal(SIGINT, bail); 656 oquit = signal(SIGQUIT, bail); 657 oabrt = signal(SIGABRT, bail); 658 659 if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) { 660 warn("cannot create `%s'", TempFilename); 661 if (fd != -1) 662 (void)close(fd); 663 error = -2; 664 goto done; 665 } 666 667 maxtabsize = getmaxtabsize(); 668 669 /* This does not work for stdin, so we'll also check later */ 670 if (!checkmaxtabsize(NewCrontab, maxtabsize)) { 671 error = -2; 672 goto done; 673 } 674 675 /* write a signature at the top of the file. 676 * 677 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. 678 */ 679 (void)fprintf(tmp, 680 "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); 681 (void)fprintf(tmp, 682 "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); 683 (void)fprintf(tmp, 684 "# (Cron version %s -- %s)\n", CRON_VERSION, 685 "$NetBSD: crontab.c,v 1.13 2015/01/04 18:45:17 joerg Exp $"); 686 687 /* copy the crontab to the tmp 688 */ 689 (void)rewind(NewCrontab); 690 Set_LineNum(1); 691 lastch = EOF; 692 for (i = 0; i < maxtabsize && EOF != (ch = get_char(NewCrontab)); i++) 693 (void)putc(lastch = (char)ch, tmp); 694 695 /* Non-Interruptible while installing */ 696 (void)signal(SIGHUP, SIG_IGN); 697 (void)signal(SIGINT, SIG_IGN); 698 (void)signal(SIGQUIT, SIG_IGN); 699 (void)signal(SIGABRT, SIG_IGN); 700 701 if (i == maxtabsize) { 702 warnx("is larger than the maximum size of %ju bytes", 703 (uintmax_t)maxtabsize); 704 error = -2; 705 goto done; 706 } 707 708 if (lastch != EOF && lastch != '\n') { 709 warnx("missing trailing newline in `%s'", Filename); 710 error = -1; 711 goto done; 712 } 713 714 if (ferror(NewCrontab)) { 715 warn("error while reading `%s'", Filename); 716 error = -2; 717 goto done; 718 } 719 720 (void)ftruncate(fileno(tmp), ftell(tmp)); 721 /* XXX this should be a NOOP - is */ 722 (void)fflush(tmp); 723 724 if (ferror(tmp)) { 725 warn("error while writing new crontab to `%s'", TempFilename); 726 error = -2; 727 goto done; 728 } 729 730 /* check the syntax of the file being installed. 731 */ 732 733 /* BUG: was reporting errors after the EOF if there were any errors 734 * in the file proper -- kludged it by stopping after first error. 735 * vix 31mar87 736 */ 737 Set_LineNum(1 - NHEADER_LINES); 738 CheckErrorCount = 0; eof = FALSE; 739 rewind(tmp); 740 while (!CheckErrorCount && !eof) { 741 switch (load_env(envstr, tmp)) { 742 case ERR: 743 /* check for data before the EOF */ 744 if (envstr[0] != '\0') { 745 Set_LineNum(LineNumber + 1); 746 check_error("premature EOF"); 747 } 748 eof = TRUE; 749 break; 750 case FALSE: 751 e = load_entry(tmp, check_error, pw, envp); 752 if (e) 753 free(e); 754 break; 755 case TRUE: 756 break; 757 } 758 } 759 760 if (CheckErrorCount != 0) { 761 warnx("errors in crontab file, can't install."); 762 error = -1; 763 goto done; 764 } 765 766 file_owner = (getgid() == getegid()) ? ROOT_UID : pw->pw_uid; 767 768 #ifdef HAVE_FCHOWN 769 error = fchown(fileno(tmp), file_owner, (uid_t)-1); 770 #else 771 error = chown(TempFilename, file_owner, (gid_t)-1); 772 #endif 773 774 if (fclose(tmp) == EOF) { 775 warn("error closing file"); 776 error = -2; 777 tmp = NULL; 778 goto done; 779 } 780 tmp = NULL; 781 782 if (error < OK) { 783 warn("cannot chown `%s'", TempFilename); 784 error = -2; 785 goto done; 786 } 787 788 if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { 789 warnx("path too long"); 790 error = -2; 791 goto done; 792 } 793 if (rename(TempFilename, n)) { 794 warn("error renaming `%s' to `%s'", TempFilename, n); 795 error = -2; 796 goto done; 797 } 798 TempFilename[0] = '\0'; 799 log_it(RealUser, Pid, "REPLACE", User); 800 801 poke_daemon(); 802 803 done: 804 (void)signal(SIGHUP, ohup); 805 (void)signal(SIGINT, oint); 806 (void)signal(SIGQUIT, oquit); 807 (void)signal(SIGABRT, oabrt); 808 if (tmp != NULL) 809 (void)fclose(tmp); 810 cleanTempFile(); 811 return (error); 812 } 813 814 static void 815 poke_daemon(void) { 816 struct timespec ts[2]; 817 (void) clock_gettime(CLOCK_REALTIME, ts); 818 ts[1] = ts[0]; 819 if (change_time(SPOOL_DIR, ts) == -1) 820 warn("can't update times on spooldir %s", SPOOL_DIR); 821 } 822 823 /* int allowed(const char *username, const char *allow_file, const char *deny_file) 824 * returns TRUE if (allow_file exists and user is listed) 825 * or (deny_file exists and user is NOT listed). 826 * root is always allowed. 827 */ 828 static int 829 allowed(const char *username, const char *allow_file, const char *deny_file) { 830 FILE *fp; 831 int isallowed; 832 833 if (strcmp(username, ROOT_USER) == 0) 834 return (TRUE); 835 #ifdef ALLOW_ONLY_ROOT 836 isallowed = FALSE; 837 #else 838 isallowed = TRUE; 839 #endif 840 if ((fp = fopen(allow_file, "r")) != NULL) { 841 isallowed = in_file(username, fp, FALSE); 842 (void)fclose(fp); 843 } else if ((fp = fopen(deny_file, "r")) != NULL) { 844 isallowed = !in_file(username, fp, FALSE); 845 (void)fclose(fp); 846 } 847 return (isallowed); 848 } 849 /* int in_file(const char *string, FILE *file, int error) 850 * return TRUE if one of the lines in file matches string exactly, 851 * FALSE if no lines match, and error on error. 852 */ 853 static int 854 in_file(const char *string, FILE *file, int error) 855 { 856 char line[MAX_TEMPSTR]; 857 char *endp; 858 859 if (fseek(file, 0L, SEEK_SET)) 860 return (error); 861 while (fgets(line, MAX_TEMPSTR, file)) { 862 if (line[0] != '\0') { 863 endp = &line[strlen(line) - 1]; 864 if (*endp != '\n') 865 return (error); 866 *endp = '\0'; 867 if (0 == strcmp(line, string)) 868 return (TRUE); 869 } 870 } 871 if (ferror(file)) 872 return (error); 873 return (FALSE); 874 } 875 876 #ifdef HAVE_SAVED_UIDS 877 878 static int relinguish_priv(void) { 879 return (setegid(rgid) || seteuid(ruid)) ? -1 : 0; 880 } 881 882 static int regain_priv(void) { 883 return (setegid(egid) || seteuid(euid)) ? -1 : 0; 884 } 885 886 #else /*HAVE_SAVED_UIDS*/ 887 888 static int relinguish_priv(void) { 889 return (setregid(egid, rgid) || setreuid(euid, ruid)) ? -1 : 0; 890 } 891 892 static int regain_priv(void) { 893 return (setregid(rgid, egid) || setreuid(ruid, euid)) ? -1 : 0; 894 } 895 #endif /*HAVE_SAVED_UIDS*/ 896