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