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