improve log format
[pve-firewall.git] / src / pvefw-logger.c
CommitLineData
ba0b3a0a
DM
1/*
2
3 Copyright (C) 2014 Proxmox Server Solutions GmbH
4
5 This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU Affero General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Affero General Public License for more details.
16
17 You should have received a copy of the GNU Affero General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 Author: Dietmar Maurer <dietmar@proxmox.com>
21
22*/
23
24#define _GNU_SOURCE
25
26#include <stdlib.h>
27#include <stdio.h>
28#include <errno.h>
29#include <stdarg.h>
30#include <string.h>
31#include <signal.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <fcntl.h>
35#include <unistd.h>
36#include <arpa/inet.h>
37#include <sys/socket.h>
38#include <sys/file.h>
39#include <linux/netlink.h>
40#include <libnfnetlink/libnfnetlink.h>
41#include <libnetfilter_log/libnetfilter_log.h>
42#include <netinet/ip.h>
43#include <netinet/ip_icmp.h>
44#include <netinet/udp.h>
45#include <netinet/tcp.h>
46#include <netinet/if_ether.h>
47
48#include <glib.h>
49#include <glib-unix.h>
50
51static struct nflog_handle *logh = NULL;
52static struct nlif_handle *nlifh = NULL;
53
782c4cde
DM
54/*
55
56LOG FORMAT:
57
58Special care was taken to allow fast parsing (and filer messages for a singl VM).
59
60<VMID> <LOGLEVEL> <CHAIN> <TIME> <TIMEZONE> <MSG>
61
62Example:
63
64117 6 tap117i0-IN 14/Mar/2014:12:47:07 +0100 policy REJECT: IN=vmbr1 ...
65
66*/
67
ba0b3a0a
DM
68#define LOGFILE "/var/log/pve-firewall.log"
69
70#define LOCKFILE "/var/lock/pvefw-logger.lck"
71#define PIDFILE "/var/run/pvefw-logger.pid"
72
73#define LQ_LEN 512
74#define LE_MAX (512 - 16) // try to fit into 512 bytes
75
782c4cde
DM
76#define MAX_CHAIN_LEN 28
77
ba0b3a0a
DM
78struct log_entry {
79 guint32 len; // max LE_MAX chars
80 char buf[LE_MAX];
81};
82
83int outfd = -1;
84
85gboolean terminate_threads = FALSE;
86
87static gboolean write_pidfile(pid_t pid)
88{
89 gboolean res;
90
91 char *strpid = g_strdup_printf("%d\n", pid);
92 res = g_file_set_contents(PIDFILE, strpid, strlen(strpid), NULL);
93 g_free(strpid);
94
95 return res;
96}
97
98static GAsyncQueue *queue;
99
100ssize_t
101safe_write(int fd, char *buf, size_t count)
102{
103 ssize_t n;
104
105 do {
106 n = write(fd, buf, count);
107 } while (n < 0 && errno == EINTR);
108
109 return n;
110}
111
112static gpointer
113log_writer_thread(gpointer data)
114{
115 while (1) {
116 struct log_entry *le = (struct log_entry *)g_async_queue_timeout_pop(queue, 250000);
117 if (le == NULL) {
118 if (terminate_threads) {
119 return NULL;
120 }
121 continue;
122 }
782c4cde
DM
123
124 printf("%s", le->buf);
ba0b3a0a
DM
125 int res = safe_write(outfd, le->buf, le->len);
126
127 g_free(le);
128
129 if (res < 0) {
130 // printf("write failed\n"); // fixme??
131 }
132 }
133
134 return NULL;
135}
136
137static int skipped_logs = 0;
138
782c4cde 139static void log_status_message(guint loglevel, const char *fmt, ...);
ba0b3a0a
DM
140
141static void
142queue_log_entry(struct log_entry *le)
143{
144 gint len = g_async_queue_length(queue);
145
146 if (skipped_logs > 0) {
147 if (len >= (LQ_LEN - 1)) {
148 skipped_logs++;
149 } else {
150 int skip_tmp = skipped_logs;
151 skipped_logs = 0; // clear before calling log_status_message()
782c4cde 152 log_status_message(3, "skipped %d log entries (queue full)", skip_tmp);
ba0b3a0a
DM
153 g_async_queue_push(queue, le);
154 }
155 } else {
156 if (len >= LQ_LEN) {
157 skipped_logs++;
158 } else {
159 g_async_queue_push(queue, le);
160 }
161 }
162}
163
164
165#define LEPRINTF(format, ...) { if (le->len < LE_MAX) le->len += snprintf(le->buf + le->len, LE_MAX - le->len, format, ##__VA_ARGS__); }
166#define LEPRINTTIME(sec) { time_t tmp_sec = sec; if (le->len < (LE_MAX - 30)) le->len += strftime(le->buf + le->len, LE_MAX - le->len, "%d/%b/%Y:%H:%M:%S %z ", localtime(&tmp_sec)); }
167
168static void
782c4cde 169log_status_message(guint loglevel, const char *fmt, ...)
ba0b3a0a
DM
170{
171 va_list ap;
172 va_start(ap, fmt);
173
782c4cde
DM
174 if (loglevel > 7 ) loglevel = 7; // syslog defines level 0-7
175
ba0b3a0a
DM
176 struct log_entry *le = g_new0(struct log_entry, 1);
177
782c4cde
DM
178 LEPRINTF("0 %d - ", loglevel);
179
ba0b3a0a
DM
180 LEPRINTTIME(time(NULL));
181
182 le->len += vsnprintf(le->buf + le->len, LE_MAX - le->len, fmt, ap);
183
184 LEPRINTF("\n");
185
186 queue_log_entry(le);
187}
188
189static int
190print_tcp(struct log_entry *le, struct tcphdr *h, int payload_len)
191{
192 LEPRINTF("PROTO=TCP ");
193
194 if (payload_len < sizeof(struct tcphdr)) {
195 LEPRINTF("LEN=%d ", payload_len);
196 LEPRINTF("INVALID=LEN ");
197 return -1;
198 }
199
200 LEPRINTF("SPT=%u DPT=%u ", ntohs(h->source), ntohs(h->dest));
201 LEPRINTF("SEQ=%u ACK=%u ", ntohl(h->seq), ntohl(h->ack_seq));
202 LEPRINTF("WINDOW=%u ", ntohs(h->window));
203
204 if (h->urg) LEPRINTF("URG ");
205 if (h->ack) LEPRINTF("ACK ");
206 if (h->psh) LEPRINTF("PSH ");
207 if (h->rst) LEPRINTF("RST ");
208 if (h->syn) LEPRINTF("SYN ");
209 if (h->fin) LEPRINTF("FIN ");
210
211 if (h->urg) LEPRINTF("URGP=%u ",ntohs(h->urg_ptr));
212
213 return 0;
214}
215
216static int
217print_udp(struct log_entry *le, struct udphdr *h, int payload_len)
218{
219 LEPRINTF("PROTO=UDP ");
220
221 if (payload_len < sizeof(struct udphdr)) {
222 LEPRINTF("LEN=%d ", payload_len);
223 LEPRINTF("INVALID=LEN ");
224 return -1;
225 }
226
227 LEPRINTF("SPT=%u DPT=%u LEN=%u", ntohs(h->source), ntohs(h->dest), ntohs(h->len));
228
229 return 0;
230}
231
232static int
233print_icmp(struct log_entry *le, struct icmphdr *h, int payload_len)
234{
235 char tmp[INET_ADDRSTRLEN];
236 u_int32_t gateway;
237
238 LEPRINTF("PROTO=ICMP ");
239
240 if (payload_len < sizeof(struct icmphdr)) {
241 LEPRINTF("LEN=%d ", payload_len);
242 LEPRINTF("INVALID=LEN ");
243 return -1;
244 }
245
246 LEPRINTF("TYPE=%u CODE=%u ", h->type, h->code);
247
248 switch (h->type) {
249 case ICMP_ECHO:
250 case ICMP_ECHOREPLY:
251 LEPRINTF("ID=%u SEQ=%u ", ntohs(h->un.echo.id), ntohs(h->un.echo.sequence));
252 break;
253 case ICMP_PARAMETERPROB:
254 LEPRINTF("PARAMETER=%u ", ntohl(h->un.gateway) >> 24);
255 break;
256 case ICMP_REDIRECT:
257 gateway = ntohl(h->un.gateway);
258 inet_ntop(AF_INET, &gateway, tmp, sizeof(tmp));
259 LEPRINTF("GATEWAY=%s ", tmp);
260 break;
261 case ICMP_DEST_UNREACH:
262 if (h->code == ICMP_FRAG_NEEDED) {
263 LEPRINTF("MTU=%u ", ntohs(h->un.frag.mtu));
264 }
265 break;
266 }
267
268 return 0;
269}
270
271/* Section 3.1. SCTP Common Header Format */
272typedef struct sctphdr {
273 __be16 source;
274 __be16 dest;
275 __be32 vtag;
276 __be32 checksum;
277} __attribute__((packed)) sctp_sctphdr_t;
278
279static int
280print_sctp(struct log_entry *le, struct sctphdr *h, int payload_len)
281{
282 LEPRINTF("PROTO=SCTP ");
283
284 if (payload_len < sizeof(struct sctphdr)) {
285 LEPRINTF("LEN=%d ", payload_len);
286 LEPRINTF("INVALID=LEN ");
287 return -1;
288 }
289
290 LEPRINTF("SPT=%u DPT=%u ", ntohs(h->source), ntohs(h->dest));
291
292 return 0;
293}
294
295static int
296print_iphdr(struct log_entry *le, char * payload, int payload_len)
297{
298 if (payload_len < sizeof(struct iphdr)) {
299 LEPRINTF("LEN=%d ", payload_len);
300 LEPRINTF("INVALID=LEN ");
301 return -1;
302 }
303
304 struct iphdr *h = (struct iphdr *)payload;
305
306 if (payload_len <= (u_int32_t)(h->ihl * 4)) {
307 LEPRINTF("INVALID=IHL ");
308 return -1;
309 }
310
311 char tmp[INET_ADDRSTRLEN];
312
313 inet_ntop(AF_INET, &h->saddr, tmp, sizeof(tmp));
314 LEPRINTF("SRC=%s ", tmp);
315 inet_ntop(AF_INET, &h->daddr, tmp, sizeof(tmp));
316 LEPRINTF("DST=%s ", tmp);
317
318 LEPRINTF("LEN=%u TOS=0x%02X PREC=0x%02X TTL=%u ID=%u ",
319 ntohs(h->tot_len), h->tos & IPTOS_TOS_MASK,
320 h->tos & IPTOS_PREC_MASK, h->ttl, ntohs(h->id));
321
322 short ip_off = ntohs(h->frag_off);
323 if (ip_off & IP_OFFMASK)
324 LEPRINTF("FRAG=%u ", ip_off & IP_OFFMASK);
325
326 if (ip_off & IP_DF) LEPRINTF("DF ");
327 if (ip_off & IP_MF) LEPRINTF("MF ");
328
329 void *nexthdr = (u_int32_t *)h + h->ihl;
330 payload_len -= h->ihl * 4;
331
332 switch (h->protocol) {
333 case IPPROTO_TCP:
334 print_tcp(le, (struct tcphdr *)nexthdr, payload_len);
335 break;
336 case IPPROTO_UDP:
337 print_udp(le, (struct udphdr *)nexthdr, payload_len);
338 break;
339 case IPPROTO_ICMP:
340 print_icmp(le, (struct icmphdr *)nexthdr, payload_len);
341 break;
342 case IPPROTO_SCTP:
343 print_sctp(le, (struct sctphdr *)nexthdr, payload_len);
344 break;
345 case IPPROTO_AH:
346 LEPRINTF("PROTO=AH ");
347 break;
348 case IPPROTO_ESP:
349 LEPRINTF("PROTO=ESP ");
350 break;
351 case IPPROTO_IGMP:
352 LEPRINTF("PROTO=IGMP ");
353 break;
354 default:
355 LEPRINTF("PROTO=%u ", h->protocol);
356 }
357
358 return 0;
359}
360
361static int
362print_ip6hdr(struct log_entry *le, char * payload, int payload_len)
363{
364 LEPRINTF("IPV6 logging not implemented ");
365
366 return 0;
367}
368
369// ebtables -I FORWARD --nflog --nflog-group 0
370static int
371print_arp(struct log_entry *le, struct ether_arp *h, int payload_len)
372{
373 if (payload_len < sizeof(struct ether_arp)) {
374 LEPRINTF("LEN=%d ", payload_len);
375 LEPRINTF("INVALID=LEN ");
376 return -1;
377 }
378
379 LEPRINTF("SRC=%u.%u.%u.%u ", h->arp_spa[0], h->arp_spa[1],
380 h->arp_spa[2], h->arp_spa[3]);
381
382 LEPRINTF("DST=%u.%u.%u.%u ", h->arp_tpa[0], h->arp_tpa[1],
383 h->arp_tpa[2], h->arp_tpa[3]);
384
385 LEPRINTF("PROTO=ARP ");
386
387 unsigned short code = ntohs(h->arp_op);
388 switch (code) {
389 case ARPOP_REQUEST:
390 LEPRINTF("REQUEST ");
391 break;
392 case ARPOP_REPLY:
393 LEPRINTF("REPLY MAC=%02x:%02x:%02x:%02x:%02x:%02x ",
394 h->arp_sha[0], h->arp_sha[1], h->arp_sha[2],
395 h->arp_sha[3], h->arp_sha[4], h->arp_sha[5]);
396 break;
397 case ARPOP_NAK:
398 LEPRINTF("NAK ");
399 break;
400 default:
401 LEPRINTF("CODE=%u ", code);
402 }
403
404
405 // LEPRINTF("HTYPE=%u ", ntohs(h->arp_hrd));
406
407 // LEPRINTF("PTYPE=%u ", ntohs(h->arp_pro));
408
409 return 0;
410}
411
412
413static int print_pkt(struct log_entry *le, struct nflog_data *ldata, u_int8_t family)
414{
415 u_int32_t mark = nflog_get_nfmark(ldata);
416 u_int32_t indev = nflog_get_indev(ldata);
417 u_int32_t outdev = nflog_get_outdev(ldata);
418 u_int32_t physindev = nflog_get_physindev(ldata);
419 u_int32_t physoutdev = nflog_get_physoutdev(ldata);
420
421 char *prefix = nflog_get_prefix(ldata);
422 char *payload;
423 char devname[256];
424
782c4cde
DM
425 guint32 vmid = 0;
426
427 guint8 log_level = 6; // info
428
429 char *chain_name = "-";
430
431 if (prefix != NULL) {
432 // Note: parse ":$vmid:$loglevel:$chain: $msg"
433 if (prefix[0] == ':') {
434 char *p = prefix + 1;
435 guint32 tmpid = 0;
436 while(*p >= '0' && *p <= '9') { tmpid *= 10; tmpid += *p - '0'; p++; }
437
438 if ((*p == ':') &&
439 (p[1] >= '0' && p[1] <= '7') &&
440 (p[2] == ':')) {
441
442 guint8 tmp_level = p[1] - '0'; // store for later use
443 char *chain_start = p + 3; // store for later use
444 p = chain_start;
445 while (*p && *p != ':' && *p != ' ') p++;
446 int len = p - chain_start;
447
448 if (*p == ':' && p[1] == ' ' && len && (len <= MAX_CHAIN_LEN)) {
449 // parsing successful
450
451 *p = 0; // terminate string
452
453 vmid = tmpid;
454 log_level = tmp_level;
455 chain_name = chain_start;
456 prefix = p + 2; // the rest
457 }
458 }
459 }
460 }
461
462 LEPRINTF("%d ", vmid);
463
464 LEPRINTF("%d ", log_level);
465
466 LEPRINTF("%s ", chain_name);
467
ba0b3a0a
DM
468 struct timeval ts;
469 nflog_get_timestamp(ldata, &ts);
470
471 LEPRINTTIME(ts.tv_sec);
472
ba0b3a0a
DM
473 if (prefix != NULL) {
474 LEPRINTF("%s", prefix);
475 }
476
477 if ((indev > 0) && (nlif_index2name(nlifh, indev, devname) != -1)) {
478 LEPRINTF("IN=%s ", devname);
479 }
480
481 if ((outdev > 0) && (nlif_index2name(nlifh, outdev, devname) != -1)) {
482 LEPRINTF("OUT=%s ", devname);
483 }
484
485 if ((physindev > 0) && (nlif_index2name(nlifh, physindev, devname) != -1)) {
486 LEPRINTF("PHYSIN=%s ", devname);
487 }
488
489 if ((physoutdev > 0) && (nlif_index2name(nlifh, physoutdev, devname) != -1)) {
490 LEPRINTF("PHYSOUT=%s ", devname);
491 }
492
493 int payload_len = nflog_get_payload(ldata, &payload);
494
495 int hwhdrlen = nflog_get_msg_packet_hwhdrlen(ldata);
496 if (hwhdrlen > 0) {
497 unsigned char *hwhdr = (unsigned char *)nflog_get_msg_packet_hwhdr(ldata);
498 if (hwhdr != NULL) {
499 int i;
500 LEPRINTF("MAC=");
501 for (i = 0; i < hwhdrlen; i++) {
502 LEPRINTF("%02x", hwhdr[i]);
503 if (i < (hwhdrlen -1 )) LEPRINTF(":");
504 }
505 LEPRINTF(" ");
506 }
507 }
508
509 u_int16_t hw_protocol = 0;
510 struct nfulnl_msg_packet_hdr *ph = NULL;
511
512 switch (family) {
513 case AF_INET:
514 print_iphdr(le, payload, payload_len);
515 break;
516 case AF_INET6:
517 print_ip6hdr(le, payload, payload_len);
518 break;
519 case AF_BRIDGE:
520 ph = nflog_get_msg_packet_hdr(ldata);
521 if (ph) hw_protocol = ntohs(ph->hw_protocol);
522
523 switch (hw_protocol) {
524 case ETH_P_IP:
525 print_iphdr(le, payload, payload_len);
526 break;
527 case ETH_P_IPV6:
528 print_ip6hdr(le, payload, payload_len);
529 break;
530 case ETH_P_ARP:
531 print_arp(le, (struct ether_arp *)payload, payload_len);
532 break;
533 }
534 break;
535 }
536
537 if (mark) LEPRINTF("mark=%u ", mark);
538
539
540 return 0;
541
542}
543
544static int
545nflog_cb(struct nflog_g_handle *gh, struct nfgenmsg *nfmsg,
546 struct nflog_data *nfa, void *data)
547{
548 struct log_entry *le = g_new0(struct log_entry, 1);
549
550 print_pkt(le, nfa, nfmsg->nfgen_family);
551
552 LEPRINTF("\n"); // add newline
553
554 queue_log_entry(le);
555
556 return 0;
557}
558
559static gboolean
560nflog_read_cb(GIOChannel *source,
561 GIOCondition condition,
562 gpointer data)
563{
564 int rv = 0;
565 gchar buf[8192];
566
567 int fd = g_io_channel_unix_get_fd(source);
568
569 if ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) {
570 nflog_handle_packet(logh, buf, rv);
571 }
572
573 return TRUE;
574}
575
576static gboolean
577nlif_read_cb(GIOChannel *source,
578 GIOCondition condition,
579 gpointer data)
580{
581 nlif_catch(nlifh);
582 // fixme: report errors
583 return TRUE;
584}
585
586GMainLoop *main_loop;
587
588static gboolean
589terminate_request(gpointer data)
590{
591 terminate_threads = TRUE;
592
782c4cde 593 log_status_message(5, "received terminate request (signal)");
ba0b3a0a
DM
594
595 g_main_loop_quit(main_loop);
596
597 return TRUE;
598}
599
600
601int
602main(int argc, char *argv[])
603{
604 int lockfd = -1;
605 gboolean foreground = FALSE;
606 gboolean wrote_pidfile = FALSE;
607
608 g_thread_init(NULL);
609
610 if ((lockfd = open(LOCKFILE, O_RDWR|O_CREAT|O_APPEND, 0644)) == -1) {
611 fprintf(stderr, "unable to create lock '%s': %s", LOCKFILE, strerror (errno));
612 exit(-1);
613 }
614
615 for (int i = 10; i >= 0; i--) {
616 if (flock(lockfd, LOCK_EX|LOCK_NB) != 0) {
617 if (!i) {
618 fprintf(stderr, "unable to aquire lock '%s': %s", LOCKFILE, strerror (errno));
619 exit(-1);
620 }
621 if (i == 10)
622 fprintf(stderr, "unable to aquire lock '%s' - trying again.\n", LOCKFILE);
623
624 sleep(1);
625 }
626 }
627
628 if ((outfd = open(LOGFILE, O_WRONLY|O_CREAT|O_APPEND, 0644)) == -1) {
629 fprintf(stderr, "unable to open file '%s': %s", LOGFILE, strerror (errno));
630 exit(-1);
631 }
632
633 if ((logh = nflog_open()) == NULL) {
634 fprintf(stderr, "unable to open nflog\n");
635 exit(-1);
636 }
637
638 if (!nflog_bind_pf(logh, AF_INET) <= 0) {
639 fprintf(stderr, "nflog_bind_pf AF_INET failed\n");
640 exit(-1);
641 }
642
643#if 0
644 if (!nflog_bind_pf(logh, AF_INET6) <= 0) {
645 fprintf(stderr, "nflog_bind_pf AF_INET6 failed\n");
646 exit(-1);
647 }
648#endif
649
650 if (!nflog_bind_pf(logh, AF_BRIDGE) <= 0) {
651 fprintf(stderr, "nflog_bind_pf AF_BRIDGE failed\n");
652 exit(-1);
653 }
654
655 struct nflog_g_handle *qh = nflog_bind_group(logh, 0);
656 if (!qh) {
657 fprintf(stderr, "no handle for group 1\n");
658 exit(-1);
659 }
660
661 if (nflog_set_mode(qh, NFULNL_COPY_PACKET, 0xffff) < 0) {
662 fprintf(stderr, "can't set packet copy mode\n");
663 exit(-1);
664 }
665
666 if ((nlifh = nlif_open()) == NULL) {
667 fprintf(stderr, "unable to open netlink interface handle\n");
668 exit(-1);
669 }
670
671 if (!foreground) {
672 pid_t cpid = fork();
673
674 if (cpid == -1) {
675 fprintf(stderr, "failed to daemonize program - %s\n", strerror (errno));
676 exit(-1);
677 } else if (cpid) {
678 write_pidfile(cpid);
679 _exit(0);
680 } else {
681 int nullfd;
682
683 if (chroot("/") != 0) fprintf(stderr, "chroot '/' failed - %s\n", strerror (errno));
684
685 if ((nullfd = open("/dev/null", O_RDWR, 0)) != -1) {
686 dup2(nullfd, 0);
687 dup2(nullfd, 1);
688 dup2(nullfd, 2);
689 if (nullfd > 2)
690 close (nullfd);
691 }
692
693 setsid();
694 }
695 } else {
696 write_pidfile(getpid());
697 }
698
699 wrote_pidfile = TRUE;
700
701 nflog_callback_register(qh, &nflog_cb, logh);
702
703 queue = g_async_queue_new_full(g_free);
704
782c4cde 705 log_status_message(5, "starting pvefw logger");
ba0b3a0a
DM
706
707 nlif_query(nlifh);
708
709 GIOChannel *nlif_ch = g_io_channel_unix_new(nlif_fd(nlifh));
710
711 g_io_add_watch(nlif_ch, G_IO_IN, nlif_read_cb, NULL);
712
713 int logfd = nflog_fd(logh);
714 GIOChannel *nflog_ch = g_io_channel_unix_new(logfd);
715
716 g_io_add_watch(nflog_ch, G_IO_IN, nflog_read_cb, NULL);
717
718 GThread *wthread = g_thread_new("log_writer_thread", log_writer_thread, NULL);
719
720 main_loop = g_main_loop_new(NULL, TRUE);
721
722 g_unix_signal_add(SIGINT, terminate_request, NULL);
723 g_unix_signal_add(SIGTERM, terminate_request, NULL);
724
725 g_main_loop_run(main_loop);
726
782c4cde 727 log_status_message(5, "stopping pvefw logger");
ba0b3a0a
DM
728
729 g_thread_join(wthread);
730
731 close(outfd);
732
733 nflog_close(logh);
734
735 if (wrote_pidfile)
736 unlink(PIDFILE);
737
738 exit(0);
739}