1 /* $NetBSD: pflogd.c,v 1.13 2024/09/03 09:03:18 rin Exp $ */ 2 /* $OpenBSD: pflogd.c,v 1.45 2007/06/06 14:11:26 henning Exp $ */ 3 4 /* 5 * Copyright (c) 2001 Theo de Raadt 6 * Copyright (c) 2001 Can Erkin Acar 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 13 * - Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * - Redistributions in binary form must reproduce the above 16 * copyright notice, this list of conditions and the following 17 * disclaimer in the documentation and/or other materials provided 18 * with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 * POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include <sys/types.h> 35 #include <sys/ioctl.h> 36 #include <sys/file.h> 37 #include <sys/stat.h> 38 #include <sys/socket.h> 39 #include <net/if.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <unistd.h> 44 /* 45 * If we're going to include parts of the libpcap internals we MUST 46 * set the feature-test macros they expect, or they may misbehave. 47 */ 48 #define HAVE_STRLCPY 49 #define HAVE_SNPRINTF 50 #define HAVE_VSNPRINTF 51 #include <pcap-int.h> 52 #include <pcap.h> 53 #include <syslog.h> 54 #include <signal.h> 55 #include <err.h> 56 #include <errno.h> 57 #include <stdarg.h> 58 #include <fcntl.h> 59 #include <util.h> 60 #include "pflogd.h" 61 62 pcap_t *hpcap; 63 static FILE *dpcap; 64 65 int Debug = 0; 66 static uint32_t snaplen = DEF_SNAPLEN; 67 static uint32_t cur_snaplen = DEF_SNAPLEN; 68 69 volatile sig_atomic_t gotsig_close, gotsig_alrm, gotsig_hup; 70 71 const char *filename = PFLOGD_LOG_FILE; 72 const char *interface = PFLOGD_DEFAULT_IF; 73 const char *filter = NULL; 74 75 char errbuf[PCAP_ERRBUF_SIZE]; 76 77 int log_debug = 0; 78 unsigned int delay = FLUSH_DELAY; 79 80 char *copy_argv(char * const *); 81 void dump_packet(u_char *, const struct pcap_pkthdr *, const u_char *); 82 void dump_packet_nobuf(u_char *, const struct pcap_pkthdr *, const u_char *); 83 int flush_buffer(FILE *); 84 int if_exists(const char *); 85 int init_pcap(void); 86 void logmsg(int, const char *, ...); 87 void purge_buffer(void); 88 int reset_dump(int); 89 int scan_dump(FILE *, off_t); 90 int set_snaplen(uint32_t); 91 void set_suspended(int); 92 void sig_alrm(int); 93 void sig_close(int); 94 void sig_hup(int); 95 void usage(void); 96 97 static int try_reset_dump(int); 98 99 /* buffer must always be greater than snaplen */ 100 static size_t bufpkt = 0; /* number of packets in buffer */ 101 static size_t buflen = 0; /* allocated size of buffer */ 102 static char *buffer = NULL; /* packet buffer */ 103 static char *bufpos = NULL; /* position in buffer */ 104 static size_t bufleft = 0; /* bytes left in buffer */ 105 106 /* if error, stop logging but count dropped packets */ 107 static int suspended = -1; 108 static long packets_dropped = 0; 109 110 /* 111 * XXX Taken from libpcap. These are no longer exposed for >= 1.10.5, 112 * for which tv_{,u}sec were converted to unsigned. 113 */ 114 struct pcap_timeval { 115 uint32_t tv_sec; 116 uint32_t tv_usec; 117 }; 118 struct pcap_sf_pkthdr { 119 struct pcap_timeval ts; 120 uint32_t caplen; 121 uint32_t len; 122 }; 123 124 void 125 set_suspended(int s) 126 { 127 if (suspended == s) 128 return; 129 130 suspended = s; 131 setproctitle("[%s] -s %d -i %s -f %s", 132 suspended ? "suspended" : "running", 133 cur_snaplen, interface, filename); 134 } 135 136 char * 137 copy_argv(char * const *argv) 138 { 139 size_t len = 0, n; 140 char *buf; 141 142 if (argv == NULL) 143 return (NULL); 144 145 for (n = 0; argv[n]; n++) 146 len += strlen(argv[n])+1; 147 if (len == 0) 148 return (NULL); 149 150 buf = malloc(len); 151 if (buf == NULL) 152 return (NULL); 153 154 strlcpy(buf, argv[0], len); 155 for (n = 1; argv[n]; n++) { 156 strlcat(buf, " ", len); 157 strlcat(buf, argv[n], len); 158 } 159 return (buf); 160 } 161 162 void 163 logmsg(int pri, const char *message, ...) 164 { 165 va_list ap; 166 va_start(ap, message); 167 168 if (log_debug) { 169 vfprintf(stderr, message, ap); 170 fprintf(stderr, "\n"); 171 } else 172 vsyslog(pri, message, ap); 173 va_end(ap); 174 } 175 176 __dead void 177 usage(void) 178 { 179 fprintf(stderr, "usage: pflogd [-Dx] [-d delay] [-f filename]"); 180 fprintf(stderr, " [-i interface] [-p pidfile]\n"); 181 fprintf(stderr, " [-s snaplen] [expression]\n"); 182 exit(1); 183 } 184 185 void 186 sig_close(int sig) 187 { 188 gotsig_close = 1; 189 } 190 191 void 192 sig_hup(int sig) 193 { 194 gotsig_hup = 1; 195 } 196 197 void 198 sig_alrm(int sig) 199 { 200 gotsig_alrm = 1; 201 } 202 203 void 204 set_pcap_filter(void) 205 { 206 struct bpf_program bprog; 207 208 if (pcap_compile(hpcap, &bprog, filter, PCAP_OPT_FIL, 0) < 0) 209 logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap)); 210 else { 211 if (pcap_setfilter(hpcap, &bprog) < 0) 212 logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap)); 213 pcap_freecode(&bprog); 214 } 215 } 216 217 int 218 if_exists(const char *ifname) 219 { 220 int s; 221 #ifdef SIOCGIFDATA 222 struct ifdatareq ifr; 223 #define ifr_name ifdr_name 224 #else 225 struct ifreq ifr; 226 struct if_data ifrdat; 227 #endif 228 229 if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 230 err(1, "socket"); 231 bzero(&ifr, sizeof(ifr)); 232 if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >= 233 sizeof(ifr.ifr_name)) 234 errx(1, "main ifr_name: strlcpy"); 235 #ifndef ifr_name 236 ifr.ifr_data = (caddr_t)&ifrdat; 237 #endif 238 if (ioctl(s, SIOCGIFDATA, (caddr_t)&ifr) == -1) 239 return (0); 240 if (close(s)) 241 err(1, "close"); 242 243 return (1); 244 } 245 246 int 247 init_pcap(void) 248 { 249 hpcap = pcap_open_live(interface, snaplen, 1, PCAP_TO_MS, errbuf); 250 if (hpcap == NULL) { 251 logmsg(LOG_ERR, "Failed to initialize: %s", errbuf); 252 return (-1); 253 } 254 255 if (pcap_datalink(hpcap) != DLT_PFLOG) { 256 logmsg(LOG_ERR, "Invalid datalink type"); 257 pcap_close(hpcap); 258 hpcap = NULL; 259 return (-1); 260 } 261 262 set_pcap_filter(); 263 264 cur_snaplen = snaplen = pcap_snapshot(hpcap); 265 266 /* lock */ 267 #ifdef __OpenBSD__ 268 if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) { 269 logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno)); 270 return (-1); 271 } 272 #endif 273 274 return (0); 275 } 276 277 int 278 set_snaplen(uint32_t snap) 279 { 280 if (priv_set_snaplen(snap)) 281 return (1); 282 283 if (cur_snaplen > snap) 284 purge_buffer(); 285 286 cur_snaplen = snap; 287 288 return (0); 289 } 290 291 int 292 reset_dump(int nomove) 293 { 294 int ret; 295 296 for (;;) { 297 ret = try_reset_dump(nomove); 298 if (ret <= 0) 299 break; 300 } 301 302 return (ret); 303 } 304 305 /* 306 * tries to (re)open log file, nomove flag is used with -x switch 307 * returns 0: success, 1: retry (log moved), -1: error 308 */ 309 int 310 try_reset_dump(int nomove) 311 { 312 struct pcap_file_header hdr; 313 struct stat st; 314 int fd; 315 FILE *fp; 316 317 if (hpcap == NULL) 318 return (-1); 319 320 if (dpcap) { 321 flush_buffer(dpcap); 322 fclose(dpcap); 323 dpcap = NULL; 324 } 325 326 /* 327 * Basically reimplement pcap_dump_open() because it truncates 328 * files and duplicates headers and such. 329 */ 330 fd = priv_open_log(); 331 if (fd < 0) 332 return (-1); 333 334 fp = fdopen(fd, "a+"); 335 336 if (fp == NULL) { 337 logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno)); 338 close(fd); 339 return (-1); 340 } 341 if (fstat(fileno(fp), &st) == -1) { 342 logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno)); 343 fclose(fp); 344 return (-1); 345 } 346 347 /* set FILE unbuffered, we do our own buffering */ 348 if (setvbuf(fp, NULL, _IONBF, 0)) { 349 logmsg(LOG_ERR, "Failed to set output buffers"); 350 fclose(fp); 351 return (-1); 352 } 353 354 #define TCPDUMP_MAGIC 0xa1b2c3d4 355 356 if (st.st_size == 0) { 357 if (snaplen != cur_snaplen) { 358 logmsg(LOG_NOTICE, "Using snaplen %d", snaplen); 359 if (set_snaplen(snaplen)) 360 logmsg(LOG_WARNING, 361 "Failed, using old settings"); 362 } 363 hdr.magic = TCPDUMP_MAGIC; 364 hdr.version_major = PCAP_VERSION_MAJOR; 365 hdr.version_minor = PCAP_VERSION_MINOR; 366 hdr.thiszone = 0; 367 hdr.sigfigs = 0; 368 hdr.snaplen = hpcap->snapshot; 369 hdr.linktype = hpcap->linktype; 370 371 if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) { 372 fclose(fp); 373 return (-1); 374 } 375 } else if (scan_dump(fp, st.st_size)) { 376 fclose(fp); 377 if (nomove || priv_move_log()) { 378 logmsg(LOG_ERR, 379 "Invalid/incompatible log file, move it away"); 380 return (-1); 381 } 382 return (1); 383 } 384 385 dpcap = fp; 386 387 set_suspended(0); 388 flush_buffer(fp); 389 390 return (0); 391 } 392 393 int 394 scan_dump(FILE *fp, off_t size) 395 { 396 struct pcap_file_header hdr; 397 #ifdef __OpenBSD__ 398 struct pcap_pkthdr ph; 399 #else 400 struct pcap_sf_pkthdr ph; 401 #endif 402 off_t pos; 403 404 /* 405 * Must read the file, compare the header against our new 406 * options (in particular, snaplen) and adjust our options so 407 * that we generate a correct file. Furthermore, check the file 408 * for consistency so that we can append safely. 409 * 410 * XXX this may take a long time for large logs. 411 */ 412 (void) fseek(fp, 0L, SEEK_SET); 413 414 if (fread((char *)&hdr, sizeof(hdr), 1, fp) != 1) { 415 logmsg(LOG_ERR, "Short file header"); 416 return (1); 417 } 418 419 if (hdr.magic != TCPDUMP_MAGIC || 420 hdr.version_major != PCAP_VERSION_MAJOR || 421 hdr.version_minor != PCAP_VERSION_MINOR || 422 hdr.linktype != (uint32_t)hpcap->linktype || 423 hdr.snaplen > PFLOGD_MAXSNAPLEN) { 424 return (1); 425 } 426 427 pos = sizeof(hdr); 428 429 while (!feof(fp)) { 430 off_t len = fread((char *)&ph, 1, sizeof(ph), fp); 431 if (len == 0) 432 break; 433 434 if (len != sizeof(ph)) 435 goto error; 436 if (ph.caplen > hdr.snaplen || ph.caplen > PFLOGD_MAXSNAPLEN) 437 goto error; 438 pos += sizeof(ph) + ph.caplen; 439 if (pos > size) 440 goto error; 441 fseek(fp, ph.caplen, SEEK_CUR); 442 } 443 444 if (pos != size) 445 goto error; 446 447 if (hdr.snaplen != cur_snaplen) { 448 logmsg(LOG_WARNING, 449 "Existing file has different snaplen %u, using it", 450 hdr.snaplen); 451 if (set_snaplen(hdr.snaplen)) { 452 logmsg(LOG_WARNING, 453 "Failed, using old settings, offset %llu", 454 (unsigned long long) size); 455 } 456 } 457 458 return (0); 459 460 error: 461 logmsg(LOG_ERR, "Corrupted log file."); 462 return (1); 463 } 464 465 /* dump a packet directly to the stream, which is unbuffered */ 466 void 467 dump_packet_nobuf(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) 468 { 469 #ifndef __OpenBSD__ 470 struct pcap_sf_pkthdr sf_hdr; 471 #endif 472 FILE *f = (FILE *)user; 473 474 if (suspended) { 475 packets_dropped++; 476 return; 477 } 478 479 #ifndef __OpenBSD__ 480 sf_hdr.ts.tv_sec = h->ts.tv_sec; 481 sf_hdr.ts.tv_usec = h->ts.tv_usec; 482 sf_hdr.caplen = h->caplen; 483 sf_hdr.len = h->len; 484 #endif 485 486 #ifdef __OpenBSD__ 487 if (fwrite((char *)h, sizeof(*h), 1, f) != 1) { 488 #else 489 if (fwrite(&sf_hdr, sizeof(sf_hdr), 1, f) != 1) { 490 #endif 491 /* try to undo header to prevent corruption */ 492 size_t pos = (size_t)ftello(f); 493 #ifdef __OpenBSD__ 494 if (pos < sizeof(*h) || 495 ftruncate(fileno(f), pos - sizeof(*h))) { 496 #else 497 if (pos < sizeof(sf_hdr) || 498 ftruncate(fileno(f), pos - sizeof(sf_hdr))) { 499 #endif 500 logmsg(LOG_ERR, "Write failed, corrupted logfile!"); 501 set_suspended(1); 502 gotsig_close = 1; 503 return; 504 } 505 goto error; 506 } 507 508 if (fwrite(sp, h->caplen, 1, f) != 1) 509 goto error; 510 511 return; 512 513 error: 514 set_suspended(1); 515 packets_dropped ++; 516 logmsg(LOG_ERR, "Logging suspended: fwrite: %s", strerror(errno)); 517 } 518 519 int 520 flush_buffer(FILE *f) 521 { 522 off_t offset; 523 int len = bufpos - buffer; 524 525 if (len <= 0) 526 return (0); 527 528 offset = ftello(f); 529 if (offset == (off_t)-1) { 530 set_suspended(1); 531 logmsg(LOG_ERR, "Logging suspended: ftello: %s", 532 strerror(errno)); 533 return (1); 534 } 535 536 if (fwrite(buffer, len, 1, f) != 1) { 537 set_suspended(1); 538 logmsg(LOG_ERR, "Logging suspended: fwrite: %s", 539 strerror(errno)); 540 ftruncate(fileno(f), offset); 541 return (1); 542 } 543 544 set_suspended(0); 545 bufpos = buffer; 546 bufleft = buflen; 547 bufpkt = 0; 548 549 return (0); 550 } 551 552 void 553 purge_buffer(void) 554 { 555 packets_dropped += bufpkt; 556 557 set_suspended(0); 558 bufpos = buffer; 559 bufleft = buflen; 560 bufpkt = 0; 561 } 562 563 /* append packet to the buffer, flushing if necessary */ 564 void 565 dump_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) 566 { 567 FILE *f = (FILE *)user; 568 #ifdef __OpenBSD__ 569 size_t len = sizeof(*h) + h->caplen; 570 #else 571 struct pcap_sf_pkthdr sf_hdr; 572 size_t len = sizeof(sf_hdr) + h->caplen; 573 #endif 574 575 if (len < sizeof(*h) || h->caplen > (size_t)cur_snaplen) { 576 logmsg(LOG_NOTICE, "invalid size %zu (%u/%u), packet dropped", 577 len, cur_snaplen, snaplen); 578 packets_dropped++; 579 return; 580 } 581 582 if (len <= bufleft) 583 goto append; 584 585 if (suspended) { 586 packets_dropped++; 587 return; 588 } 589 590 if (flush_buffer(f)) { 591 packets_dropped++; 592 return; 593 } 594 595 if (len > bufleft) { 596 dump_packet_nobuf(user, h, sp); 597 return; 598 } 599 600 append: 601 #ifdef __OpenBSD__ 602 memcpy(bufpos, h, sizeof(*h)); 603 memcpy(bufpos + sizeof(*h), sp, h->caplen); 604 #else 605 sf_hdr.ts.tv_sec = h->ts.tv_sec; 606 sf_hdr.ts.tv_usec = h->ts.tv_usec; 607 sf_hdr.caplen = h->caplen; 608 sf_hdr.len = h->len; 609 610 memcpy(bufpos, &sf_hdr, sizeof(sf_hdr)); 611 memcpy(bufpos + sizeof(sf_hdr), sp, h->caplen); 612 #endif 613 614 bufpos += len; 615 bufleft -= len; 616 bufpkt++; 617 618 return; 619 } 620 621 int 622 main(int argc, char **argv) 623 { 624 struct pcap_stat pstat; 625 int ch, np, ret, Xflag = 0; 626 pcap_handler phandler = dump_packet; 627 const char *errstr = NULL; 628 char *pidf = NULL; 629 630 ret = 0; 631 632 closefrom(STDERR_FILENO + 1); 633 634 while ((ch = getopt(argc, argv, "Dxd:f:i:p:s:")) != -1) { 635 switch (ch) { 636 case 'D': 637 Debug = 1; 638 break; 639 case 'd': 640 delay = strtonum(optarg, 5, 60*60, &errstr); 641 if (errstr) 642 usage(); 643 break; 644 case 'f': 645 filename = optarg; 646 break; 647 case 'i': 648 interface = optarg; 649 break; 650 case 'p': 651 pidf = optarg; 652 break; 653 case 's': 654 snaplen = strtonum(optarg, 0, PFLOGD_MAXSNAPLEN, 655 &errstr); 656 if (snaplen <= 0) 657 snaplen = DEF_SNAPLEN; 658 if (errstr) 659 snaplen = PFLOGD_MAXSNAPLEN; 660 break; 661 case 'x': 662 Xflag++; 663 break; 664 default: 665 usage(); 666 } 667 668 } 669 670 log_debug = Debug; 671 argc -= optind; 672 argv += optind; 673 674 /* does interface exist */ 675 if (!if_exists(interface)) { 676 warn("Failed to initialize: %s", interface); 677 logmsg(LOG_ERR, "Failed to initialize: %s", interface); 678 logmsg(LOG_ERR, "Exiting, init failure"); 679 exit(1); 680 } 681 682 if (!Debug) { 683 openlog("pflogd", LOG_PID | LOG_CONS, LOG_DAEMON); 684 if (daemon(0, 0)) { 685 logmsg(LOG_WARNING, "Failed to become daemon: %s", 686 strerror(errno)); 687 } 688 pidfile(pidf); 689 } 690 691 tzset(); 692 (void)umask(S_IRWXG | S_IRWXO); 693 694 /* filter will be used by the privileged process */ 695 if (argc) { 696 filter = copy_argv(argv); 697 if (filter == NULL) 698 logmsg(LOG_NOTICE, "Failed to form filter expression"); 699 } 700 701 /* initialize pcap before dropping privileges */ 702 if (init_pcap()) { 703 logmsg(LOG_ERR, "Exiting, init failure"); 704 exit(1); 705 } 706 707 /* Privilege separation begins here */ 708 if (priv_init()) { 709 logmsg(LOG_ERR, "unable to privsep"); 710 exit(1); 711 } 712 713 setproctitle("[initializing]"); 714 /* Process is now unprivileged and inside a chroot */ 715 signal(SIGTERM, sig_close); 716 signal(SIGINT, sig_close); 717 signal(SIGQUIT, sig_close); 718 signal(SIGALRM, sig_alrm); 719 signal(SIGHUP, sig_hup); 720 alarm(delay); 721 722 buffer = malloc(PFLOGD_BUFSIZE); 723 724 if (buffer == NULL) { 725 logmsg(LOG_WARNING, "Failed to allocate output buffer"); 726 phandler = dump_packet_nobuf; 727 } else { 728 bufleft = buflen = PFLOGD_BUFSIZE; 729 bufpos = buffer; 730 bufpkt = 0; 731 } 732 733 if (reset_dump(Xflag) < 0) { 734 if (Xflag) 735 return (1); 736 737 logmsg(LOG_ERR, "Logging suspended: open error"); 738 set_suspended(1); 739 } else if (Xflag) 740 return (0); 741 742 while (1) { 743 np = pcap_dispatch(hpcap, PCAP_NUM_PKTS, 744 phandler, (u_char *)dpcap); 745 if (np < 0) { 746 if (!if_exists(interface)) { 747 logmsg(LOG_NOTICE, "interface %s went away", 748 interface); 749 ret = -1; 750 break; 751 } 752 logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap)); 753 } 754 755 if (gotsig_close) 756 break; 757 if (gotsig_hup) { 758 if (reset_dump(0)) { 759 logmsg(LOG_ERR, 760 "Logging suspended: open error"); 761 set_suspended(1); 762 } 763 gotsig_hup = 0; 764 } 765 766 if (gotsig_alrm) { 767 if (dpcap) 768 flush_buffer(dpcap); 769 else 770 gotsig_hup = 1; 771 gotsig_alrm = 0; 772 alarm(delay); 773 } 774 } 775 776 logmsg(LOG_NOTICE, "Exiting"); 777 if (dpcap) { 778 flush_buffer(dpcap); 779 fclose(dpcap); 780 } 781 purge_buffer(); 782 783 if (pcap_stats(hpcap, &pstat) < 0) 784 logmsg(LOG_WARNING, "Reading stats: %s", pcap_geterr(hpcap)); 785 else 786 logmsg(LOG_NOTICE, 787 "%u packets received, %u/%ld dropped (kernel/pflogd)", 788 pstat.ps_recv, pstat.ps_drop, packets_dropped); 789 790 pcap_close(hpcap); 791 if (!Debug) 792 closelog(); 793 return (ret); 794 } 795