From: Victor Julien Date: Fri, 22 Feb 2013 17:17:49 +0000 (+0100) Subject: DNS TCP and UDP parser and DNS response logger X-Git-Tag: suricata-2.0beta1~79 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8e01cba85da67941da0753094b0ab03257ed70aa;p=thirdparty%2Fsuricata.git DNS TCP and UDP parser and DNS response logger --- diff --git a/src/Makefile.am b/src/Makefile.am index 90d582c904..4f39391182 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 index 0000000000..d1156c0ac9 --- /dev/null +++ b/src/app-layer-dns-common.c @@ -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 + */ + +#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, "", 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 index 0000000000..595f0b2b08 --- /dev/null +++ b/src/app-layer-dns-common.h @@ -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 + */ + +#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 index 0000000000..82bbca15ef --- /dev/null +++ b/src/app-layer-dns-tcp.c @@ -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 + */ + +#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 index 0000000000..2f3b4ffcec --- /dev/null +++ b/src/app-layer-dns-tcp.h @@ -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 + */ + + +#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 index 0000000000..0db39c7e02 --- /dev/null +++ b/src/app-layer-dns-udp.c @@ -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 + */ + +#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 index 0000000000..a6ee12a820 --- /dev/null +++ b/src/app-layer-dns-udp.h @@ -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 + */ + +#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__ */ diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 20f3d9c432..d84162de6a 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -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); diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index e6a76cfdb6..7e8abeae23 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -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"; } diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index 2da4b7cc94..c065509be0 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -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 index 0000000000..25ee03efd6 --- /dev/null +++ b/src/log-dnslog.c @@ -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 + * + * 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, ""); + } + + 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, ""); + } 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 index 0000000000..61ec900a38 --- /dev/null +++ b/src/log-dnslog.h @@ -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 + */ + +#ifndef __LOG_DNSLOG_H__ +#define __LOG_DNSLOG_H__ + +void TmModuleLogDnsLogRegister (void); +void TmModuleLogDnsLogIPv4Register (void); +void TmModuleLogDnsLogIPv6Register (void); +OutputCtx *LogDnsLogInitCtx(ConfNode *); + +#endif /* __LOG_DNSLOG_H__ */ diff --git a/src/suricata.c b/src/suricata.c index 3f58191703..8a992d6c48 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -103,6 +103,7 @@ #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(); diff --git a/src/tm-modules.c b/src/tm-modules.c index 908938b3ac..1aba592431 100644 --- a/src/tm-modules.c +++ b/src/tm-modules.c @@ -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); diff --git a/src/tm-threads-common.h b/src/tm-threads-common.h index 2a59a7d73f..f5dc03f233 100644 --- a/src/tm-threads-common.h +++ b/src/tm-threads-common.h @@ -52,6 +52,7 @@ typedef enum { TMM_ALERTSYSLOG4, TMM_ALERTSYSLOG6, TMM_RESPONDREJECT, + TMM_LOGDNSLOG, TMM_LOGHTTPLOG, TMM_LOGHTTPLOG4, TMM_LOGHTTPLOG6, diff --git a/src/util-error.c b/src/util-error.c index 25c184194d..d500f74c1b 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -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"; diff --git a/src/util-error.h b/src/util-error.h index 6500751c9d..d91adf0966 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -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); diff --git a/suricata.yaml.in b/suricata.yaml.in index 891e7e4c6f..6ab2799965 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -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