/* Converts a trace of DNS packets (pcap format) to a PostgreSQL
   database. For demonstration only, do not rely too much on
   it. 

   S. Bortzmeyer <bortzmeyer@afnic.fr> */

/* Standard headers */
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>
#include <unistd.h>
#include <time.h>

/* Application-specific headers */
#include <postgresql/libpq-fe.h>
#include <pcap.h>

/* pcap variables, constants and macros */

/* Ethernet addresses are 6 bytes */
#define ETHER_ADDR_LEN	6

/* Ethernet headers are always exactly 14 bytes */
#define SIZE_ETHERNET 14

#define SIZE_IPv6 40

#define SIZE_FRAGMENT_HDR 8

#define SIZE_UDP 8

#define SIZE_DNS 12

#define IPv4_ETHERTYPE 0x800

#define IPv6_ETHERTYPE 0x86DD

#define UDP 17

#define DNS_PORT 53

/* Ethernet header */
struct sniff_ethernet {
    uint8_t         ether_dhost[ETHER_ADDR_LEN];        /* Destination host address */
    uint8_t         ether_shost[ETHER_ADDR_LEN];        /* Source host address */
    uint16_t        ether_type; /* IP? ARP? RARP? etc */
};

/* IPv6 header. RFC 2460, section3. Reading /usr/include/netinet/ip6.h is
 * interesting */
struct sniff_ipv6 {
    uint32_t        ip_vtcfl;   /* version << 4 then traffic class and flow label */
    uint16_t        ip_len;     /* payload length */
    uint8_t         ip_nxt;     /* next header (protocol) */
    uint8_t         ip_hopl;    /* hop limit (ttl) */
    struct in6_addr ip_src, ip_dst;     /* source and dest address */
};
#define IPV6_VERSION(ip)          (ntohl((ip)->ip_vtcfl) >> 28)

struct sniff_eh {
    uint8_t         eh_next;    /* next header (protocol) */
    uint8_t         eh_length;
};

struct sniff_frag {
    uint8_t         frag_next;  /* next header (protocol) */
    uint8_t         frag_reserved;
    uint16_t        frag_offset_res_m;  /* Fragment offset then Reserved then M flag 
                                         */
    uint32_t        frag_identification;
};
#define FRAG_OFFSET(frag) (ntohs(frag->frag_offset_res_m) >> 3)

/* IPv4 header */
struct sniff_ipv4 {
    uint8_t         ip_vhl;     /* version << 4 | header length >> 2 */
    uint8_t         ip_tos;     /* type of service */
    uint16_t        ip_len;     /* total length */
    uint16_t        ip_id;      /* identification */
    uint16_t        ip_off;     /* fragment offset field */
#define IP_RF 0x8000            /* reserved fragment flag */
#define IP_DF 0x4000            /* dont fragment flag */
#define IP_MF 0x2000            /* more fragments flag */
#define IP_OFFMASK 0x1fff       /* mask for fragmenting bits */
    uint8_t         ip_ttl;     /* time to live */
    uint8_t         ip_p;       /* protocol */
    uint16_t        ip_sum;     /* checksum */
    struct in_addr  ip_src, ip_dst;     /* source and dest address */
};
#define IP_HL(ip)               (((ip)->ip_vhl) & 0x0f)
#define IPV4_VERSION(ip)                (((ip)->ip_vhl) >> 4)

/* UDP header */
struct sniff_udp {
    uint16_t        sport;      /* source port */
    uint16_t        dport;      /* destination port */
    uint16_t        udp_length;
    uint16_t        udp_sum;    /* checksum */
};

struct sniff_dns {
    /* RFC 1035, section 4.1 */
    /* This is only the DNS header, the sections (Question, Answer, etc) follow */
    uint16_t        query_id;
    uint16_t        codes;
    uint16_t        qdcount, ancount, nscount, arcount;
};
#define DNS_QR(dns)		((ntohs((dns)->codes) & 0x8000) >> 15)
#define DNS_OPCODE(dns)	((ntohs((dns)->codes) >> 11) & 0x000F)
#define DNS_RCODE(dns)	(ntohs((dns)->codes) & 0x000F)
#define DNS_AA(dns)   ((ntohs((dns)->codes) & 0x0400) >> 10)
#define DNS_TC(dns)   ((ntohs((dns)->codes) & 0x0200) >> 9)
#define DNS_RD(dns)   ((ntohs((dns)->codes) & 0x0100) >> 8)
#define DNS_RA(dns)   ((ntohs((dns)->codes) & 0x0080) >> 7)

/* RR types */
#define OPT 41

#define MAX_NAME 255
/* End of pcap variables, constants and macros */

/* Our data structures and utilities */

static char    *progname;

static void
fatal(char *msg, ...)
{
    va_list         args;
    va_start(args, msg);
    fprintf(stderr, "Fatal error: ");
    (void) vfprintf(stderr, msg, args);
    va_end(args);
    fprintf(stderr, "\n");
    exit(EXIT_FAILURE);
}

static void
usage()
{
    (void) fprintf(stderr, "Usage: %s filename.pcap\n", progname);
    exit(EXIT_FAILURE);
}

struct dns_packet {
    unsigned int    rank;
    struct timeval  date;
    unsigned int    captured_length;
    unsigned int    length;     /* Original length on the cable */
    char           *src;
    char           *dst;
    char           *protocol;
    unsigned int    src_port, dst_port;
    bool            query;
    int             query_id;
    int             opcode;
    int             returncode;
    bool            aa, tc, rd, ra;
    char           *qname;
    int             qtype, qclass;
    bool            edns0;
    int             edns0_size;
    bool            do_dnssec;
    int             ancount, nscount, arcount;
};

#define SQL_PACKET_COMMAND "INSERT INTO DNS_Packets \
           (file, rank, date, length, src_address, dst_address, protocol, src_port, dst_port, \
            query, query_id, opcode, rcode, aa, tc, rd, ra, qname, qtype, edns0_size, do_dnssec, \
            ancount, nscount, arcount) \
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, \
                   $16, $17, $18, $19, $20, $21, $22, $23, $24);"
#define NUM_PACKET_PARAMS 24
#define PREPARED_PACKET_STMT "insert-data"
#define SQL_FILE_COMMAND "INSERT INTO Pcap_Files (filename) VALUES ($1) RETURNING id;"
#define NUM_FILE_PARAMS 1
#define PREPARED_FILE_STMT "insert-filename"
#define SQL_FILEEND_COMMAND "UPDATE Pcap_Files SET totalpackets=$1, storedpackets=$2 \
                                WHERE id=$3;"
#define NUM_FILEEND_PARAMS 3
#define PREPARED_FILEEND_STMT "update-filename"
#define MAX_INTEGER_WIDTH 30
#define MAX_TIMESTAMP_WIDTH 60

#define CHECK_SECTIONPTR(need) if ((sectionptr - packet + need - 1) > decoded.captured_length) { \
                               if (verbose) { \
                                    fprintf(stdout, \
                                    "Warning: ignoring packet #%i because malformed (sectionptr moves out of the packet, captured length is %i bytes)\n", \
											packetnum, decoded.captured_length);				\
                               } \
                               goto next_packet; \
            				}

#ifdef PICKY_WITH_ALIGNMENT
uint16_t
unaligned_uint16(const uint8_t * p)
{
    /* This assumes big-endian values in data stream (which is standard for TCP/IP,
     * it is the "network byte order"). Stolen from
     * <http://stackoverflow.com/questions/529327/safe-efficient-way-to-access-unaligned-data-in-a-network-packet-from-c/529491#529491> 
     */
    unsigned char  *pByte = (unsigned char *) p;
    uint16_t        val = (pByte[0] << 8) | pByte[1];
    return val;
}
#endif

/* Defaults */
static bool     verbose = false;
static unsigned short debug = 0;
static unsigned long maxpackets = 0;

int
main(int argc, char *argv[])
{
    /* Misc. variables */
    char           *filename, errbuf[PCAP_ERRBUF_SIZE];
    char            fqdn[MAX_NAME + 1];
    char            edns_msg[64];
    char            ch;
    char           *backup;
    struct dns_packet decoded;

    /* pcap-related variables */
    pcap_t         *handle;
    int             datalink;
    const uint8_t  *packet;     /* The actual packet */
    struct pcap_pkthdr header;  /* The header that pcap gives us */
    const struct sniff_ethernet *ethernet;      /* The ethernet header */
    const struct sniff_ipv4 *ipv4;      /* The IP header */
    const struct sniff_ipv6 *ipv6;
    const struct sniff_udp *udp;        /* The UDP header */
    const struct sniff_dns *dns;
    u_int           size_ip;
    unsigned short  ip_version;
    const uint8_t  *qsection;
    uint8_t         labelsize;
    uint16_t        add_type;
    uint16_t        edns_size;
    bool            edns0;
    const uint8_t  *sectionptr;
    const uint8_t  *where_am_i; /* Cursor in packet */
    bool            end_of_name;
    u_short         source_port, dest_port;
    char           *source, *destination;
    unsigned int    packetnum = 0;
    unsigned int    packettotal = 0;
    unsigned int    size_header;
    bool            end_of_headers, fragmented;
    uint8_t         next_v6_header;
    const struct sniff_eh *eh;  /* The IPv6 extension header, if present */
    const struct sniff_frag *frag;

    /* PostgreSQL-related variables */
    PGconn         *conn = NULL;
    ConnStatusType  status;
    PGresult       *result;
    const char     *packet_params[NUM_PACKET_PARAMS];
    const char     *file_params[NUM_FILE_PARAMS];
    const char     *fileend_params[NUM_FILE_PARAMS];
    unsigned int    file_id;

    progname = argv[0];
    while ((ch = (char) getopt(argc, argv, "vd:m:")) != -1) {
        switch (ch) {
        case 'v':
            verbose = true;
            break;
        case 'd':
            debug = atoi(optarg);
            if (debug <= 0) {
                fatal("illegal debug value");
            }
            break;
        case 'm':
            maxpackets = (unsigned long) atoi(optarg);
            if (maxpackets == 0) {
                fatal("illegal max. packets value");
            }
            break;
        default:
            usage();
        }
    }
    argc -= optind;
    argv += optind;
    if (argc < 1) {
        usage();
    }
    filename = argv[0];
    handle = pcap_open_offline(filename, errbuf);
    if (handle == NULL) {
        fatal("Couldn't open file %s: %s\n", filename, errbuf);
    }
    datalink = pcap_datalink(handle);
    if (verbose) {
        fprintf(stdout,
                "Analyzing %s, version %i.%i, type %s, max packet size %u bytes...\n",
                filename, pcap_major_version(handle), pcap_minor_version(handle),
                pcap_datalink_val_to_description(datalink), pcap_snapshot(handle));
    }
    conn = PQconnectdb("dbname=essais");
    if (conn == NULL) {
        fatal("Cannot connect to the database (unknown reason)");
    }
    status = PQstatus(conn);
    if (status != CONNECTION_OK) {
        fatal(PQerrorMessage(conn));
    }
    /* We find lot of funny characters in domain names, not always UTF-8. Setting
     * the client encoding to Latin-1 is arbitrary, but it is to be sure the program 
     * * * * * * * * * won't crash (because any string is valid Latin-&, unlike
     * UTF-8). */
    result = PQexec(conn, "SET CLIENT_ENCODING TO 'LATIN-1';");
    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
        fatal("Cannot set encoding");
    }
    result = PQexec(conn, "BEGIN;");
    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
        fatal("Cannot start transaction");
    }
    result = PQprepare(conn, PREPARED_FILE_STMT, SQL_FILE_COMMAND, 1, NULL);
    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
        fatal("Cannot prepare statement: %s", PQresultErrorMessage(result));
    }
    file_params[0] = filename;
    /* TODO: add the datalink type and the snapshot length */
    result =
        PQexecPrepared(conn, PREPARED_FILE_STMT, NUM_FILE_PARAMS, file_params, NULL,
                       NULL, 0);
    if (PQresultStatus(result) == PGRES_TUPLES_OK) {
        file_id = (unsigned int) atoi(PQgetvalue(result, 0, 0));
        if (file_id == 0) {
            fatal("Cannot retrieve file_id after inserting file name %s", filename);
        }
    } else {
        fatal("Result for '%s' with \"%s\" is %s", SQL_FILE_COMMAND, file_params[0],
              PQresultErrorMessage(result));
    }
    result = PQprepare(conn, PREPARED_PACKET_STMT, SQL_PACKET_COMMAND, 1, NULL);
    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
        fatal("Cannot prepare statement: %s", PQresultErrorMessage(result));
    }
    result = PQprepare(conn, PREPARED_FILEEND_STMT, SQL_FILEEND_COMMAND, 1, NULL);
    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
        fatal("Cannot prepare statement: %s", PQresultErrorMessage(result));
    }
    packet_params[0] = malloc(MAX_INTEGER_WIDTH);
    packet_params[1] = malloc(MAX_INTEGER_WIDTH);
    packet_params[2] = malloc(MAX_TIMESTAMP_WIDTH);
    packet_params[3] = malloc(MAX_INTEGER_WIDTH);
    packet_params[4] = malloc(INET6_ADDRSTRLEN);
    packet_params[5] = malloc(INET6_ADDRSTRLEN);
    /* packet_params[6] = malloc(MAX_PROTO_WIDTH); Static value */
    packet_params[7] = malloc(MAX_INTEGER_WIDTH);
    packet_params[8] = malloc(MAX_INTEGER_WIDTH);
    /* packet_params[9] = malloc(MAX_BOOL_WIDTH); Static value */
    packet_params[10] = malloc(MAX_INTEGER_WIDTH);
    packet_params[11] = malloc(MAX_INTEGER_WIDTH);
    packet_params[12] = malloc(MAX_INTEGER_WIDTH);
    /* packet_params[13] = malloc(MAX_BOOL_WIDTH); Static value */
    /* packet_params[14] = malloc(MAX_BOOL_WIDTH); Static value */
    /* packet_params[15] = malloc(MAX_BOOL_WIDTH); Static value */
    /* packet_params[16] = malloc(MAX_BOOL_WIDTH); Static value */
/* packet_params[17] = malloc(MAX_NAME); Allocated via the fqdn variable */
    packet_params[18] = malloc(MAX_INTEGER_WIDTH);
    packet_params[19] = malloc(MAX_INTEGER_WIDTH);
/* packet_params[20] = malloc(MAX_BOOL_WIDTH); Static value */
    packet_params[21] = malloc(MAX_INTEGER_WIDTH);
    packet_params[22] = malloc(MAX_INTEGER_WIDTH);
    packet_params[23] = malloc(MAX_INTEGER_WIDTH);
    sprintf((char *) packet_params[0], "%i", (int) file_id);
    for (;;) {
        /* Grab a packet */
        decoded.rank = packetnum;
        packet = (uint8_t *) pcap_next(handle, &header);
        if (packet == NULL) {   /* End of file */
            break;
        }
        decoded.length = header.len;
        decoded.captured_length = header.caplen;
        decoded.date = header.ts;
        /* TODO: read the datalink type to skip ethernet header if the capture is
         * raw */
        ethernet = (struct sniff_ethernet *) (packet);
        if (ntohs(ethernet->ether_type) == IPv6_ETHERTYPE) {
            ip_version = 6;
            ipv6 = (struct sniff_ipv6 *) (packet + SIZE_ETHERNET);
            size_ip = SIZE_IPv6;
            assert(IPV6_VERSION(ipv6) == 6);
            next_v6_header = ipv6->ip_nxt;
            size_header = 0;
            where_am_i = where_am_i + SIZE_IPv6;
            end_of_headers = false;
            fragmented = false;
            while (!end_of_headers) {
                /* Extension headers defined in RFC 2460, section 4 */
                if (next_v6_header == 0 ||
                    next_v6_header == 43 || next_v6_header == 50
                    || next_v6_header == 51 || next_v6_header == 60) {
                    eh = (struct sniff_eh *) (where_am_i);
                    next_v6_header = eh->eh_next;
                    size_header = eh->eh_length;
                }
                /* Fragment */
                else if (next_v6_header == 44) {
                    fragmented = 1;
                    frag = (struct sniff_frag *) (where_am_i);
                    next_v6_header = frag->frag_next;
                    size_header = SIZE_FRAGMENT_HDR;
                } else {
                    end_of_headers = true;
                }
                where_am_i = where_am_i + size_header;
                size_ip += size_header;
            }
            if (fragmented && FRAG_OFFSET(frag) == 0) {
                goto next_packet;
            }
        } else if (ntohs(ethernet->ether_type) == IPv4_ETHERTYPE) {
            ip_version = 4;
            ipv4 = (struct sniff_ipv4 *) (packet + SIZE_ETHERNET);
            size_ip = IP_HL(ipv4) * 4;
            assert(IPV4_VERSION(ipv4) == 4);
        } else {                /* Ignore other Ethernet types */
            continue;
        }
        if ((ip_version == 6 && next_v6_header == UDP)
            || (ip_version == 4 && ipv4->ip_p == UDP)) {
            if (ip_version == 6) {
                source = malloc(INET6_ADDRSTRLEN);
                destination = malloc(INET6_ADDRSTRLEN);
                inet_ntop(AF_INET6, &ipv6->ip_src, source, INET6_ADDRSTRLEN);
                inet_ntop(AF_INET6, &ipv6->ip_dst, destination, INET6_ADDRSTRLEN);
            } else if (ip_version == 4) {
                source = malloc(INET_ADDRSTRLEN);
                destination = malloc(INET_ADDRSTRLEN);
                inet_ntop(AF_INET, &ipv4->ip_src, source, INET_ADDRSTRLEN);
                inet_ntop(AF_INET, &ipv4->ip_dst, destination, INET_ADDRSTRLEN);
            }
            udp = (struct sniff_udp *) (packet + SIZE_ETHERNET + size_ip);
            source_port = (u_short) ntohs(udp->sport);
            dest_port = (u_short) ntohs(udp->dport);
            decoded.src = source;
            decoded.dst = destination;
            decoded.src_port = source_port;
            decoded.dst_port = dest_port;
            if (source_port == DNS_PORT || dest_port == DNS_PORT) {
                if (maxpackets > 0 && packetnum >= maxpackets) {
                    break;
                }
                dns =
                    (struct sniff_dns
                     *) (packet + SIZE_ETHERNET + size_ip + SIZE_UDP);
                decoded.query = DNS_QR(dns) == 0 ? true : false;
                decoded.query_id = dns->query_id;
                decoded.opcode = DNS_OPCODE(dns);
                decoded.returncode = DNS_RCODE(dns);
                decoded.aa = DNS_AA(dns) ? true : false;
                decoded.tc = DNS_TC(dns) ? true : false;
                decoded.rd = DNS_RD(dns) ? true : false;
                decoded.ra = DNS_RA(dns) ? true : false;
                decoded.ancount = dns->ancount;
                decoded.nscount = dns->nscount;
                decoded.arcount = dns->arcount;
                qsection = (uint8_t *) (packet +
                                        SIZE_ETHERNET +
                                        size_ip + SIZE_UDP + SIZE_DNS);
                fqdn[0] = '\0';
                end_of_name = false;
                for (sectionptr = qsection; !end_of_name;) {
                    labelsize = (uint8_t) * sectionptr;
                    if (labelsize == 0) {
                        sectionptr++;
                        end_of_name = true;
                    } else if (labelsize > 63) {
                        /* It can be an error/attack or it can be compression (RFC
                         * 1035, section 4.1.4). Today, we ignore packets with
                         * compression (we just parse the question section, anyway). 
                         * * * * * * * * * TODO */
                        if (verbose) {
                            fprintf(stdout,
                                    "Warning: ignoring packet #%i because labelsize > 63\n",
                                    packetnum);
                        }
                        goto next_packet;
                    } else {
                        if (strlen(fqdn) == 0) {
                            strncpy(fqdn, (char *)
                                    sectionptr + 1, labelsize);
                            fqdn[labelsize] = '\0';
                        } else {
                            strncat(fqdn, ".", 1);
                            strncat(fqdn, (char *)
                                    sectionptr + 1, labelsize);
                        }
                        sectionptr = sectionptr + labelsize + 1;
                        CHECK_SECTIONPTR(0);
                    }
                }
                CHECK_SECTIONPTR(2);
                decoded.qname = fqdn;
#ifdef PICKY_WITH_ALIGNMENT
                decoded.qtype = unaligned_uint16(sectionptr);
#else
                decoded.qtype = ntohs(*((uint16_t *) sectionptr));
#endif
                sectionptr += 2;
                CHECK_SECTIONPTR(2);
#ifdef PICKY_WITH_ALIGNMENT
                decoded.qclass = unaligned_uint16(sectionptr);
#else
                decoded.qclass = ntohs(*((uint16_t *) sectionptr));
#endif
                sectionptr += 2;
                if (DNS_QR(dns) == 0) {
                    edns0 = false;
                    edns_size = 0;
                    if (dns->ancount == 0 && dns->nscount == 0) {
                        /* Probably by far the most common case in queries... */
                        if (dns->arcount != 0) {        /* There is an additional
                                                         * section. Probably the OPT 
                                                         * * * * * * * * * * * * *
                                                         * of EDNS */
                            CHECK_SECTIONPTR(1);
                            labelsize = (uint8_t) * sectionptr;
                            if (labelsize == 0) {       /* Yes, EDNS0 */
                                sectionptr += 1;
                                CHECK_SECTIONPTR(2);
#ifdef PICKY_WITH_ALIGNMENT
                                add_type = unaligned_uint16(sectionptr);
#else
                                add_type = ntohs(*((uint16_t *) sectionptr));
#endif
                                sectionptr += 2;
                                CHECK_SECTIONPTR(2);
                                if (add_type == OPT) {
#ifdef PICKY_WITH_ALIGNMENT
                                    edns_size = unaligned_uint16(sectionptr);
#else
                                    edns_size = ntohs(*((uint16_t *) sectionptr));
#endif
                                    edns0 = true;
                                    sprintf(edns_msg, ", EDNS0 (%i)", edns_size);
                                }
                                sectionptr += 2;
                            }
                        }
                    }
                }
                if (edns0) {
                    decoded.edns0 = true;
                    decoded.edns0_size = edns_size;
                } else {
                    decoded.edns0 = false;
                }
                sprintf((char *) packet_params[1], "%i", decoded.rank);
                strftime((char *) packet_params[2], MAX_TIMESTAMP_WIDTH,
                         "%Y-%m-%d:%H:%M:%SZ",
                         gmtime((const time_t *) &decoded.date.tv_sec));
                sprintf((char *) packet_params[3], "%i", decoded.length);
                packet_params[4] = decoded.src;
                packet_params[5] = decoded.dst;;
                packet_params[6] = "UDP";       /* TODO: add TCP support one day... */
                sprintf((char *) packet_params[7], "%i", decoded.src_port);
                sprintf((char *) packet_params[8], "%i", decoded.dst_port);
                packet_params[9] = decoded.query ? "true" : "false";
                sprintf((char *) packet_params[10], "%i", decoded.query_id);
                sprintf((char *) packet_params[11], "%i", decoded.opcode);
                sprintf((char *) packet_params[12], "%i", decoded.returncode);
                packet_params[13] = decoded.aa ? "true" : "false";
                packet_params[14] = decoded.tc ? "true" : "false";
                packet_params[15] = decoded.rd ? "true" : "false";
                packet_params[16] = decoded.ra ? "true" : "false";
                packet_params[17] = decoded.qname;
                sprintf((char *) packet_params[18], "%i", decoded.qtype);
                if (decoded.edns0) {
                    sprintf((char *) packet_params[19], "%i", decoded.edns0_size);
                } else {
                    backup = (char *) packet_params[19];
                    packet_params[19] = NULL;
                }
                packet_params[20] = decoded.do_dnssec ? "true" : "false";
                sprintf((char *) packet_params[21], "%i", decoded.ancount);
                sprintf((char *) packet_params[22], "%i", decoded.nscount);
                sprintf((char *) packet_params[23], "%i", decoded.arcount);
                result =
                    PQexecPrepared(conn, PREPARED_PACKET_STMT, NUM_PACKET_PARAMS,
                                   packet_params, NULL, NULL, 0);
                if (PQresultStatus(result) == PGRES_COMMAND_OK) {
                    /* OK */
                } else {
                    fatal("Result for '%s' with \"%s\" is %s", SQL_PACKET_COMMAND,
                          packet_params[0], PQresultErrorMessage(result));
                }
                if (!decoded.edns0) {
                    packet_params[19] = backup;
                }
              next_packet:
                packetnum++;
            }
            free(source);
            free(destination);
        }
        packettotal++;
    }
    if (verbose) {
        fprintf(stdout, "Done, %i DNS packets stored (among %i packets read%s)\n",
                packetnum, packettotal, (maxpackets > 0
                                         && packetnum >=
                                         maxpackets) ?
                " - interrupted before the end because max packets read" : "");
    }
    fileend_params[0] = malloc(MAX_INTEGER_WIDTH);
    fileend_params[1] = malloc(MAX_INTEGER_WIDTH);
    fileend_params[2] = malloc(MAX_INTEGER_WIDTH);
    sprintf((char *) fileend_params[0], "%i", packettotal);
    sprintf((char *) fileend_params[1], "%i", packetnum);
    sprintf((char *) fileend_params[2], "%i", file_id);
    result =
        PQexecPrepared(conn, PREPARED_FILEEND_STMT, NUM_FILEEND_PARAMS,
                       fileend_params, NULL, NULL, 0);
    if (PQresultStatus(result) == PGRES_COMMAND_OK) {
        /* OK */
    } else {
        fatal("Result for '%s' with \"%s\" is %s", SQL_FILEEND_COMMAND,
              fileend_params[2], PQresultErrorMessage(result));
    }
    /* And close the session */
    pcap_close(handle);
    result = PQexec(conn, "COMMIT;");
    if (PQresultStatus(result) != PGRES_COMMAND_OK) {
        fatal("Cannot commit transaction");
    }
    PQfinish(conn);
    return (0);
}

