]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
DNS TCP and UDP parser and DNS response logger
authorVictor Julien <victor@inliniac.net>
Fri, 22 Feb 2013 17:17:49 +0000 (18:17 +0100)
committerVictor Julien <victor@inliniac.net>
Thu, 27 Jun 2013 11:43:06 +0000 (13:43 +0200)
18 files changed:
src/Makefile.am
src/app-layer-dns-common.c [new file with mode: 0644]
src/app-layer-dns-common.h [new file with mode: 0644]
src/app-layer-dns-tcp.c [new file with mode: 0644]
src/app-layer-dns-tcp.h [new file with mode: 0644]
src/app-layer-dns-udp.c [new file with mode: 0644]
src/app-layer-dns-udp.h [new file with mode: 0644]
src/app-layer-parser.c
src/app-layer-protos.c
src/app-layer-protos.h
src/log-dnslog.c [new file with mode: 0644]
src/log-dnslog.h [new file with mode: 0644]
src/suricata.c
src/tm-modules.c
src/tm-threads-common.h
src/util-error.c
src/util-error.h
suricata.yaml.in

index 90d582c904a4b13fd8461cc33f6b5d258b439fea..4f393911822f7418e76b69abb46230131d23bf57 100644 (file)
@@ -17,6 +17,9 @@ app-layer.c app-layer.h \
 app-layer-dcerpc.c app-layer-dcerpc.h \
 app-layer-dcerpc-udp.c app-layer-dcerpc-udp.h \
 app-layer-detect-proto.c app-layer-detect-proto.h \
+app-layer-dns-common.c app-layer-dns-common.h \
+app-layer-dns-tcp.c app-layer-dns-tcp.h \
+app-layer-dns-udp.c app-layer-dns-udp.h \
 app-layer-ftp.c app-layer-ftp.h \
 app-layer-htp-body.c app-layer-htp-body.h \
 app-layer-htp.c app-layer-htp.h \
@@ -193,6 +196,7 @@ flow-var.c flow-var.h \
 host.c host.h \
 host-queue.c host-queue.h \
 host-timeout.c host-timeout.h \
+log-dnslog.c log-dnslog.h \
 log-droplog.c log-droplog.h \
 log-file.c log-file.h \
 log-filestore.c log-filestore.h \
diff --git a/src/app-layer-dns-common.c b/src/app-layer-dns-common.c
new file mode 100644 (file)
index 0000000..d1156c0
--- /dev/null
@@ -0,0 +1,641 @@
+/* Copyright (C) 2013 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#include "suricata-common.h"
+#include "app-layer-dns-common.h"
+#ifdef DEBUG
+#include "util-print.h"
+#endif
+
+SCEnumCharMap dns_decoder_event_table[ ] = {
+    { "UNSOLLICITED_RESPONSE",      DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE, },
+    { "MALFORMED_DATA",             DNS_DECODER_EVENT_MALFORMED_DATA, },
+    { "NOT_A_REQUEST",              DNS_DECODER_EVENT_NOT_A_REQUEST, },
+    { "NOT_A_RESPONSE",             DNS_DECODER_EVENT_NOT_A_RESPONSE, },
+    { "Z_FLAG_SET",                 DNS_DECODER_EVENT_Z_FLAG_SET, },
+
+    { NULL,                         -1 },
+};
+
+/** \brief register event map */
+void DNSAppLayerDecoderEventsRegister(int alproto) {
+    AppLayerDecoderEventsModuleRegister(alproto, dns_decoder_event_table);
+}
+
+void *DNSStateAlloc(void) {
+    void *s = SCMalloc(sizeof(DNSState));
+    if (unlikely(s == NULL))
+        return NULL;
+
+    memset(s, 0, sizeof(DNSState));
+
+    DNSState *dns_state = (DNSState *)s;
+
+    TAILQ_INIT(&dns_state->tx_list);
+    return s;
+}
+
+void DNSStateFree(void *s) {
+    if (s) {
+        DNSState *dns_state = (DNSState *) s;
+
+        DNSTransaction *tx = NULL;
+        while ((tx = TAILQ_FIRST(&dns_state->tx_list))) {
+            TAILQ_REMOVE(&dns_state->tx_list, tx, next);
+            DNSTransactionFree(tx);
+        }
+
+        if (dns_state->buffer != NULL)
+            SCFree(dns_state->buffer);
+
+        SCFree(s);
+        s = NULL;
+    }
+}
+
+void *DNSGetTx(void *alstate, uint64_t tx_id) {
+    /* todo */
+    return NULL;
+}
+
+uint64_t DNSGetTxCnt(void *alstate) {
+    DNSState *dns_state = (DNSState *)alstate;
+    return (uint64_t)dns_state->transaction_cnt;
+}
+
+int DNSGetAlstateProgress(void *tx, uint8_t direction) {
+    DNSTransaction *dns_tx = (DNSTransaction *)tx;
+    return dns_tx->replied;
+}
+
+/* value for tx->replied value */
+int DNSGetAlstateProgressCompletionStatus(uint8_t direction) {
+    return (direction == 0) ? 0 : 1;
+}
+
+/** \internal
+ *  \brief Allocate a DNS TX
+ *  \retval tx or NULL */
+DNSTransaction *DNSTransactionAlloc(const uint16_t tx_id) {
+    DNSTransaction *tx = SCMalloc(sizeof(DNSTransaction));
+    if (tx == NULL)
+        return NULL;
+    memset(tx, 0x00, sizeof(DNSTransaction));
+
+    TAILQ_INIT(&tx->query_list);
+    TAILQ_INIT(&tx->answer_list);
+    TAILQ_INIT(&tx->authority_list);
+
+    tx->tx_id = tx_id;
+    return tx;
+}
+
+/** \internal
+ *  \brief Free a DNS TX
+ *  \param tx DNS TX to free */
+void DNSTransactionFree(DNSTransaction *tx) {
+    DNSQueryEntry *q = NULL;
+    while ((q = TAILQ_FIRST(&tx->query_list))) {
+        TAILQ_REMOVE(&tx->query_list, q, next);
+        SCFree(q);
+    }
+
+    DNSAnswerEntry *a = NULL;
+    while ((a = TAILQ_FIRST(&tx->answer_list))) {
+        TAILQ_REMOVE(&tx->answer_list, a, next);
+        SCFree(a);
+    }
+    while ((a = TAILQ_FIRST(&tx->authority_list))) {
+        TAILQ_REMOVE(&tx->authority_list, a, next);
+        SCFree(a);
+    }
+    SCFree(tx);
+}
+
+/** \internal
+ *  \brief Find the DNS Tx in the state
+ *  \param tx_id id of the tx
+ *  \retval tx or NULL if not found */
+DNSTransaction *DNSTransactionFindByTxId(const DNSState *dns_state, const uint16_t tx_id) {
+    if (dns_state->curr == NULL)
+        return NULL;
+
+    /* fast path */
+    if (dns_state->curr->tx_id == tx_id) {
+        return dns_state->curr;
+
+    /* slow path, iterate list */
+    } else {
+        DNSTransaction *tx = NULL;
+        TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
+            if (tx->tx_id == tx_id) {
+                return tx;
+            }
+        }
+    }
+    /* not found */
+    return NULL;
+}
+
+/** \brief Validation checks for DNS request header
+ *
+ *  Will set decoder events if anomalies are found.
+ *
+ *  \retval 0 ok
+ *  \retval -1 error
+ */
+int DNSValidateRequestHeader(Flow *f, const DNSHeader *dns_header) {
+    uint16_t flags = ntohs(dns_header->flags);
+
+    if ((flags & 0x8000) != 0) {
+        SCLogDebug("not a request 0x%04x", flags);
+        if (f != NULL)
+            AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_NOT_A_REQUEST);
+        goto bad_data;
+    }
+
+    if ((flags & 0x0040) != 0) {
+        SCLogDebug("Z flag not 0, 0x%04x", flags);
+        if (f != NULL)
+            AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_Z_FLAG_SET);
+        goto bad_data;
+    }
+
+    return 0;
+bad_data:
+    return -1;
+}
+
+/** \brief Validation checks for DNS response header
+ *
+ *  Will set decoder events if anomalies are found.
+ *
+ *  \retval 0 ok
+ *  \retval -1 error
+ */
+int DNSValidateResponseHeader(Flow *f, const DNSHeader *dns_header) {
+    uint16_t flags = ntohs(dns_header->flags);
+
+    if ((flags & 0x8000) == 0) {
+        SCLogDebug("not a response 0x%04x", flags);
+        AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_NOT_A_RESPONSE);
+        goto bad_data;
+    }
+
+    if ((flags & 0x0040) != 0) {
+        SCLogDebug("Z flag not 0, 0x%04x", flags);
+        AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_Z_FLAG_SET);
+        goto bad_data;
+    }
+
+    return 0;
+bad_data:
+    return -1;
+}
+
+void DNSStoreQueryInState(DNSState *dns_state, const uint8_t *fqdn, const uint16_t fqdn_len,
+        const uint16_t type, const uint16_t class, const uint16_t tx_id)
+{
+    DNSTransaction *tx = DNSTransactionFindByTxId(dns_state, tx_id);
+    if (tx == NULL) {
+        tx = DNSTransactionAlloc(tx_id);
+        if (tx == NULL)
+            return;
+        TAILQ_INSERT_TAIL(&dns_state->tx_list, tx, next);
+        dns_state->curr = tx;
+    }
+
+    DNSQueryEntry *q = SCMalloc(sizeof(DNSQueryEntry) + fqdn_len);
+    if (q == NULL)
+        return;
+    q->type = type;
+    q->class = class;
+    q->len = fqdn_len;
+    memcpy((uint8_t *)q + sizeof(DNSQueryEntry), fqdn, fqdn_len);
+
+    TAILQ_INSERT_TAIL(&tx->query_list, q, next);
+
+    SCLogDebug("Query for TX %04x stored", tx_id);
+}
+
+void DNSStoreAnswerInState(DNSState *dns_state, const int rtype, const uint8_t *fqdn,
+        const uint16_t fqdn_len, const uint16_t type, const uint16_t class, const uint16_t ttl,
+        const uint8_t *data, const uint16_t data_len, const uint16_t tx_id)
+{
+    DNSTransaction *tx = DNSTransactionFindByTxId(dns_state, tx_id);
+    if (tx == NULL) {
+        tx = DNSTransactionAlloc(tx_id);
+        if (tx == NULL)
+            return;
+        TAILQ_INSERT_TAIL(&dns_state->tx_list, tx, next);
+        dns_state->curr = tx;
+    }
+
+    dns_state->transaction_cnt++;
+    SCLogDebug("dns_state->transaction_cnt %u", dns_state->transaction_cnt);
+
+    DNSAnswerEntry *q = SCMalloc(sizeof(DNSAnswerEntry) + fqdn_len + data_len);
+    if (q == NULL)
+        return;
+    q->type = type;
+    q->class = class;
+    q->ttl = ttl;
+    q->fqdn_len = fqdn_len;
+    q->data_len = data_len;
+
+    uint8_t *ptr = (uint8_t *)q + sizeof(DNSAnswerEntry);
+    memcpy(ptr, fqdn, fqdn_len);
+    ptr += fqdn_len;
+    memcpy(ptr, data, data_len);
+
+    if (rtype == DNS_LIST_ANSWER)
+        TAILQ_INSERT_TAIL(&tx->answer_list, q, next);
+    else if (rtype == DNS_LIST_AUTHORITY)
+        TAILQ_INSERT_TAIL(&tx->authority_list, q, next);
+    else
+        BUG_ON(1);
+
+    SCLogDebug("Answer for TX %04x stored", tx_id);
+
+    /* mark tx is as replied so we can log it */
+    tx->replied = 1;
+}
+
+/** \internal
+ *  \brief get domain name from dns packet
+ *
+ *  In case of compressed name storage this function follows the ptrs to
+ *  create the full domain name.
+ *
+ *  The length bytes are converted into dots, e.g. |03|com|00| becomes
+ *  .com
+ *  The trailing . is not stored.
+ *
+ *  \param input input buffer (complete dns record)
+ *  \param input_len lenght of input buffer
+ *  \param offset offset into @input where dns name starts
+ *  \param fqdn buffer to store result
+ *  \param fqdn_size size of @fqdn buffer
+ *  \retval 0 on error/no buffer
+ *  \retval size size of fqdn
+ */
+static uint16_t DNSResponseGetNameByOffset(const uint8_t * const input, const uint32_t input_len,
+        const uint16_t offset, uint8_t *fqdn, const size_t fqdn_size)
+{
+    if (input + input_len < input + offset + 1) {
+        SCLogInfo("input buffer too small for domain of len %u", offset);
+        goto insufficient_data;
+    }
+
+    uint16_t fqdn_offset = 0;
+    uint8_t length = *(input + offset);
+    const uint8_t *qdata = input + offset;
+    SCLogDebug("qry length %u", length);
+
+    if (length == 0) {
+        memcpy(fqdn, "<root>", fqdn_size);
+        SCReturnUInt(6U);
+    }
+
+    while (length != 0) {
+        if (length & 0xc0) {
+            uint16_t offset = ((length & 0x3f) << 8) + *(qdata+1);
+            qdata = (const uint8_t *)input + offset;
+
+            if (input + input_len < qdata + 1) {
+                SCLogInfo("input buffer too small");
+                goto insufficient_data;
+            }
+
+            length = *qdata;
+            SCLogDebug("qry length %u", length);
+        }
+        qdata++;
+
+        if (length > 0) {
+            if (input + input_len < qdata + length) {
+                SCLogInfo("input buffer too small for domain of len %u", length);
+                goto insufficient_data;
+            }
+            //PrintRawDataFp(stdout, qdata, length);
+
+            if ((size_t)(fqdn_offset + length + 1) < fqdn_size) {
+                memcpy(fqdn + fqdn_offset, qdata, length);
+                fqdn_offset += length;
+                fqdn[fqdn_offset++] = '.';
+            }
+        }
+        qdata += length;
+
+        if (input + input_len < qdata + 1) {
+            SCLogInfo("input buffer too small for len field");
+            goto insufficient_data;
+        }
+
+        length = *qdata;
+        SCLogDebug("qry length %u", length);
+    }
+    if (fqdn_offset) {
+        fqdn_offset--;
+    }
+    //PrintRawDataFp(stdout, fqdn, fqdn_offset);
+    SCReturnUInt(fqdn_offset);
+insufficient_data:
+    SCReturnUInt(0U);
+}
+
+/** \internal
+ *  \brief skip past domain name field
+ *
+ *  Skip the domain at position data. We don't care about following compressed names
+ *  as we only want to know when the next part of the buffer starts
+ *
+ *  \param input input buffer (complete dns record)
+ *  \param input_len lenght of input buffer
+ *  \param data current position
+ *
+ *  \retval NULL on out of bounds data
+ *  \retval sdata ptr to position in buffer past the name
+ */
+static const uint8_t *SkipDomain(const uint8_t * const input,
+        const uint32_t input_len, const uint8_t *data)
+{
+    const uint8_t *sdata = data;
+    while (*sdata != 0x00) {
+        if (*sdata & 0xc0) {
+            sdata++;
+            break;
+        } else {
+            sdata += ((*sdata) + 1);
+        }
+        if (input + input_len < sdata) {
+            SCLogInfo("input buffer too small for data of len");
+            goto insufficient_data;
+        }
+    }
+    sdata++;
+    if (input + input_len < sdata) {
+        SCLogInfo("input buffer too small for data of len");
+        goto insufficient_data;
+    }
+    return sdata;
+insufficient_data:
+    return NULL;
+}
+
+const uint8_t *DNSReponseParse(DNSState *dns_state, const DNSHeader * const dns_header,
+        const uint16_t num, const DnsListEnum list, const uint8_t * const input,
+        const uint32_t input_len, const uint8_t *data)
+{
+    if (input + input_len < data + 2) {
+        SCLogInfo("input buffer too small for record 'name' field, record %u, "
+                "total answer_rr %u", num, ntohs(dns_header->answer_rr));
+        goto insufficient_data;
+    }
+
+    uint8_t fqdn[DNS_MAX_SIZE];
+    uint16_t fqdn_len = 0;
+
+    /* see if name is compressed */
+    if (!(data[0] & 0xc0)) {
+        if ((fqdn_len = DNSResponseGetNameByOffset(input, input_len,
+                        data - input, fqdn, sizeof(fqdn))) == 0)
+        {
+#if DEBUG
+            PrintRawDataFp(stdout, (uint8_t *)input, input_len);
+            BUG_ON(1);
+#endif
+            goto insufficient_data;
+        }
+        //PrintRawDataFp(stdout, fqdn, fqdn_len);
+        const uint8_t *tdata = SkipDomain(input, input_len, data);
+        if (tdata == NULL) {
+            goto insufficient_data;
+        }
+        data = tdata;
+    } else {
+        uint16_t offset = (data[0] & 0x3f) << 8 | data[1];
+
+        if ((fqdn_len = DNSResponseGetNameByOffset(input, input_len,
+                        offset, fqdn, sizeof(fqdn))) == 0)
+        {
+#if DEBUG
+            PrintRawDataFp(stdout, (uint8_t *)input, input_len);
+            BUG_ON(1);
+#endif
+            goto insufficient_data;
+        }
+        //PrintRawDataFp(stdout, fqdn, fqdn_len);
+        data += 2;
+    }
+
+    if (input + input_len < data + sizeof(DNSAnswerHeader)) {
+        SCLogInfo("input buffer too small for DNSAnswerHeader");
+        goto insufficient_data;
+    }
+
+    const DNSAnswerHeader *head = (DNSAnswerHeader *)data;
+    switch (ntohs(head->type)) {
+        case DNS_RECORD_TYPE_A:
+        case DNS_RECORD_TYPE_AAAA:
+        case DNS_RECORD_TYPE_CNAME:
+        {
+            data += sizeof(DNSAnswerHeader);
+
+            SCLogDebug("head->len %u", ntohs(head->len));
+
+            if (input + input_len < data + ntohs(head->len)) {
+                SCLogInfo("input buffer too small for data of len %u", ntohs(head->len));
+                goto insufficient_data;
+            }
+            SCLogDebug("TTL %u", ntohl(head->ttl));
+
+            if (ntohs(head->type) == DNS_RECORD_TYPE_A && ntohs(head->len) == 4) {
+                //PrintRawDataFp(stdout, data, ntohs(head->len));
+                //char a[16];
+                //PrintInet(AF_INET, (const void *)data, a, sizeof(a));
+                //SCLogInfo("A %s TTL %u", a, ntohl(head->ttl));
+
+                DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
+                        ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+                        data, 4, ntohs(dns_header->tx_id));
+            } else if (ntohs(head->type) == DNS_RECORD_TYPE_AAAA && ntohs(head->len) == 16) {
+                //char a[46];
+                //PrintInet(AF_INET6, (const void *)data, a, sizeof(a));
+                //SCLogInfo("AAAA %s TTL %u", a, ntohl(head->ttl));
+
+                DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
+                        ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+                        data, 16, ntohs(dns_header->tx_id));
+            } else if (ntohs(head->type) == DNS_RECORD_TYPE_CNAME) {
+                uint8_t cname[DNS_MAX_SIZE];
+                uint16_t cname_len = 0;
+
+                if ((cname_len = DNSResponseGetNameByOffset(input, input_len,
+                                data - input, cname, sizeof(cname))) == 0)
+                {
+#if DEBUG
+                    PrintRawDataFp(stdout, (uint8_t *)input, input_len);
+                    BUG_ON(1);
+#endif
+                    goto insufficient_data;
+                }
+
+                DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
+                        ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+                        cname, cname_len, ntohs(dns_header->tx_id));
+            }
+
+            data += ntohs(head->len);
+            break;
+        }
+        case DNS_RECORD_TYPE_MX:
+        {
+            data += sizeof(DNSAnswerHeader);
+
+            SCLogDebug("head->len %u", ntohs(head->len));
+
+            if (input + input_len < data + ntohs(head->len)) {
+                SCLogInfo("input buffer too small for data of len %u", ntohs(head->len));
+                goto insufficient_data;
+            }
+
+            SCLogDebug("TTL %u", ntohl(head->ttl));
+
+            uint8_t mxname[DNS_MAX_SIZE];
+            uint16_t mxname_len = 0;
+
+            if ((mxname_len = DNSResponseGetNameByOffset(input, input_len,
+                            data - input + 2, mxname, sizeof(mxname))) == 0) {
+#if DEBUG
+                PrintRawDataFp(stdout, (uint8_t *)input, input_len);
+                BUG_ON(1);
+#endif
+                goto insufficient_data;
+            }
+
+            DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
+                    ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+                    mxname, mxname_len, ntohs(dns_header->tx_id));
+
+            data += ntohs(head->len);
+            break;
+        }
+        case DNS_RECORD_TYPE_NS:
+        case DNS_RECORD_TYPE_SOA:
+        {
+            data += sizeof(DNSAnswerHeader);
+
+            if (input + input_len < data + ntohs(head->len)) {
+                SCLogInfo("input buffer too small for data of len %u", ntohs(head->len));
+                goto insufficient_data;
+            }
+
+            SCLogDebug("TTL %u", ntohl(head->ttl));
+
+            uint8_t pname[DNS_MAX_SIZE];
+            uint16_t pname_len = 0;
+
+            if ((pname_len = DNSResponseGetNameByOffset(input, input_len,
+                            data - input, pname, sizeof(pname))) == 0)
+            {
+#if DEBUG
+                PrintRawDataFp(stdout, (uint8_t *)input, input_len);
+                BUG_ON(1);
+#endif
+                goto insufficient_data;
+            }
+
+            if (ntohs(head->type) == DNS_RECORD_TYPE_SOA) {
+                const uint8_t *sdata = SkipDomain(input, input_len, data);
+                if (sdata == NULL) {
+                    goto insufficient_data;
+                }
+
+                uint8_t pmail[DNS_MAX_SIZE];
+                uint16_t pmail_len = 0;
+
+                if ((pmail_len = DNSResponseGetNameByOffset(input, input_len,
+                                sdata - input, pmail, sizeof(pmail))) == 0)
+                {
+#if DEBUG
+                    PrintRawDataFp(stdout, (uint8_t *)input, input_len);
+                    BUG_ON(1);
+#endif
+                    goto insufficient_data;
+                }
+
+                const uint8_t *tdata = SkipDomain(input, input_len, sdata);
+                if (tdata == NULL) {
+                    goto insufficient_data;
+                }
+#if DEBUG
+                struct Trailer {
+                    uint32_t serial;
+                    uint32_t refresh;
+                    uint32_t retry;
+                    uint32_t experiation;
+                    uint32_t minttl;
+                } *tail = (struct Trailer *)tdata;
+
+                if (input + input_len < tdata + sizeof(struct Trailer)) {
+                    SCLogInfo("input buffer too small for data of len");
+                    goto insufficient_data;
+                }
+
+                SCLogDebug("serial %u refresh %u retry %u exp %u min ttl %u",
+                        ntohl(tail->serial), ntohl(tail->refresh),
+                        ntohl(tail->retry), ntohl(tail->experiation),
+                        ntohl(tail->minttl));
+#endif
+            }
+
+            DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
+                    ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+                    pname, pname_len, ntohs(dns_header->tx_id));
+
+            data += ntohs(head->len);
+            break;
+        }
+        default:    /* unsupported record */
+        {
+            data += sizeof(DNSAnswerHeader);
+
+            if (input + input_len < data + ntohs(head->len)) {
+                SCLogInfo("input buffer too small for data of len %u", ntohs(head->len));
+                goto insufficient_data;
+            }
+
+            DNSStoreAnswerInState(dns_state, list, NULL, 0,
+                    ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+                    NULL, 0, ntohs(dns_header->tx_id));
+
+            //PrintRawDataFp(stdout, data, ntohs(head->len));
+            data += ntohs(head->len);
+            break;
+        }
+    }
+    return data;
+insufficient_data:
+    return NULL;
+}
diff --git a/src/app-layer-dns-common.h b/src/app-layer-dns-common.h
new file mode 100644 (file)
index 0000000..595f0b2
--- /dev/null
@@ -0,0 +1,184 @@
+/* Copyright (C) 2013 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#ifndef __APP_LAYER_DNS_COMMON_H__
+#define __APP_LAYER_DNS_COMMON_H__
+
+#include "app-layer-protos.h"
+#include "app-layer-parser.h"
+#include "flow.h"
+#include "queue.h"
+#include "util-byte.h"
+
+#define DNS_MAX_SIZE 256
+
+#define DNS_RECORD_TYPE_A       0x0001
+#define DNS_RECORD_TYPE_NS      0x0002
+
+#define DNS_RECORD_TYPE_CNAME   0x0005
+#define DNS_RECORD_TYPE_SOA     0x0006
+
+#define DNS_RECORD_TYPE_TXT     0x0010
+
+#define DNS_RECORD_TYPE_PTR     0x000c
+#define DNS_RECORD_TYPE_MX      0x000f
+
+#define DNS_RECORD_TYPE_AAAA    0x001c
+
+#define DNS_RECORD_TYPE_ANY     0x00ff
+
+#define DNS_RECORD_TYPE_TKEY    0x00f9
+#define DNS_RECORD_TYPE_TSIG    0x00fa /**< XXX correct? */
+
+enum {
+    DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE,
+    DNS_DECODER_EVENT_MALFORMED_DATA,
+    DNS_DECODER_EVENT_NOT_A_REQUEST,
+    DNS_DECODER_EVENT_NOT_A_RESPONSE,
+    DNS_DECODER_EVENT_Z_FLAG_SET,
+};
+
+/** \brief DNS packet header */
+typedef struct DNSHeader_ {
+    uint16_t tx_id;
+    uint16_t flags;
+    uint16_t questions;
+    uint16_t answer_rr;
+    uint16_t authority_rr;
+    uint16_t additional_rr;
+} DNSHeader;
+
+typedef struct DNSQueryTrailer_ {
+    uint16_t type;
+    uint16_t class;
+} DNSQueryTrailer;
+
+/** \brief DNS answer header
+ *  packed as we don't want alignment to mess up sizeof() */
+struct DNSAnswerHeader_ {
+    uint16_t type;
+    uint16_t class;
+    uint32_t ttl;
+    uint16_t len;
+} __attribute__((__packed__));
+typedef struct DNSAnswerHeader_ DNSAnswerHeader;
+
+/** \brief List types in the TX.
+ *  Used when storing answers from "Answer" or "Authority" */
+typedef enum {
+    DNS_LIST_ANSWER = 0,
+    DNS_LIST_AUTHORITY,
+} DnsListEnum;
+
+/** \brief DNS Query storage. Stored in TX list.
+ *
+ *  Layout is:
+ *  [list ptr][2 byte type][2 byte class][2 byte len][...data...]
+ */
+typedef struct DNSQueryEntry_ {
+    TAILQ_ENTRY(DNSQueryEntry_) next;
+    uint16_t type;
+    uint16_t class;
+    uint16_t len;
+} DNSQueryEntry;
+
+/** \brief DNS Answer storage. Stored in TX list.
+ *
+ *  Layout is:
+ *  [list ptr][2 byte type][2 byte class][2 byte ttl] \
+ *      [2 byte fqdn len][2 byte data len][...fqdn...][...data...]
+ */
+typedef struct DNSAnswerEntry_ {
+    TAILQ_ENTRY(DNSAnswerEntry_) next;
+
+    uint16_t type;
+    uint16_t class;
+
+    uint32_t ttl;
+
+    uint16_t fqdn_len;
+    uint16_t data_len;
+} DNSAnswerEntry;
+
+/** \brief DNS Transaction, request/reply with same TX id. */
+typedef struct DNSTransaction_ {
+    uint16_t tx_id;                                 /**< transaction id */
+    uint16_t replied;                               /**< bool indicating request is
+                                                         replied to. */
+    uint16_t no_such_name;                          /**< server said "no such name" */
+
+    TAILQ_HEAD(, DNSQueryEntry_) query_list;        /**< list for query/queries */
+    TAILQ_HEAD(, DNSAnswerEntry_) answer_list;      /**< list for answers */
+    TAILQ_HEAD(, DNSAnswerEntry_) authority_list;   /**< list for authority records */
+
+    TAILQ_ENTRY(DNSTransaction_) next;
+} DNSTransaction;
+
+/** \brief Per flow DNS state container */
+typedef struct DNSState_ {
+    TAILQ_HEAD(, DNSTransaction_) tx_list;  /**< transaction list */
+    DNSTransaction *curr;                   /**< ptr to current tx */
+    uint16_t transaction_cnt;
+    uint16_t transaction_done;
+
+    /* used by TCP only */
+    uint16_t offset;
+    uint16_t record_len;
+    uint8_t *buffer;
+} DNSState;
+
+void RegisterDNSParsers(void);
+void DNSParserTests(void);
+void DNSParserRegisterTests(void);
+void DNSAppLayerDecoderEventsRegister(int alproto);
+
+void *DNSGetTx(void *alstate, uint64_t tx_id);
+uint64_t DNSGetTxCnt(void *alstate);
+int DNSGetAlstateProgress(void *tx, uint8_t direction);
+int DNSGetAlstateProgressCompletionStatus(uint8_t direction);
+
+DNSTransaction *DNSTransactionAlloc(const uint16_t tx_id);
+void DNSTransactionFree(DNSTransaction *tx);
+DNSTransaction *DNSTransactionFindByTxId(const DNSState *dns_state, const uint16_t tx_id);
+
+void *DNSStateAlloc(void);
+void DNSStateFree(void *s);
+
+int DNSValidateRequestHeader(Flow *f, const DNSHeader *dns_header);
+int DNSValidateResponseHeader(Flow *f, const DNSHeader *dns_header);
+
+void DNSStoreQueryInState(DNSState *dns_state, const uint8_t *fqdn, const uint16_t fqdn_len,
+        const uint16_t type, const uint16_t class, const uint16_t tx_id);
+
+void DNSStoreAnswerInState(DNSState *dns_state, const int rtype, const uint8_t *fqdn,
+        const uint16_t fqdn_len, const uint16_t type, const uint16_t class, const uint16_t ttl,
+        const uint8_t *data, const uint16_t data_len, const uint16_t tx_id);
+
+const uint8_t *DNSReponseParse(DNSState *dns_state, const DNSHeader * const dns_header,
+        const uint16_t num, const DnsListEnum list, const uint8_t * const input,
+        const uint32_t input_len, const uint8_t *data);
+
+uint16_t DNSUdpResponseGetNameByOffset(const uint8_t * const input, const uint32_t input_len,
+        const uint16_t offset, uint8_t *fqdn, const size_t fqdn_size);
+
+#endif /* __APP_LAYER_DNS_COMMON_H__ */
diff --git a/src/app-layer-dns-tcp.c b/src/app-layer-dns-tcp.c
new file mode 100644 (file)
index 0000000..82bbca1
--- /dev/null
@@ -0,0 +1,652 @@
+/* Copyright (C) 2013 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#include "suricata-common.h"
+#include "suricata.h"
+
+#include "debug.h"
+#include "decode.h"
+
+#include "flow-util.h"
+
+#include "threads.h"
+
+#include "util-print.h"
+#include "util-pool.h"
+#include "util-debug.h"
+
+#include "stream-tcp-private.h"
+#include "stream-tcp-reassemble.h"
+#include "stream-tcp.h"
+#include "stream.h"
+
+#include "app-layer-protos.h"
+#include "app-layer-parser.h"
+
+#include "util-spm.h"
+#include "util-unittest.h"
+
+#include "app-layer-dns-tcp.h"
+
+struct DNSTcpHeader_ {
+    uint16_t len;
+    uint16_t tx_id;
+    uint16_t flags;
+    uint16_t questions;
+    uint16_t answer_rr;
+    uint16_t authority_rr;
+    uint16_t additional_rr;
+} __attribute__((__packed__));
+typedef struct DNSTcpHeader_ DNSTcpHeader;
+
+/** \internal
+ *  \param input_len at least enough for the DNSTcpHeader
+ */
+static int DNSTCPRequestParseProbe(uint8_t *input, uint32_t input_len)
+{
+#ifdef DEBUG
+    BUG_ON(input_len < sizeof(DNSTcpHeader));
+#endif
+    SCLogDebug("starting %u", input_len);
+
+    DNSTcpHeader *dns_tcp_header = (DNSTcpHeader *)input;
+    if (ntohs(dns_tcp_header->len) < sizeof(DNSHeader)) {
+        goto bad_data;
+    }
+    if (ntohs(dns_tcp_header->len) >= input_len) {
+        goto insufficient_data;
+    }
+
+    input += 2;
+    input_len -= 2;
+    DNSHeader *dns_header = (DNSHeader *)input;
+
+    uint16_t q;
+    const uint8_t *data = input + sizeof(DNSHeader);
+
+    for (q = 0; q < ntohs(dns_header->questions); q++) {
+        uint16_t fqdn_offset = 0;
+
+        if (input + input_len < data + 1) {
+            SCLogDebug("input buffer too small for len field");
+            goto insufficient_data;
+        }
+        SCLogDebug("query length %u", *data);
+
+        while (*data != 0) {
+            if (*data > 63) {
+                /** \todo set event?*/
+                goto bad_data;
+            }
+            uint8_t length = *data;
+
+            data++;
+
+            if (length > 0) {
+                if (input + input_len < data + length) {
+                    SCLogDebug("input buffer too small for domain of len %u", length);
+                    goto insufficient_data;
+                }
+                //PrintRawDataFp(stdout, data, qry->length);
+
+                if ((fqdn_offset + length + 1) < DNS_MAX_SIZE) {
+                    fqdn_offset += length;
+                } else {
+                    /** \todo set event? */
+                    goto bad_data;
+                }
+            }
+
+            data += length;
+
+            if (input + input_len < data + 1) {
+                SCLogDebug("input buffer too small for new len");
+                goto insufficient_data;
+            }
+
+            SCLogDebug("qry length %u", *data);
+        }
+        if (fqdn_offset) {
+            fqdn_offset--;
+        }
+
+        data++;
+        if (input + input_len < data + sizeof(DNSQueryTrailer)) {
+            SCLogDebug("input buffer too small for DNSQueryTrailer");
+            goto insufficient_data;
+        }
+        DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
+        SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
+        data += sizeof(DNSQueryTrailer);
+    }
+
+       SCReturnInt(1);
+insufficient_data:
+    SCReturnInt(0);
+bad_data:
+    SCReturnInt(-1);
+}
+
+static int BufferData(DNSState *dns_state, uint8_t *data, uint16_t len) {
+    if (dns_state->buffer == NULL) {
+        /** \todo be smarter about this, like use a pool or several pools for
+         *        chunks of various sizes */
+        dns_state->buffer = SCMalloc(0xffff);
+        if (dns_state->buffer == NULL) {
+            return -1;
+        }
+    }
+
+    if ((uint32_t)len + (uint32_t)dns_state->offset > (uint32_t)dns_state->record_len) {
+        SCLogInfo("oh my, we have more data than the max record size. What do we do. WHAT DO WE DOOOOO!");
+        BUG_ON(1);
+        len = dns_state->record_len - dns_state->offset;
+    }
+
+    memcpy(dns_state->buffer + dns_state->offset, data, len);
+    dns_state->offset += len;
+    return 0;
+}
+
+static void BufferReset(DNSState *dns_state) {
+    dns_state->record_len = 0;
+    dns_state->offset = 0;
+}
+
+static int DNSRequestParseData(Flow *f, DNSState *dns_state, const uint8_t *input, const uint32_t input_len) {
+    DNSHeader *dns_header = (DNSHeader *)input;
+
+    if (DNSValidateRequestHeader(f, dns_header) < 0)
+        goto bad_data;
+
+    //SCLogInfo("ID %04x", ntohs(dns_header->tx_id));
+
+    uint16_t q;
+    const uint8_t *data = input + sizeof(DNSHeader);
+
+    //PrintRawDataFp(stdout, (uint8_t*)data, input_len - (data - input));
+
+    for (q = 0; q < ntohs(dns_header->questions); q++) {
+        uint8_t fqdn[DNS_MAX_SIZE];
+        uint16_t fqdn_offset = 0;
+
+        if (input + input_len < data + 1) {
+            SCLogDebug("input buffer too small for DNSTcpQuery");
+            goto insufficient_data;
+        }
+        SCLogDebug("query length %u", *data);
+
+        while (*data != 0) {
+            if (*data > 63) {
+                /** \todo set event?*/
+                goto insufficient_data;
+            }
+            uint8_t length = *data;
+
+            data++;
+
+            if (length > 0) {
+                if (input + input_len < data + length) {
+                    SCLogDebug("input buffer too small for domain of len %u", length);
+                    goto insufficient_data;
+                }
+                //PrintRawDataFp(stdout, data, qry->length);
+
+                if ((size_t)(fqdn_offset + length + 1) < sizeof(fqdn)) {
+                    memcpy(fqdn + fqdn_offset, data, length);
+                    fqdn_offset += length;
+                    fqdn[fqdn_offset++] = '.';
+                } else {
+                    /** \todo set event? */
+                    goto insufficient_data;
+                }
+            }
+
+            data += length;
+
+            if (input + input_len < data + 1) {
+                SCLogDebug("input buffer too small for DNSTcpQuery(2)");
+                goto insufficient_data;
+            }
+
+            SCLogDebug("qry length %u", *data);
+        }
+        if (fqdn_offset) {
+            fqdn_offset--;
+        }
+
+        data++;
+        if (input + input_len < data + sizeof(DNSQueryTrailer)) {
+            SCLogDebug("input buffer too small for DNSQueryTrailer");
+            goto insufficient_data;
+        }
+        DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
+        SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
+        data += sizeof(DNSQueryTrailer);
+
+        /* store our data */
+        if (dns_state != NULL) {
+            DNSStoreQueryInState(dns_state, fqdn, fqdn_offset,
+                    ntohs(trailer->type), ntohs(trailer->class),
+                    ntohs(dns_header->tx_id));
+        }
+    }
+
+       SCReturnInt(1);
+bad_data:
+insufficient_data:
+    SCReturnInt(-1);
+
+}
+
+/** \internal
+ *  \brief Parse DNS request packet
+ */
+static int DNSTCPRequestParse(Flow *f, void *dstate,
+                          AppLayerParserState *pstate,
+                          uint8_t *input, uint32_t input_len,
+                          void *local_data, AppLayerParserResult *output)
+{
+       DNSState *dns_state = (DNSState *)dstate;
+    SCLogDebug("starting %u", input_len);
+
+    /** \todo remove this when PP is fixed to enforce ipproto */
+    if (f != NULL && f->proto != IPPROTO_TCP)
+        SCReturnInt(-1);
+
+    /* probably a rst/fin sending an eof */
+    if (input_len == 0) {
+        goto insufficient_data;
+    }
+
+next_record:
+    /* if this is the beginning of a record, we need at least the header */
+    if (dns_state->offset == 0 && input_len < sizeof(DNSTcpHeader)) {
+        SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSTcpHeader));
+        goto insufficient_data;
+    }
+    SCLogDebug("input_len %u offset %u record %u",
+            input_len, dns_state->offset, dns_state->record_len);
+
+    /* this is the first data of this record */
+    if (dns_state->offset == 0) {
+        DNSTcpHeader *dns_tcp_header = (DNSTcpHeader *)input;
+        SCLogDebug("DNS %p", dns_tcp_header);
+
+        if (ntohs(dns_tcp_header->len) < sizeof(DNSHeader)) {
+            /* bogus len, doesn't fit even basic dns header */
+            goto bad_data;
+        } else if (ntohs(dns_tcp_header->len) == (input_len-2)) {
+            /* we have all data, so process w/o buffering */
+            if (DNSRequestParseData(f, dns_state, input+2, input_len-2) < 0)
+                goto bad_data;
+
+        } else if ((input_len-2) > ntohs(dns_tcp_header->len)) {
+            /* we have all data, so process w/o buffering */
+            if (DNSRequestParseData(f, dns_state, input+2, ntohs(dns_tcp_header->len)) < 0)
+                goto bad_data;
+
+            /* treat the rest of the data as a (potential) new record */
+            input += ntohs(dns_tcp_header->len);
+            input_len -= ntohs(dns_tcp_header->len);
+            goto next_record;
+        } else {
+            /* not enough data, store record length and buffer */
+            dns_state->record_len = ntohs(dns_tcp_header->len);
+            BufferData(dns_state, input+2, input_len-2);
+        }
+    } else if (input_len + dns_state->offset < dns_state->record_len) {
+        /* we don't have the full record yet, buffer */
+        BufferData(dns_state, input, input_len);
+    } else if (input_len > (uint32_t)(dns_state->record_len - dns_state->offset)) {
+        /* more data than expected, we may have another record coming up */
+        uint16_t need = (dns_state->record_len - dns_state->offset);
+        BufferData(dns_state, input, need);
+        int r = DNSRequestParseData(f, dns_state, dns_state->buffer, dns_state->record_len);
+        BufferReset(dns_state);
+        if (r < 0)
+            goto bad_data;
+
+        /* treat the rest of the data as a (potential) new record */
+        input += need;
+        input_len -= need;
+        goto next_record;
+    } else {
+        /* implied exactly the amount of data we want
+         * add current to buffer, then inspect buffer */
+        BufferData(dns_state, input, input_len);
+        int r = DNSRequestParseData(f, dns_state, dns_state->buffer, dns_state->record_len);
+        BufferReset(dns_state);
+        if (r < 0)
+            goto bad_data;
+    }
+
+       SCReturnInt(1);
+insufficient_data:
+    SCReturnInt(-1);
+bad_data:
+    SCReturnInt(-1);
+}
+
+static int DNSReponseParseData(Flow *f, DNSState *dns_state, const uint8_t *input, const uint32_t input_len) {
+    DNSHeader *dns_header = (DNSHeader *)input;
+
+    if (DNSValidateResponseHeader(f, dns_header) < 0)
+        goto bad_data;
+
+    DNSTransaction *tx = NULL;
+    int found = 0;
+    TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
+        if (tx->tx_id == ntohs(dns_header->tx_id)) {
+            found = 1;
+            break;
+        }
+    }
+    if (!found) {
+        SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
+        AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
+    }
+
+    uint16_t q;
+    const uint8_t *data = input + sizeof(DNSHeader);
+    for (q = 0; q < ntohs(dns_header->questions); q++) {
+        uint8_t fqdn[DNS_MAX_SIZE];
+        uint16_t fqdn_offset = 0;
+
+        if (input + input_len < data + 1) {
+            SCLogDebug("input buffer too small for len field");
+            goto insufficient_data;
+        }
+        SCLogDebug("qry length %u", *data);
+
+        while (*data != 0) {
+            uint8_t length = *data;
+            data++;
+
+            if (length > 0) {
+                if (input + input_len < data + length) {
+                    SCLogDebug("input buffer too small for domain of len %u", length);
+                    goto insufficient_data;
+                }
+                //PrintRawDataFp(stdout, data, length);
+
+                if ((size_t)(fqdn_offset + length + 1) < sizeof(fqdn)) {
+                    memcpy(fqdn + fqdn_offset, data, length);
+                    fqdn_offset += length;
+                    fqdn[fqdn_offset++] = '.';
+                }
+            }
+
+            data += length;
+
+            if (input + input_len < data + 1) {
+                SCLogDebug("input buffer too small for len field");
+                goto insufficient_data;
+            }
+
+            length = *data;
+            SCLogDebug("length %u", length);
+        }
+        if (fqdn_offset) {
+            fqdn_offset--;
+        }
+
+        data++;
+        if (input + input_len < data + sizeof(DNSQueryTrailer)) {
+            SCLogDebug("input buffer too small for DNSQueryTrailer");
+            goto insufficient_data;
+        }
+#if DEBUG
+        DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
+        SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
+#endif
+        data += sizeof(DNSQueryTrailer);
+    }
+
+    for (q = 0; q < ntohs(dns_header->answer_rr); q++) {
+        data = DNSReponseParse(dns_state, dns_header, q, DNS_LIST_ANSWER,
+                input, input_len, data);
+        if (data == NULL) {
+            goto insufficient_data;
+        }
+    }
+
+    //PrintRawDataFp(stdout, (uint8_t *)data, input_len - (data - input));
+    for (q = 0; q < ntohs(dns_header->authority_rr); q++) {
+        data = DNSReponseParse(dns_state, dns_header, q, DNS_LIST_AUTHORITY,
+                input, input_len, data);
+        if (data == NULL) {
+            goto insufficient_data;
+        }
+    }
+
+       SCReturnInt(1);
+bad_data:
+insufficient_data:
+    SCReturnInt(-1);
+}
+
+/** \internal
+ *  \brief DNS TCP record parser, entry function
+ *
+ *  Parses a DNS TCP record and fills the DNS state
+ *
+ *  As TCP records can be 64k we'll have to buffer the data. Streaming parsing
+ *  would have been _very_ tricky due to the way names are compressed in DNS
+ *
+ */
+static int DNSTCPResponseParse(Flow *f, void *dstate,
+                          AppLayerParserState *pstate,
+                          uint8_t *input, uint32_t input_len,
+                          void *local_data, AppLayerParserResult *output)
+{
+       DNSState *dns_state = (DNSState *)dstate;
+
+    /** \todo remove this when PP is fixed to enforce ipproto */
+    if (f != NULL && f->proto != IPPROTO_TCP)
+        SCReturnInt(-1);
+
+    /* probably a rst/fin sending an eof */
+    if (input_len == 0) {
+        goto insufficient_data;
+    }
+
+next_record:
+    /* if this is the beginning of a record, we need at least the header */
+    if (dns_state->offset == 0 &&  input_len < sizeof(DNSTcpHeader)) {
+        SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSTcpHeader));
+        goto insufficient_data;
+    }
+    SCLogDebug("input_len %u offset %u record %u",
+            input_len, dns_state->offset, dns_state->record_len);
+
+    /* this is the first data of this record */
+    if (dns_state->offset == 0) {
+        DNSTcpHeader *dns_tcp_header = (DNSTcpHeader *)input;
+        SCLogDebug("DNS %p", dns_tcp_header);
+
+        if (ntohs(dns_tcp_header->len) == (input_len-2)) {
+            /* we have all data, so process w/o buffering */
+            if (DNSReponseParseData(f, dns_state, input+2, input_len-2) < 0)
+                goto bad_data;
+
+        } else if ((input_len-2) > ntohs(dns_tcp_header->len)) {
+            /* we have all data, so process w/o buffering */
+            if (DNSReponseParseData(f, dns_state, input+2, ntohs(dns_tcp_header->len)) < 0)
+                goto bad_data;
+
+            /* treat the rest of the data as a (potential) new record */
+            input += ntohs(dns_tcp_header->len);
+            input_len -= ntohs(dns_tcp_header->len);
+            goto next_record;
+        } else {
+            /* not enough data, store record length and buffer */
+            dns_state->record_len = ntohs(dns_tcp_header->len);
+            BufferData(dns_state, input+2, input_len-2);
+        }
+    } else if (input_len + dns_state->offset < dns_state->record_len) {
+        /* we don't have the full record yet, buffer */
+        BufferData(dns_state, input, input_len);
+    } else if (input_len > (uint32_t)(dns_state->record_len - dns_state->offset)) {
+        /* more data than expected, we may have another record coming up */
+        uint16_t need = (dns_state->record_len - dns_state->offset);
+        BufferData(dns_state, input, need);
+        int r = DNSReponseParseData(f, dns_state, dns_state->buffer, dns_state->record_len);
+        BufferReset(dns_state);
+        if (r < 0)
+            goto bad_data;
+
+        /* treat the rest of the data as a (potential) new record */
+        input += need;
+        input_len -= need;
+        goto next_record;
+    } else {
+        /* implied exactly the amount of data we want
+         * add current to buffer, then inspect buffer */
+        BufferData(dns_state, input, input_len);
+        int r = DNSReponseParseData(f, dns_state, dns_state->buffer, dns_state->record_len);
+        BufferReset(dns_state);
+        if (r < 0)
+            goto bad_data;
+    }
+       SCReturnInt(1);
+insufficient_data:
+    SCReturnInt(-1);
+bad_data:
+    SCReturnInt(-1);
+}
+
+static uint16_t DNSTcpProbingParser(uint8_t *input, uint32_t ilen)
+{
+    if (ilen == 0 || ilen < sizeof(DNSTcpHeader)) {
+        SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSTcpHeader));
+        return ALPROTO_UNKNOWN;
+    }
+
+    DNSTcpHeader *dns_header = (DNSTcpHeader *)input;
+    if (ntohs(dns_header->len) < sizeof(DNSHeader)) {
+        /* length field bogus, won't even fit a minimal DNS header. */
+        return ALPROTO_FAILED;
+    } else if (ntohs(dns_header->len) > ilen) {
+        int r = DNSTCPRequestParseProbe(input, ilen);
+        if (r == -1) {
+            /* probing parser told us "bad data", so it's not
+             * DNS */
+            return ALPROTO_FAILED;
+        } else if (ilen > 512) {
+            SCLogDebug("all the parser told us was not enough data, which is expected. Lets assume it's DNS");
+            return ALPROTO_DNS_TCP;
+        }
+
+        SCLogDebug("not yet enough info %u > %u", ntohs(dns_header->len), ilen);
+        return ALPROTO_UNKNOWN;
+    }
+
+    int r = DNSTCPRequestParseProbe(input, ilen);
+    if (r != 1)
+        return ALPROTO_FAILED;
+
+    SCLogDebug("ALPROTO_DNS_TCP");
+    return ALPROTO_DNS_TCP;
+}
+
+/**
+ *  \brief Update the transaction id based on the dns state
+ */
+void DNSStateUpdateTransactionId(void *state, uint16_t *id) {
+    SCEnter();
+
+    DNSState *s = state;
+
+    SCLogDebug("original id %"PRIu16", s->transaction_cnt %"PRIu16,
+            *id, (s->transaction_cnt));
+
+    if ((s->transaction_cnt) > (*id)) {
+        SCLogDebug("original id %"PRIu16", updating with s->transaction_cnt %"PRIu16,
+                *id, (s->transaction_cnt));
+
+        (*id) = (s->transaction_cnt);
+
+        SCLogDebug("updated id %"PRIu16, *id);
+    }
+
+    SCReturn;
+}
+
+/**
+ *  \brief dns transaction cleanup callback
+ */
+void DNSStateTransactionFree(void *state, uint16_t id) {
+    SCEnter();
+
+    DNSState *s = state;
+
+    s->transaction_done = id;
+    SCLogDebug("state %p, id %"PRIu16, s, id);
+
+    /* we can't remove the actual transactions here */
+
+    SCReturn;
+}
+
+
+void RegisterDNSTCPParsers(void) {
+    char *proto_name = "dnstcp";
+
+    /** DNS */
+       AppLayerRegisterProto(proto_name, ALPROTO_DNS_TCP, STREAM_TOSERVER,
+                       DNSTCPRequestParse);
+       AppLayerRegisterProto(proto_name, ALPROTO_DNS_TCP, STREAM_TOCLIENT,
+                       DNSTCPResponseParse);
+       AppLayerRegisterStateFuncs(ALPROTO_DNS_TCP, DNSStateAlloc,
+                       DNSStateFree);
+    AppLayerRegisterTransactionIdFuncs(ALPROTO_DNS_TCP,
+            DNSStateUpdateTransactionId, DNSStateTransactionFree);
+
+    AppLayerRegisterGetTx(ALPROTO_DNS_TCP,
+            DNSGetTx);
+    AppLayerRegisterGetTxCnt(ALPROTO_DNS_TCP,
+            DNSGetTxCnt);
+    AppLayerRegisterGetAlstateProgressFunc(ALPROTO_DNS_TCP,
+            DNSGetAlstateProgress);
+    AppLayerRegisterGetAlstateProgressCompletionStatus(ALPROTO_DNS_TCP,
+            DNSGetAlstateProgressCompletionStatus);
+
+    AppLayerRegisterProbingParser(&alp_proto_ctx,
+                                  53,
+                                  IPPROTO_TCP,
+                                  proto_name,
+                                  ALPROTO_DNS_TCP,
+                                  0, sizeof(DNSTcpHeader),
+                                  STREAM_TOSERVER,
+                                  APP_LAYER_PROBING_PARSER_PRIORITY_HIGH, 1,
+                                  DNSTcpProbingParser);
+
+    DNSAppLayerDecoderEventsRegister(ALPROTO_DNS_TCP);
+}
+
+/* UNITTESTS */
+#ifdef UNITTESTS
+void DNSTCPParserRegisterTests(void) {
+//     UtRegisterTest("DNSTCPParserTest01", DNSTCPParserTest01, 1);
+}
+#endif
diff --git a/src/app-layer-dns-tcp.h b/src/app-layer-dns-tcp.h
new file mode 100644 (file)
index 0000000..2f3b4ff
--- /dev/null
@@ -0,0 +1,38 @@
+/* Copyright (C) 2013 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+
+#ifndef __APP_LAYER_DNS_TCP_H__
+#define __APP_LAYER_DNS_TCP_H__
+
+#include "app-layer-protos.h"
+#include "app-layer-parser.h"
+#include "app-layer-dns-common.h"
+#include "flow.h"
+#include "queue.h"
+#include "util-byte.h"
+
+void RegisterDNSTCPParsers(void);
+void DNSTCPParserTests(void);
+void DNSTCPParserRegisterTests(void);
+
+#endif /* __APP_LAYER_DNS_TCP_H__ */
diff --git a/src/app-layer-dns-udp.c b/src/app-layer-dns-udp.c
new file mode 100644 (file)
index 0000000..0db39c7
--- /dev/null
@@ -0,0 +1,376 @@
+/* Copyright (C) 2013 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#include "suricata-common.h"
+#include "suricata.h"
+
+#include "debug.h"
+#include "decode.h"
+
+#include "flow-util.h"
+
+#include "threads.h"
+
+#include "util-print.h"
+#include "util-pool.h"
+#include "util-debug.h"
+
+#include "stream-tcp-private.h"
+#include "stream-tcp-reassemble.h"
+#include "stream-tcp.h"
+#include "stream.h"
+
+#include "app-layer-protos.h"
+#include "app-layer-parser.h"
+
+#include "util-spm.h"
+#include "util-unittest.h"
+
+#include "app-layer-dns-udp.h"
+
+/** \internal
+ *  \brief Parse DNS request packet
+ */
+static int DNSUDPRequestParse(Flow *f, void *dstate,
+                          AppLayerParserState *pstate,
+                          uint8_t *input, uint32_t input_len,
+                          void *local_data, AppLayerParserResult *output)
+{
+    DNSState *dns_state = (DNSState *)dstate;
+
+    SCLogDebug("starting %u", input_len);
+
+    /** \todo remove this when PP is fixed to enforce ipproto */
+    if (f != NULL && f->proto != IPPROTO_UDP)
+        SCReturnInt(-1);
+
+    if (input_len == 0 || input_len < sizeof(DNSHeader)) {
+        SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSHeader));
+        goto insufficient_data;
+    }
+
+    DNSHeader *dns_header = (DNSHeader *)input;
+    SCLogDebug("DNS %p", dns_header);
+
+    if (DNSValidateRequestHeader(f, dns_header) < 0)
+        goto bad_data;
+
+    uint16_t q;
+    const uint8_t *data = input + sizeof(DNSHeader);
+    for (q = 0; q < ntohs(dns_header->questions); q++) {
+        uint8_t fqdn[DNS_MAX_SIZE];
+        uint16_t fqdn_offset = 0;
+
+        if (input + input_len < data + 1) {
+            SCLogDebug("input buffer too small for len");
+            goto insufficient_data;
+        }
+        SCLogDebug("query length %u", *data);
+
+        while (*data != 0) {
+            if (*data > 63) {
+                /** \todo set event?*/
+                goto insufficient_data;
+            }
+            uint8_t length = *data;
+
+            data++;
+
+            if (length > 0) {
+                if (input + input_len < data + length) {
+                    SCLogDebug("input buffer too small for domain of len %u", length);
+                    goto insufficient_data;
+                }
+                //PrintRawDataFp(stdout, data, qry->length);
+
+                if ((size_t)(fqdn_offset + length + 1) < sizeof(fqdn)) {
+                    memcpy(fqdn + fqdn_offset, data, length);
+                    fqdn_offset += length;
+                    fqdn[fqdn_offset++] = '.';
+                } else {
+                    /** \todo set event? */
+                    goto insufficient_data;
+                }
+            }
+
+            data += length;
+
+            if (input + input_len < data + 1) {
+                SCLogDebug("input buffer too small for len(2)");
+                goto insufficient_data;
+            }
+
+            SCLogDebug("qry length %u", *data);
+        }
+        if (fqdn_offset) {
+            fqdn_offset--;
+        }
+
+        data++;
+        if (input + input_len < data + sizeof(DNSQueryTrailer)) {
+            SCLogDebug("input buffer too small for DNSQueryTrailer");
+            goto insufficient_data;
+        }
+        DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
+        SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
+        data += sizeof(DNSQueryTrailer);
+
+        /* store our data */
+        if (dns_state != NULL) {
+            DNSStoreQueryInState(dns_state, fqdn, fqdn_offset,
+                    ntohs(trailer->type), ntohs(trailer->class),
+                    ntohs(dns_header->tx_id));
+        }
+    }
+
+       SCReturnInt(1);
+bad_data:
+insufficient_data:
+    SCReturnInt(-1);
+}
+
+/** \internal
+ *  \brief DNS UDP record parser, entry function
+ *
+ *  Parses a DNS UDP record and fills the DNS state
+ *
+ */
+static int DNSUDPResponseParse(Flow *f, void *dstate,
+                          AppLayerParserState *pstate,
+                          uint8_t *input, uint32_t input_len,
+                          void *local_data, AppLayerParserResult *output)
+{
+       DNSState *dns_state = (DNSState *)dstate;
+
+    SCLogDebug("starting %u", input_len);
+
+    /** \todo remove this when PP is fixed to enforce ipproto */
+    if (f != NULL && f->proto != IPPROTO_UDP)
+        SCReturnInt(-1);
+
+    if (input_len == 0 || input_len < sizeof(DNSHeader)) {
+        SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSHeader));
+        goto insufficient_data;
+    }
+
+    DNSHeader *dns_header = (DNSHeader *)input;
+    SCLogDebug("DNS %p", dns_header);
+
+    DNSTransaction *tx = NULL;
+    int found = 0;
+    TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
+        if (tx->tx_id == ntohs(dns_header->tx_id)) {
+            found = 1;
+            break;
+        }
+    }
+    if (!found) {
+        SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
+        AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
+    }
+
+    if (DNSValidateResponseHeader(f, dns_header) < 0)
+        goto bad_data;
+
+    uint16_t q;
+    const uint8_t *data = input + sizeof(DNSHeader);
+    for (q = 0; q < ntohs(dns_header->questions); q++) {
+        uint8_t fqdn[DNS_MAX_SIZE];
+        uint16_t fqdn_offset = 0;
+
+        if (input + input_len < data + 1) {
+            SCLogDebug("input buffer too small for len");
+            goto insufficient_data;
+        }
+        SCLogDebug("qry length %u", *data);
+
+        while (*data != 0) {
+            uint8_t length = *data;
+            data++;
+
+            if (length > 0) {
+                if (input + input_len < data + length) {
+                    SCLogDebug("input buffer too small for domain of len %u", length);
+                    goto insufficient_data;
+                }
+                //PrintRawDataFp(stdout, data, length);
+
+                if ((size_t)(fqdn_offset + length + 1) < sizeof(fqdn)) {
+                    memcpy(fqdn + fqdn_offset, data, length);
+                    fqdn_offset += length;
+                    fqdn[fqdn_offset++] = '.';
+                }
+            }
+
+            data += length;
+
+            if (input + input_len < data + 1) {
+                SCLogDebug("input buffer too small for len");
+                goto insufficient_data;
+            }
+
+            length = *data;
+            SCLogDebug("length %u", length);
+        }
+        if (fqdn_offset) {
+            fqdn_offset--;
+        }
+
+        data++;
+        if (input + input_len < data + sizeof(DNSQueryTrailer)) {
+            SCLogDebug("input buffer too small for DNSQueryTrailer");
+            goto insufficient_data;
+        }
+#if DEBUG
+        DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
+        SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
+#endif
+        data += sizeof(DNSQueryTrailer);
+    }
+
+    for (q = 0; q < ntohs(dns_header->answer_rr); q++) {
+        data = DNSReponseParse(dns_state, dns_header, q, DNS_LIST_ANSWER,
+                input, input_len, data);
+        if (data == NULL) {
+            goto insufficient_data;
+        }
+    }
+
+    for (q = 0; q < ntohs(dns_header->authority_rr); q++) {
+        data = DNSReponseParse(dns_state, dns_header, q, DNS_LIST_AUTHORITY,
+                input, input_len, data);
+        if (data == NULL) {
+            goto insufficient_data;
+        }
+    }
+
+    /* see if this is a "no such name" error */
+    if (ntohs(dns_header->flags) & 0x0003) {
+        SCLogDebug("no such name");
+
+        if (dns_state->curr != NULL) {
+            dns_state->curr->no_such_name = 1;
+        }
+    }
+
+       SCReturnInt(1);
+
+bad_data:
+insufficient_data:
+    AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_MALFORMED_DATA);
+    SCReturnInt(-1);
+}
+
+static uint16_t DNSUdpProbingParser(uint8_t *input, uint32_t ilen)
+{
+    if (ilen == 0 || ilen < sizeof(DNSHeader)) {
+        SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSHeader));
+        return ALPROTO_UNKNOWN;
+    }
+
+    if (DNSUDPRequestParse(NULL, NULL, NULL, input, ilen, NULL, NULL) == -1)
+        return ALPROTO_FAILED;
+
+    return ALPROTO_DNS_UDP;
+}
+
+/**
+ *  \brief Update the transaction id based on the dns state
+ */
+static void DNSStateUpdateTransactionId(void *state, uint16_t *id) {
+    SCEnter();
+
+    DNSState *s = state;
+
+    SCLogDebug("original id %"PRIu16", s->transaction_cnt %"PRIu16,
+            *id, (s->transaction_cnt));
+
+    if ((s->transaction_cnt) > (*id)) {
+        SCLogDebug("original id %"PRIu16", updating with s->transaction_cnt %"PRIu16,
+                *id, (s->transaction_cnt));
+
+        (*id) = (s->transaction_cnt);
+
+        SCLogDebug("updated id %"PRIu16, *id);
+    }
+
+    SCReturn;
+}
+
+/**
+ *  \brief dns transaction cleanup callback
+ */
+static void DNSStateTransactionFree(void *state, uint16_t id) {
+    SCEnter();
+
+    DNSState *s = state;
+
+    s->transaction_done = id;
+    SCLogDebug("state %p, id %"PRIu16, s, id);
+
+    /* we can't remove the actual transactions here */
+
+    SCReturn;
+}
+
+
+void RegisterDNSUDPParsers(void) {
+    char *proto_name = "dnsudp";
+
+    /** DNS */
+       AppLayerRegisterProto(proto_name, ALPROTO_DNS_UDP, STREAM_TOSERVER,
+                       DNSUDPRequestParse);
+       AppLayerRegisterProto(proto_name, ALPROTO_DNS_UDP, STREAM_TOCLIENT,
+                       DNSUDPResponseParse);
+       AppLayerRegisterStateFuncs(ALPROTO_DNS_UDP, DNSStateAlloc,
+                       DNSStateFree);
+    AppLayerRegisterTransactionIdFuncs(ALPROTO_DNS_UDP,
+            DNSStateUpdateTransactionId, DNSStateTransactionFree);
+
+    AppLayerRegisterGetTx(ALPROTO_DNS_UDP,
+            DNSGetTx);
+    AppLayerRegisterGetTxCnt(ALPROTO_DNS_UDP,
+            DNSGetTxCnt);
+    AppLayerRegisterGetAlstateProgressFunc(ALPROTO_DNS_UDP,
+            DNSGetAlstateProgress);
+    AppLayerRegisterGetAlstateProgressCompletionStatus(ALPROTO_DNS_UDP,
+            DNSGetAlstateProgressCompletionStatus);
+
+    AppLayerRegisterProbingParser(&alp_proto_ctx,
+                                  53,
+                                  IPPROTO_UDP,
+                                  proto_name,
+                                  ALPROTO_DNS_UDP,
+                                  0, sizeof(DNSHeader),
+                                  STREAM_TOSERVER,
+                                  APP_LAYER_PROBING_PARSER_PRIORITY_HIGH, 1,
+                                  DNSUdpProbingParser);
+
+    DNSAppLayerDecoderEventsRegister(ALPROTO_DNS_UDP);
+}
+
+/* UNITTESTS */
+#ifdef UNITTESTS
+void DNSUDPParserRegisterTests(void) {
+//     UtRegisterTest("DNSUDPParserTest01", DNSUDPParserTest01, 1);
+}
+#endif
diff --git a/src/app-layer-dns-udp.h b/src/app-layer-dns-udp.h
new file mode 100644 (file)
index 0000000..a6ee12a
--- /dev/null
@@ -0,0 +1,37 @@
+/* Copyright (C) 2013 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#ifndef __APP_LAYER_DNS_UDP_H__
+#define __APP_LAYER_DNS_UDP_H__
+
+#include "app-layer-protos.h"
+#include "app-layer-parser.h"
+#include "app-layer-dns-common.h"
+#include "flow.h"
+#include "queue.h"
+#include "util-byte.h"
+
+void RegisterDNSUDPParsers(void);
+void DNSUDPParserTests(void);
+void DNSUDPParserRegisterTests(void);
+
+#endif /* __APP_LAYER_DNS_UDP_H__ */
index 20f3d9c4324f1ffb96226fc285074a08b8d6aed2..d84162de6a101ae84525f92b9cccb6ab960d0b03 100644 (file)
@@ -52,6 +52,8 @@
 #include "app-layer-ssl.h"
 #include "app-layer-ssh.h"
 #include "app-layer-smtp.h"
+#include "app-layer-dns-udp.h"
+#include "app-layer-dns-tcp.h"
 
 #include "util-spm.h"
 
@@ -1256,6 +1258,8 @@ void RegisterAppLayerParsers(void)
     RegisterFTPParsers();
     RegisterSSHParsers();
     RegisterSMTPParsers();
+    RegisterDNSUDPParsers();
+    RegisterDNSTCPParsers();
 
     /** IMAP */
     //AlpProtoAdd(&alp_proto_ctx, IPPROTO_TCP, ALPROTO_IMAP, "|2A 20|OK|20|", 5, 0, STREAM_TOCLIENT);
index e6a76cfdb602180ae52ba9506a7e1b4504afcd60..7e8abeae23fd990a2045bf0fe56dcfe1f04ff1e2 100644 (file)
@@ -49,6 +49,9 @@ const char *TmModuleAlprotoToString(int proto)
         CASE_CODE (ALPROTO_DCERPC);
         CASE_CODE (ALPROTO_DCERPC_UDP);
 
+        CASE_CODE (ALPROTO_DNS_UDP);
+        CASE_CODE (ALPROTO_DNS_TCP);
+
         default:
             return "ALPROTO_UNDEFINED";
     }
index 2da4b7cc943467d7e967864a749e8f0bc13a64f9..c065509be05140d56aee4a9648beb4ab9c75418f 100644 (file)
@@ -39,6 +39,8 @@ enum {
     ALPROTO_DCERPC,
     ALPROTO_DCERPC_UDP,
     ALPROTO_IRC,
+    ALPROTO_DNS_UDP,
+    ALPROTO_DNS_TCP,
     /* used by the probing parser when alproto detection fails
      * permanently for that particular stream */
     ALPROTO_FAILED,
diff --git a/src/log-dnslog.c b/src/log-dnslog.c
new file mode 100644 (file)
index 0000000..25ee03e
--- /dev/null
@@ -0,0 +1,479 @@
+/* Copyright (C) 2007-2013 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ *
+ * Implements dns logging portion of the engine.
+ */
+
+#include "suricata-common.h"
+#include "debug.h"
+#include "detect.h"
+#include "pkt-var.h"
+#include "conf.h"
+
+#include "threads.h"
+#include "threadvars.h"
+#include "tm-threads.h"
+
+#include "util-print.h"
+#include "util-unittest.h"
+
+#include "util-debug.h"
+
+#include "output.h"
+#include "log-dnslog.h"
+#include "app-layer-dns-udp.h"
+#include "app-layer.h"
+#include "util-privs.h"
+#include "util-buffer.h"
+
+#include "util-logopenfile.h"
+
+#define DEFAULT_LOG_FILENAME "dns.log"
+
+#define MODULE_NAME "LogDnsLog"
+
+#define OUTPUT_BUFFER_SIZE 65535
+
+/* we can do query logging as well, but it's disabled for now as the
+ * TX id handling doesn't expect it */
+#define QUERY 0
+
+TmEcode LogDnsLog (ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
+TmEcode LogDnsLogIPv4(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
+TmEcode LogDnsLogIPv6(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
+TmEcode LogDnsLogThreadInit(ThreadVars *, void *, void **);
+TmEcode LogDnsLogThreadDeinit(ThreadVars *, void *);
+void LogDnsLogExitPrintStats(ThreadVars *, void *);
+static void LogDnsLogDeInitCtx(OutputCtx *);
+
+void TmModuleLogDnsLogRegister (void) {
+    tmm_modules[TMM_LOGDNSLOG].name = MODULE_NAME;
+    tmm_modules[TMM_LOGDNSLOG].ThreadInit = LogDnsLogThreadInit;
+    tmm_modules[TMM_LOGDNSLOG].Func = LogDnsLog;
+    tmm_modules[TMM_LOGDNSLOG].ThreadExitPrintStats = LogDnsLogExitPrintStats;
+    tmm_modules[TMM_LOGDNSLOG].ThreadDeinit = LogDnsLogThreadDeinit;
+    tmm_modules[TMM_LOGDNSLOG].RegisterTests = NULL;
+    tmm_modules[TMM_LOGDNSLOG].cap_flags = 0;
+
+    OutputRegisterModule(MODULE_NAME, "dns-log", LogDnsLogInitCtx);
+
+    /* enable the logger for the app layer */
+    AppLayerRegisterLogger(ALPROTO_DNS_UDP);
+    AppLayerRegisterLogger(ALPROTO_DNS_TCP);
+    SCLogInfo("registered %s", MODULE_NAME);
+}
+
+typedef struct LogDnsFileCtx_ {
+    LogFileCtx *file_ctx;
+    uint32_t flags; /** Store mode */
+} LogDnsFileCtx;
+
+typedef struct LogDnsLogThread_ {
+    LogDnsFileCtx *dnslog_ctx;
+    /** LogFileCtx has the pointer to the file and a mutex to allow multithreading */
+    uint32_t dns_cnt;
+
+    MemBuffer *buffer;
+} LogDnsLogThread;
+
+static void CreateTimeString (const struct timeval *ts, char *str, size_t size)
+{
+    time_t time = ts->tv_sec;
+    struct tm local_tm;
+    struct tm *t = (struct tm *)SCLocalTime(time, &local_tm);
+
+    snprintf(str, size, "%02d/%02d/%02d-%02d:%02d:%02d.%06u",
+        t->tm_mon + 1, t->tm_mday, t->tm_year + 1900, t->tm_hour,
+            t->tm_min, t->tm_sec, (uint32_t) ts->tv_usec);
+}
+
+static void CreateTypeString(uint16_t type, char *str, size_t str_size) {
+    if (type == DNS_RECORD_TYPE_A) {
+        snprintf(str, str_size, "A");
+    } else if (type == DNS_RECORD_TYPE_NS) {
+        snprintf(str, str_size, "NS");
+    } else if (type == DNS_RECORD_TYPE_AAAA) {
+        snprintf(str, str_size, "AAAA");
+    } else if (type == DNS_RECORD_TYPE_TXT) {
+        snprintf(str, str_size, "TXT");
+    } else if (type == DNS_RECORD_TYPE_CNAME) {
+        snprintf(str, str_size, "CNAME");
+    } else if (type == DNS_RECORD_TYPE_SOA) {
+        snprintf(str, str_size, "SOA");
+    } else if (type == DNS_RECORD_TYPE_MX) {
+        snprintf(str, str_size, "MX");
+    } else if (type == DNS_RECORD_TYPE_PTR) {
+        snprintf(str, str_size, "PTR");
+    } else if (type == DNS_RECORD_TYPE_ANY) {
+        snprintf(str, str_size, "ANY");
+    } else if (type == DNS_RECORD_TYPE_TKEY) {
+        snprintf(str, str_size, "TKEY");
+    } else if (type == DNS_RECORD_TYPE_TSIG) {
+        snprintf(str, str_size, "TSIG");
+    } else {
+        snprintf(str, str_size, "%04x/%u", type, type);
+    }
+}
+
+static void LogQuery(LogDnsLogThread *aft, char *timebuf, char *srcip, char *dstip, Port sp, Port dp, DNSTransaction *tx, DNSQueryEntry *entry) {
+    LogDnsFileCtx *hlog = aft->dnslog_ctx;
+
+    SCLogDebug("got a DNS request and now logging !!");
+
+    /* reset */
+    MemBufferReset(aft->buffer);
+
+    /* time & tx */
+    MemBufferWriteString(aft->buffer,
+            "%s [**] Query TX %04x [**] ", timebuf, tx->tx_id);
+
+    /* query */
+    PrintRawUriBuf((char *)aft->buffer->buffer, &aft->buffer->offset, aft->buffer->size,
+            (uint8_t *)((uint8_t *)entry + sizeof(DNSQueryEntry)),
+            entry->len);
+
+    char record[16] = "";
+    CreateTypeString(entry->type, record, sizeof(record));
+    MemBufferWriteString(aft->buffer,
+            " [**] %s [**] %s:%" PRIu16 " -> %s:%" PRIu16 "\n",
+            record, srcip, sp, dstip, dp);
+
+    aft->dns_cnt++;
+
+    SCMutexLock(&hlog->file_ctx->fp_mutex);
+    (void)MemBufferPrintToFPAsString(aft->buffer, hlog->file_ctx->fp);
+    fflush(hlog->file_ctx->fp);
+    SCMutexUnlock(&hlog->file_ctx->fp_mutex);
+}
+
+static void LogAnswer(LogDnsLogThread *aft, char *timebuf, char *srcip, char *dstip, Port sp, Port dp, DNSTransaction *tx, DNSAnswerEntry *entry) {
+    LogDnsFileCtx *hlog = aft->dnslog_ctx;
+
+    SCLogDebug("got a DNS response and now logging !!");
+
+    /* reset */
+    MemBufferReset(aft->buffer);
+
+    /* time & tx*/
+    MemBufferWriteString(aft->buffer,
+            "%s [**] Response TX %04x [**] ", timebuf, tx->tx_id);
+
+    if (entry == NULL) {
+        MemBufferWriteString(aft->buffer,
+                "No Such Name");
+    } else {
+        /* query */
+        if (entry->fqdn_len > 0) {
+            PrintRawUriBuf((char *)aft->buffer->buffer, &aft->buffer->offset, aft->buffer->size,
+                    (uint8_t *)((uint8_t *)entry + sizeof(DNSAnswerEntry)),
+                    entry->fqdn_len);
+        } else {
+            MemBufferWriteString(aft->buffer, "<no data>");
+        }
+
+        char record[16] = "";
+        CreateTypeString(entry->type, record, sizeof(record));
+        MemBufferWriteString(aft->buffer,
+                " [**] %s [**] TTL %u [**] ", record, entry->ttl);
+
+        uint8_t *ptr = (uint8_t *)((uint8_t *)entry + sizeof(DNSAnswerEntry) + entry->fqdn_len);
+        if (entry->type == DNS_RECORD_TYPE_A) {
+            char a[16] = "";
+            PrintInet(AF_INET, (const void *)ptr, a, sizeof(a));
+            MemBufferWriteString(aft->buffer, "%s", a);
+        } else if (entry->type == DNS_RECORD_TYPE_AAAA) {
+            char a[46];
+            PrintInet(AF_INET6, (const void *)ptr, a, sizeof(a));
+            MemBufferWriteString(aft->buffer, "%s", a);
+        } else if (entry->data_len == 0) {
+            MemBufferWriteString(aft->buffer, "<no data>");
+        } else {
+            PrintRawUriBuf((char *)aft->buffer->buffer, &aft->buffer->offset,
+                    aft->buffer->size, ptr, entry->data_len);
+        }
+    }
+
+    /* ip/tcp header info */
+    MemBufferWriteString(aft->buffer,
+            " [**] %s:%" PRIu16 " -> %s:%" PRIu16 "\n",
+            srcip, sp, dstip, dp);
+
+    aft->dns_cnt++;
+
+    SCMutexLock(&hlog->file_ctx->fp_mutex);
+    (void)MemBufferPrintToFPAsString(aft->buffer, hlog->file_ctx->fp);
+    fflush(hlog->file_ctx->fp);
+    SCMutexUnlock(&hlog->file_ctx->fp_mutex);
+}
+
+static TmEcode LogDnsLogIPWrapper(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq,
+                            PacketQueue *postpq, int ipproto)
+{
+    SCEnter();
+
+    LogDnsLogThread *aft = (LogDnsLogThread *)data;
+    char timebuf[64];
+
+    /* no flow, no htp state */
+    if (p->flow == NULL) {
+        SCLogDebug("no flow");
+        SCReturnInt(TM_ECODE_OK);
+    }
+
+    /* check if we have DNS state or not */
+    FLOWLOCK_WRLOCK(p->flow); /* WRITE lock before we updated flow logged id */
+    uint16_t proto = AppLayerGetProtoFromPacket(p);
+    if (proto != ALPROTO_DNS_UDP && proto != ALPROTO_DNS_TCP) {
+        SCLogDebug("proto not ALPROTO_DNS_UDP: %u", proto);
+        goto end;
+    }
+
+    DNSState *dns_state = (DNSState *)AppLayerGetProtoStateFromPacket(p);
+    if (dns_state == NULL) {
+        SCLogDebug("no dns state, so no request logging");
+        goto end;
+    }
+
+    uint64_t total_txs = AppLayerGetTxCnt(proto, dns_state);
+    uint64_t tx_id = AppLayerTransactionGetLogId(p->flow);
+    //int tx_progress_done_value_ts = AppLayerGetAlstateProgressCompletionStatus(proto, 0);
+    //int tx_progress_done_value_tc = AppLayerGetAlstateProgressCompletionStatus(proto, 1);
+
+    SCLogDebug("pcap_cnt %"PRIu64, p->pcap_cnt);
+    CreateTimeString(&p->ts, timebuf, sizeof(timebuf));
+
+    char srcip[46], dstip[46];
+    Port sp, dp;
+    if ((PKT_IS_TOCLIENT(p))) {
+        switch (ipproto) {
+            case AF_INET:
+                PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), srcip, sizeof(srcip));
+                PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), dstip, sizeof(dstip));
+                break;
+            case AF_INET6:
+                PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), srcip, sizeof(srcip));
+                PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), dstip, sizeof(dstip));
+                break;
+            default:
+                goto end;
+        }
+        sp = p->sp;
+        dp = p->dp;
+    } else {
+        switch (ipproto) {
+            case AF_INET:
+                PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), srcip, sizeof(srcip));
+                PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), dstip, sizeof(dstip));
+                break;
+            case AF_INET6:
+                PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), srcip, sizeof(srcip));
+                PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), dstip, sizeof(dstip));
+                break;
+            default:
+                goto end;
+        }
+        sp = p->dp;
+        dp = p->sp;
+    }
+#if QUERY
+    if (PKT_IS_TOSERVER(p)) {
+        DNSTransaction *tx = NULL;
+        TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
+            DNSQueryEntry *entry = NULL;
+            TAILQ_FOREACH(entry, &tx->query_list, next) {
+                LogQuery(aft, timebuf, srcip, dstip, sp, dp, tx, entry);
+            }
+        }
+    } else
+#endif
+    if ((PKT_IS_TOCLIENT(p))) {
+        DNSTransaction *tx = NULL;
+        for (; tx_id < total_txs; tx_id++)
+        {
+            tx = AppLayerGetTx(proto, dns_state, tx_id);
+            if (tx == NULL)
+                continue;
+
+            DNSQueryEntry *query = NULL;
+            TAILQ_FOREACH(query, &tx->query_list, next) {
+                LogQuery(aft, timebuf, dstip, srcip, dp, sp, tx, query);
+            }
+
+            if (tx->no_such_name) {
+                LogAnswer(aft, timebuf, srcip, dstip, sp, dp, tx, NULL);
+            }
+
+            DNSAnswerEntry *entry = NULL;
+            TAILQ_FOREACH(entry, &tx->answer_list, next) {
+                LogAnswer(aft, timebuf, srcip, dstip, sp, dp, tx, entry);
+            }
+
+            entry = NULL;
+            TAILQ_FOREACH(entry, &tx->authority_list, next) {
+                LogAnswer(aft, timebuf, srcip, dstip, sp, dp, tx, entry);
+            }
+
+            SCLogDebug("calling AppLayerTransactionUpdateLoggedId");
+            AppLayerTransactionUpdateLogId(p->flow);
+        }
+    }
+
+end:
+    FLOWLOCK_UNLOCK(p->flow);
+    SCReturnInt(TM_ECODE_OK);
+}
+
+TmEcode LogDnsLogIPv4(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq)
+{
+    return LogDnsLogIPWrapper(tv, p, data, pq, postpq, AF_INET);
+}
+
+TmEcode LogDnsLogIPv6(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq)
+{
+    return LogDnsLogIPWrapper(tv, p, data, pq, postpq, AF_INET6);
+}
+
+TmEcode LogDnsLog (ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq)
+{
+    SCEnter();
+
+    SCLogDebug("pcap_cnt %"PRIu64, p->pcap_cnt);
+    /* no flow, no htp state */
+    if (p->flow == NULL) {
+        SCReturnInt(TM_ECODE_OK);
+    }
+
+    if (!(PKT_IS_UDP(p)) && !(PKT_IS_TCP(p))) {
+        SCReturnInt(TM_ECODE_OK);
+    }
+
+    if (PKT_IS_IPV4(p)) {
+        int r  = LogDnsLogIPv4(tv, p, data, pq, postpq);
+        SCReturnInt(r);
+    } else if (PKT_IS_IPV6(p)) {
+        int r  = LogDnsLogIPv6(tv, p, data, pq, postpq);
+        SCReturnInt(r);
+    }
+
+    SCReturnInt(TM_ECODE_OK);
+}
+
+TmEcode LogDnsLogThreadInit(ThreadVars *t, void *initdata, void **data)
+{
+    LogDnsLogThread *aft = SCMalloc(sizeof(LogDnsLogThread));
+    if (unlikely(aft == NULL))
+        return TM_ECODE_FAILED;
+    memset(aft, 0, sizeof(LogDnsLogThread));
+
+    if(initdata == NULL)
+    {
+        SCLogDebug("Error getting context for DNSLog.  \"initdata\" argument NULL");
+        SCFree(aft);
+        return TM_ECODE_FAILED;
+    }
+
+    aft->buffer = MemBufferCreateNew(OUTPUT_BUFFER_SIZE);
+    if (aft->buffer == NULL) {
+        SCFree(aft);
+        return TM_ECODE_FAILED;
+    }
+
+    /* Use the Ouptut Context (file pointer and mutex) */
+    aft->dnslog_ctx= ((OutputCtx *)initdata)->data;
+
+    *data = (void *)aft;
+    return TM_ECODE_OK;
+}
+
+TmEcode LogDnsLogThreadDeinit(ThreadVars *t, void *data)
+{
+    LogDnsLogThread *aft = (LogDnsLogThread *)data;
+    if (aft == NULL) {
+        return TM_ECODE_OK;
+    }
+
+    MemBufferFree(aft->buffer);
+    /* clear memory */
+    memset(aft, 0, sizeof(LogDnsLogThread));
+
+    SCFree(aft);
+    return TM_ECODE_OK;
+}
+
+void LogDnsLogExitPrintStats(ThreadVars *tv, void *data) {
+    LogDnsLogThread *aft = (LogDnsLogThread *)data;
+    if (aft == NULL) {
+        return;
+    }
+
+    SCLogInfo("DNS logger logged %" PRIu32 " requests", aft->dns_cnt);
+}
+
+/** \brief Create a new dns log LogFileCtx.
+ *  \param conf Pointer to ConfNode containing this loggers configuration.
+ *  \return NULL if failure, LogFileCtx* to the file_ctx if succesful
+ * */
+OutputCtx *LogDnsLogInitCtx(ConfNode *conf)
+{
+    LogFileCtx* file_ctx = LogFileNewCtx();
+
+    if(file_ctx == NULL) {
+        SCLogError(SC_ERR_DNS_LOG_GENERIC, "couldn't create new file_ctx");
+        return NULL;
+    }
+
+    if (SCConfLogOpenGeneric(conf, file_ctx, DEFAULT_LOG_FILENAME) < 0) {
+        LogFileFreeCtx(file_ctx);
+        return NULL;
+    }
+
+    LogDnsFileCtx *dnslog_ctx = SCMalloc(sizeof(LogDnsFileCtx));
+    if (unlikely(dnslog_ctx == NULL)) {
+        LogFileFreeCtx(file_ctx);
+        return NULL;
+    }
+    memset(dnslog_ctx, 0x00, sizeof(LogDnsFileCtx));
+
+    dnslog_ctx->file_ctx = file_ctx;
+
+    OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
+    if (unlikely(output_ctx == NULL)) {
+        LogFileFreeCtx(file_ctx);
+        SCFree(dnslog_ctx);
+        return NULL;
+    }
+
+    output_ctx->data = dnslog_ctx;
+    output_ctx->DeInit = LogDnsLogDeInitCtx;
+
+    SCLogDebug("DNS log output initialized");
+
+    return output_ctx;
+}
+
+static void LogDnsLogDeInitCtx(OutputCtx *output_ctx)
+{
+    LogDnsFileCtx *dnslog_ctx = (LogDnsFileCtx *)output_ctx->data;
+    LogFileFreeCtx(dnslog_ctx->file_ctx);
+    SCFree(dnslog_ctx);
+    SCFree(output_ctx);
+}
diff --git a/src/log-dnslog.h b/src/log-dnslog.h
new file mode 100644 (file)
index 0000000..61ec900
--- /dev/null
@@ -0,0 +1,32 @@
+/* Copyright (C) 2007-2013 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#ifndef __LOG_DNSLOG_H__
+#define __LOG_DNSLOG_H__
+
+void TmModuleLogDnsLogRegister (void);
+void TmModuleLogDnsLogIPv4Register (void);
+void TmModuleLogDnsLogIPv6Register (void);
+OutputCtx *LogDnsLogInitCtx(ConfNode *);
+
+#endif /* __LOG_DNSLOG_H__ */
index 3f581917031a47226107ca0c0b3eea205dd4e28e..8a992d6c485a59e902ba5121d076988e0b26e518 100644 (file)
 
 #include "log-droplog.h"
 #include "log-httplog.h"
+#include "log-dnslog.h"
 #include "log-tlslog.h"
 #include "log-pcap.h"
 #include "log-file.h"
@@ -1593,6 +1594,13 @@ int main(int argc, char **argv)
     /* file log */
     TmModuleLogFileLogRegister();
     TmModuleLogFilestoreRegister();
+    /* dns log */
+    TmModuleLogDnsLogRegister();
+    /* cuda */
+#ifdef __SC_CUDA_SUPPORT__
+    TmModuleCudaMpmB2gRegister();
+    TmModuleCudaPacketBatcherRegister();
+#endif
     TmModuleDebugList();
 
     AppLayerHtpNeedFileInspection();
index 908938b3acbd7501ffe3b82b0bc4d138338685fa..1aba592431e0458039655668eea60b585421d2d5 100644 (file)
@@ -98,6 +98,9 @@ int TmModuleGetIDForTM(TmModule *tm)
     for (i = 0; i < TMM_SIZE; i++) {
         t = &tmm_modules[i];
 
+        if (t->name == NULL)
+            continue;
+
         if (strcmp(t->name, tm->name) == 0)
             return i;
     }
@@ -242,6 +245,7 @@ const char * TmModuleTmmIdToString(TmmId id)
         CASE_CODE (TMM_ALERTSYSLOG4);
         CASE_CODE (TMM_ALERTSYSLOG6);
         CASE_CODE (TMM_RESPONDREJECT);
+        CASE_CODE (TMM_LOGDNSLOG);
         CASE_CODE (TMM_LOGHTTPLOG);
         CASE_CODE (TMM_LOGHTTPLOG4);
         CASE_CODE (TMM_LOGHTTPLOG6);
index 2a59a7d73fca0bdb2d8851c2211606172e139c76..f5dc03f233232ecdaffac4e4f0151dfea5fdcfc8 100644 (file)
@@ -52,6 +52,7 @@ typedef enum {
     TMM_ALERTSYSLOG4,
     TMM_ALERTSYSLOG6,
     TMM_RESPONDREJECT,
+    TMM_LOGDNSLOG,
     TMM_LOGHTTPLOG,
     TMM_LOGHTTPLOG4,
     TMM_LOGHTTPLOG6,
index 25c184194d6f056d8f552bc77c92fcc5215e7d02..d500f74c1b3fe8bb29e29dc448fafcee22d50dd3 100644 (file)
@@ -272,6 +272,7 @@ const char * SCErrorToString(SCError err)
         CASE_CODE (SC_ERR_MAGIC_OPEN);
         CASE_CODE (SC_ERR_MAGIC_LOAD);
         CASE_CODE (SC_ERR_CUDA_BUFFER_ERROR);
+        CASE_CODE (SC_ERR_DNS_LOG_GENERIC);
     }
 
     return "UNKNOWN_ERROR";
index 6500751c9d6be42733930c916ff358fa60bc3cd5..d91adf096645632acbd78e6244f9db8e45292492 100644 (file)
@@ -261,6 +261,7 @@ typedef enum {
     SC_ERR_LIVE_RULE_SWAP,
     SC_WARN_UNCOMMON,
     SC_ERR_CUDA_BUFFER_ERROR,
+    SC_ERR_DNS_LOG_GENERIC,
 } SCError;
 
 const char *SCErrorToString(SCError);
index 891e7e4c6fe5359383af5b15fd226818bc4c7159..6ab27999652d4d1454038d83b1e555652fc2094d 100644 (file)
@@ -104,6 +104,13 @@ outputs:
       #extended: yes # Log extended information like fingerprint
       certs-log-dir: certs # directory to store the certificates files
 
+  # a line based log of DNS requests and/or replies (no alerts)
+  - dns-log:
+      enabled: yes
+      filename: dns.log
+      append: yes
+      #filetype: regular # 'regular', 'unix_stream' or 'unix_dgram'
+
   # a line based log to used with pcap file study.
   # this module is dedicated to offline pcap parsing (empty output
   # if used with another kind of input). It can interoperate with