|
|
|
/* Copyright (C) 2013-2014 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"
|
App layer API rewritten. The main files in question are:
app-layer.[ch], app-layer-detect-proto.[ch] and app-layer-parser.[ch].
Things addressed in this commit:
- Brings out a proper separation between protocol detection phase and the
parser phase.
- The dns app layer now is registered such that we don't use "dnstcp" and
"dnsudp" in the rules. A user who previously wrote a rule like this -
"alert dnstcp....." or
"alert dnsudp....."
would now have to use,
alert dns (ipproto:tcp;) or
alert udp (app-layer-protocol:dns;) or
alert ip (ipproto:udp; app-layer-protocol:dns;)
The same rules extend to other another such protocol, dcerpc.
- The app layer parser api now takes in the ipproto while registering
callbacks.
- The app inspection/detection engine also takes an ipproto.
- All app layer parser functions now take direction as STREAM_TOSERVER or
STREAM_TOCLIENT, as opposed to 0 or 1, which was taken by some of the
functions.
- FlowInitialize() and FlowRecycle() now resets proto to 0. This is
needed by unittests, which would try to clean the flow, and that would
call the api, AppLayerParserCleanupParserState(), which would try to
clean the app state, but the app layer now needs an ipproto to figure
out which api to internally call to clean the state, and if the ipproto
is 0, it would return without trying to clean the state.
- A lot of unittests are now updated where if they are using a flow and
they need to use the app layer, we would set a flow ipproto.
- The "app-layer" section in the yaml conf has also been updated as well.
12 years ago
|
|
|
#include "stream.h"
|
|
|
|
#include "app-layer-parser.h"
|
|
|
|
#include "app-layer-dns-common.h"
|
|
|
|
#ifdef DEBUG
|
|
|
|
#include "util-print.h"
|
|
|
|
#endif
|
|
|
|
#include "util-memcmp.h"
|
|
|
|
#include "util-atomic.h"
|
|
|
|
|
|
|
|
typedef struct DNSConfig_ {
|
|
|
|
uint32_t request_flood;
|
|
|
|
uint32_t state_memcap; /**< memcap in bytes per state */
|
|
|
|
uint64_t global_memcap; /**< memcap in bytes globally for parser */
|
|
|
|
} DNSConfig;
|
|
|
|
static DNSConfig dns_config;
|
|
|
|
|
|
|
|
void DNSConfigInit(void)
|
|
|
|
{
|
|
|
|
memset(&dns_config, 0x00, sizeof(dns_config));
|
|
|
|
}
|
|
|
|
|
|
|
|
void DNSConfigSetRequestFlood(uint32_t value)
|
|
|
|
{
|
|
|
|
dns_config.request_flood = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DNSConfigSetStateMemcap(uint32_t value)
|
|
|
|
{
|
|
|
|
dns_config.state_memcap = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
SC_ATOMIC_DECLARE(uint64_t, dns_memuse); /**< byte counter of current memuse */
|
|
|
|
SC_ATOMIC_DECLARE(uint64_t, dns_memcap_state); /**< counts number of 'rejects' */
|
|
|
|
SC_ATOMIC_DECLARE(uint64_t, dns_memcap_global); /**< counts number of 'rejects' */
|
|
|
|
|
|
|
|
void DNSConfigSetGlobalMemcap(uint64_t value)
|
|
|
|
{
|
|
|
|
dns_config.global_memcap = value;
|
|
|
|
|
|
|
|
SC_ATOMIC_INIT(dns_memuse);
|
|
|
|
SC_ATOMIC_INIT(dns_memcap_state);
|
|
|
|
SC_ATOMIC_INIT(dns_memcap_global);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DNSIncrMemcap(uint32_t size, DNSState *state)
|
|
|
|
{
|
|
|
|
if (state != NULL) {
|
|
|
|
state->memuse += size;
|
|
|
|
}
|
|
|
|
SC_ATOMIC_ADD(dns_memuse, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DNSDecrMemcap(uint32_t size, DNSState *state)
|
|
|
|
{
|
|
|
|
if (state != NULL) {
|
|
|
|
BUG_ON(size > state->memuse); /**< TODO remove later */
|
|
|
|
state->memuse -= size;
|
|
|
|
}
|
|
|
|
|
|
|
|
BUG_ON(size > SC_ATOMIC_GET(dns_memuse)); /**< TODO remove later */
|
|
|
|
(void)SC_ATOMIC_SUB(dns_memuse, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
int DNSCheckMemcap(uint32_t want, DNSState *state)
|
|
|
|
{
|
|
|
|
if (state != NULL) {
|
|
|
|
if (state->memuse + want > dns_config.state_memcap) {
|
|
|
|
SC_ATOMIC_ADD(dns_memcap_state, 1);
|
|
|
|
DNSSetEvent(state, DNS_DECODER_EVENT_STATE_MEMCAP_REACHED);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SC_ATOMIC_GET(dns_memuse) + (uint64_t)want > dns_config.global_memcap) {
|
|
|
|
SC_ATOMIC_ADD(dns_memcap_global, 1);
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DNSMemcapGetCounters(uint64_t *memuse, uint64_t *memcap_state,
|
|
|
|
uint64_t *memcap_global)
|
|
|
|
{
|
|
|
|
*memuse = SC_ATOMIC_GET(dns_memuse);
|
|
|
|
*memcap_state = SC_ATOMIC_GET(dns_memcap_state);
|
|
|
|
*memcap_global = SC_ATOMIC_GET(dns_memcap_global);
|
|
|
|
}
|
|
|
|
|
|
|
|
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, },
|
|
|
|
{ "FLOODED", DNS_DECODER_EVENT_FLOODED, },
|
|
|
|
{ "STATE_MEMCAP_REACHED", DNS_DECODER_EVENT_STATE_MEMCAP_REACHED, },
|
|
|
|
|
|
|
|
{ NULL, -1 },
|
|
|
|
};
|
|
|
|
|
|
|
|
int DNSStateGetEventInfo(const char *event_name,
|
|
|
|
int *event_id, AppLayerEventType *event_type)
|
|
|
|
{
|
|
|
|
*event_id = SCMapEnumNameToValue(event_name, dns_decoder_event_table);
|
|
|
|
if (*event_id == -1) {
|
|
|
|
SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%s\" not present in "
|
|
|
|
"dns's enum map table.", event_name);
|
|
|
|
/* this should be treated as fatal */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DNSAppLayerRegisterGetEventInfo(uint8_t ipproto, AppProto alproto)
|
|
|
|
{
|
App layer API rewritten. The main files in question are:
app-layer.[ch], app-layer-detect-proto.[ch] and app-layer-parser.[ch].
Things addressed in this commit:
- Brings out a proper separation between protocol detection phase and the
parser phase.
- The dns app layer now is registered such that we don't use "dnstcp" and
"dnsudp" in the rules. A user who previously wrote a rule like this -
"alert dnstcp....." or
"alert dnsudp....."
would now have to use,
alert dns (ipproto:tcp;) or
alert udp (app-layer-protocol:dns;) or
alert ip (ipproto:udp; app-layer-protocol:dns;)
The same rules extend to other another such protocol, dcerpc.
- The app layer parser api now takes in the ipproto while registering
callbacks.
- The app inspection/detection engine also takes an ipproto.
- All app layer parser functions now take direction as STREAM_TOSERVER or
STREAM_TOCLIENT, as opposed to 0 or 1, which was taken by some of the
functions.
- FlowInitialize() and FlowRecycle() now resets proto to 0. This is
needed by unittests, which would try to clean the flow, and that would
call the api, AppLayerParserCleanupParserState(), which would try to
clean the app state, but the app layer now needs an ipproto to figure
out which api to internally call to clean the state, and if the ipproto
is 0, it would return without trying to clean the state.
- A lot of unittests are now updated where if they are using a flow and
they need to use the app layer, we would set a flow ipproto.
- The "app-layer" section in the yaml conf has also been updated as well.
12 years ago
|
|
|
AppLayerParserRegisterGetEventInfo(ipproto, alproto, DNSStateGetEventInfo);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
AppLayerDecoderEvents *DNSGetEvents(void *state, uint64_t id)
|
|
|
|
{
|
|
|
|
DNSState *dns_state = (DNSState *)state;
|
|
|
|
DNSTransaction *tx;
|
|
|
|
|
|
|
|
if (dns_state->curr && dns_state->curr->tx_num == (id + 1)) {
|
|
|
|
return dns_state->curr->decoder_events;
|
|
|
|
}
|
|
|
|
|
|
|
|
TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
|
|
|
|
if (tx->tx_num == (id+1))
|
|
|
|
return tx->decoder_events;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int DNSHasEvents(void *state)
|
|
|
|
{
|
|
|
|
DNSState *dns_state = (DNSState *)state;
|
|
|
|
return (dns_state->events > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void *DNSGetTx(void *alstate, uint64_t tx_id)
|
|
|
|
{
|
|
|
|
DNSState *dns_state = (DNSState *)alstate;
|
|
|
|
DNSTransaction *tx = NULL;
|
|
|
|
|
|
|
|
/* fast track: try the current tx */
|
|
|
|
if (dns_state->curr && dns_state->curr->tx_num == tx_id + 1)
|
|
|
|
return dns_state->curr;
|
|
|
|
|
|
|
|
/* fast track:
|
|
|
|
* if the prev tx_id is equal to the stored tx ptr, we can
|
|
|
|
* use this shortcut to get to the next. */
|
|
|
|
if (dns_state->iter) {
|
|
|
|
if (tx_id == dns_state->iter->tx_num) {
|
|
|
|
tx = TAILQ_NEXT(dns_state->iter, next);
|
|
|
|
if (tx && tx->tx_num == tx_id + 1) {
|
|
|
|
dns_state->iter = tx;
|
|
|
|
return tx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* no luck with the fast tracks, do the full list walk */
|
|
|
|
TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
|
|
|
|
SCLogDebug("tx->tx_num %u, tx_id %"PRIu64, tx->tx_num, (tx_id+1));
|
|
|
|
if ((tx_id+1) != tx->tx_num)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
SCLogDebug("returning tx %p", tx);
|
|
|
|
dns_state->iter = tx;
|
|
|
|
return tx;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t DNSGetTxCnt(void *alstate)
|
|
|
|
{
|
|
|
|
DNSState *dns_state = (DNSState *)alstate;
|
|
|
|
return (uint64_t)dns_state->transaction_max;
|
|
|
|
}
|
|
|
|
|
|
|
|
int DNSGetAlstateProgress(void *tx, uint8_t direction)
|
|
|
|
{
|
|
|
|
DNSTransaction *dns_tx = (DNSTransaction *)tx;
|
|
|
|
if (direction == 1)
|
|
|
|
return dns_tx->replied|dns_tx->reply_lost;
|
|
|
|
else {
|
|
|
|
/* toserver/query is complete if we have stored a query */
|
|
|
|
return (TAILQ_FIRST(&dns_tx->query_list) != NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \brief get value for 'complete' status in DNS
|
|
|
|
*
|
|
|
|
* For DNS we use a simple bool.
|
|
|
|
*/
|
|
|
|
int DNSGetAlstateProgressCompletionStatus(uint8_t direction)
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DNSSetEvent(DNSState *s, uint8_t e)
|
|
|
|
{
|
|
|
|
if (s && s->curr) {
|
|
|
|
SCLogDebug("s->curr->decoder_events %p", s->curr->decoder_events);
|
|
|
|
AppLayerDecoderEventsSetEventRaw(&s->curr->decoder_events, e);
|
|
|
|
SCLogDebug("s->curr->decoder_events %p", s->curr->decoder_events);
|
|
|
|
s->events++;
|
|
|
|
} else {
|
|
|
|
SCLogDebug("couldn't set event %u", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \internal
|
|
|
|
* \brief Allocate a DNS TX
|
|
|
|
* \retval tx or NULL */
|
|
|
|
static DNSTransaction *DNSTransactionAlloc(DNSState *state, const uint16_t tx_id)
|
|
|
|
{
|
|
|
|
if (DNSCheckMemcap(sizeof(DNSTransaction), state) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
DNSTransaction *tx = SCMalloc(sizeof(DNSTransaction));
|
|
|
|
if (unlikely(tx == NULL))
|
|
|
|
return NULL;
|
|
|
|
DNSIncrMemcap(sizeof(DNSTransaction), state);
|
|
|
|
|
|
|
|
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 */
|
|
|
|
static void DNSTransactionFree(DNSTransaction *tx, DNSState *state)
|
|
|
|
{
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
DNSQueryEntry *q = NULL;
|
|
|
|
while ((q = TAILQ_FIRST(&tx->query_list))) {
|
|
|
|
TAILQ_REMOVE(&tx->query_list, q, next);
|
|
|
|
DNSDecrMemcap((sizeof(DNSQueryEntry) + q->len), state);
|
|
|
|
SCFree(q);
|
|
|
|
}
|
|
|
|
|
|
|
|
DNSAnswerEntry *a = NULL;
|
|
|
|
while ((a = TAILQ_FIRST(&tx->answer_list))) {
|
|
|
|
TAILQ_REMOVE(&tx->answer_list, a, next);
|
|
|
|
DNSDecrMemcap((sizeof(DNSAnswerEntry) + a->fqdn_len + a->data_len), state);
|
|
|
|
SCFree(a);
|
|
|
|
}
|
|
|
|
while ((a = TAILQ_FIRST(&tx->authority_list))) {
|
|
|
|
TAILQ_REMOVE(&tx->authority_list, a, next);
|
|
|
|
DNSDecrMemcap((sizeof(DNSAnswerEntry) + a->fqdn_len + a->data_len), state);
|
|
|
|
SCFree(a);
|
|
|
|
}
|
|
|
|
|
|
|
|
AppLayerDecoderEventsFreeEvents(&tx->decoder_events);
|
|
|
|
|
|
|
|
if (tx->de_state != NULL) {
|
|
|
|
DetectEngineStateFree(tx->de_state);
|
|
|
|
BUG_ON(state->tx_with_detect_state_cnt == 0);
|
|
|
|
state->tx_with_detect_state_cnt--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->iter == tx)
|
|
|
|
state->iter = NULL;
|
|
|
|
|
|
|
|
DNSDecrMemcap(sizeof(DNSTransaction), state);
|
|
|
|
SCFree(tx);
|
|
|
|
SCReturn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief dns transaction cleanup callback
|
|
|
|
*/
|
|
|
|
void DNSStateTransactionFree(void *state, uint64_t tx_id)
|
|
|
|
{
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
DNSState *dns_state = state;
|
|
|
|
DNSTransaction *tx = NULL;
|
|
|
|
|
|
|
|
SCLogDebug("state %p, id %"PRIu64, dns_state, tx_id);
|
|
|
|
|
|
|
|
TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
|
|
|
|
SCLogDebug("tx %p tx->tx_num %u, tx_id %"PRIu64, tx, tx->tx_num, (tx_id+1));
|
|
|
|
if ((tx_id+1) < tx->tx_num)
|
|
|
|
break;
|
|
|
|
else if ((tx_id+1) > tx->tx_num)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (tx == dns_state->curr)
|
|
|
|
dns_state->curr = NULL;
|
|
|
|
|
|
|
|
if (tx->decoder_events != NULL) {
|
|
|
|
if (tx->decoder_events->cnt <= dns_state->events)
|
|
|
|
dns_state->events -= tx->decoder_events->cnt;
|
|
|
|
else
|
|
|
|
dns_state->events = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
TAILQ_REMOVE(&dns_state->tx_list, tx, next);
|
|
|
|
DNSTransactionFree(tx, state);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
SCReturn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \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;
|
|
|
|
}
|
|
|
|
|
|
|
|
int DNSStateHasTxDetectState(void *alstate)
|
|
|
|
{
|
|
|
|
DNSState *state = (DNSState *)alstate;
|
|
|
|
return (state->tx_with_detect_state_cnt > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
DetectEngineState *DNSGetTxDetectState(void *vtx)
|
|
|
|
{
|
|
|
|
DNSTransaction *tx = (DNSTransaction *)vtx;
|
|
|
|
return tx->de_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
int DNSSetTxDetectState(void *alstate, void *vtx, DetectEngineState *s)
|
|
|
|
{
|
|
|
|
DNSState *state = (DNSState *)alstate;
|
|
|
|
DNSTransaction *tx = (DNSTransaction *)vtx;
|
|
|
|
state->tx_with_detect_state_cnt++;
|
|
|
|
tx->de_state = s;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *DNSStateAlloc(void)
|
|
|
|
{
|
|
|
|
void *s = SCMalloc(sizeof(DNSState));
|
|
|
|
if (unlikely(s == NULL))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
memset(s, 0, sizeof(DNSState));
|
|
|
|
|
|
|
|
DNSState *dns_state = (DNSState *)s;
|
|
|
|
|
|
|
|
DNSIncrMemcap(sizeof(DNSState), dns_state);
|
|
|
|
|
|
|
|
TAILQ_INIT(&dns_state->tx_list);
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DNSStateFree(void *s)
|
|
|
|
{
|
|
|
|
SCEnter();
|
|
|
|
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, dns_state);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dns_state->buffer != NULL) {
|
|
|
|
DNSDecrMemcap(0xffff, dns_state); /** TODO update if/once we alloc
|
|
|
|
* in a smarter way */
|
|
|
|
SCFree(dns_state->buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
BUG_ON(dns_state->tx_with_detect_state_cnt > 0);
|
|
|
|
|
|
|
|
DNSDecrMemcap(sizeof(DNSState), dns_state);
|
|
|
|
BUG_ON(dns_state->memuse > 0);
|
|
|
|
SCFree(s);
|
|
|
|
}
|
|
|
|
SCReturn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \brief Validation checks for DNS request header
|
|
|
|
*
|
|
|
|
* Will set decoder events if anomalies are found.
|
|
|
|
*
|
|
|
|
* \retval 0 ok
|
|
|
|
* \retval -1 error
|
|
|
|
*/
|
|
|
|
int DNSValidateRequestHeader(DNSState *dns_state, const DNSHeader *dns_header)
|
|
|
|
{
|
|
|
|
uint16_t flags = ntohs(dns_header->flags);
|
|
|
|
|
|
|
|
if ((flags & 0x8000) != 0) {
|
|
|
|
SCLogDebug("not a request 0x%04x", flags);
|
|
|
|
DNSSetEvent(dns_state, DNS_DECODER_EVENT_NOT_A_REQUEST);
|
|
|
|
goto bad_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((flags & 0x0040) != 0) {
|
|
|
|
SCLogDebug("Z flag not 0, 0x%04x", flags);
|
|
|
|
DNSSetEvent(dns_state, 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(DNSState *dns_state, const DNSHeader *dns_header)
|
|
|
|
{
|
|
|
|
uint16_t flags = ntohs(dns_header->flags);
|
|
|
|
|
|
|
|
if ((flags & 0x8000) == 0) {
|
|
|
|
SCLogDebug("not a response 0x%04x", flags);
|
|
|
|
DNSSetEvent(dns_state, DNS_DECODER_EVENT_NOT_A_RESPONSE);
|
|
|
|
goto bad_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((flags & 0x0040) != 0) {
|
|
|
|
SCLogDebug("Z flag not 0, 0x%04x", flags);
|
|
|
|
DNSSetEvent(dns_state, DNS_DECODER_EVENT_Z_FLAG_SET);
|
|
|
|
goto bad_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
bad_data:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \internal
|
|
|
|
* \brief check the query list to see if we already have this exact query
|
|
|
|
* \retval bool true or false
|
|
|
|
*/
|
|
|
|
static int QueryIsDuplicate(DNSTransaction *tx, const uint8_t *fqdn, const uint16_t fqdn_len,
|
|
|
|
const uint16_t type, const uint16_t class)
|
|
|
|
{
|
|
|
|
DNSQueryEntry *q = NULL;
|
|
|
|
|
|
|
|
TAILQ_FOREACH(q, &tx->query_list, next) {
|
|
|
|
uint8_t *qfqdn = (uint8_t *)q + sizeof(DNSQueryEntry);
|
|
|
|
|
|
|
|
if (q->len == fqdn_len && q->type == type &&
|
|
|
|
q->class == class &&
|
|
|
|
SCMemcmp(qfqdn, fqdn, fqdn_len) == 0) {
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
/* flood protection */
|
|
|
|
if (dns_state->givenup)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* find the tx and see if this is an exact duplicate */
|
|
|
|
DNSTransaction *tx = DNSTransactionFindByTxId(dns_state, tx_id);
|
|
|
|
if ((tx != NULL) && (QueryIsDuplicate(tx, fqdn, fqdn_len, type, class) == TRUE)) {
|
|
|
|
SCLogDebug("query is duplicate");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* see if the last tx is unreplied */
|
|
|
|
if (dns_state->curr != tx && dns_state->curr != NULL &&
|
|
|
|
dns_state->curr->replied == 0)
|
|
|
|
{
|
|
|
|
dns_state->curr->reply_lost = 1;
|
|
|
|
dns_state->unreplied_cnt++;
|
|
|
|
|
|
|
|
/* check flood limit */
|
|
|
|
if (dns_config.request_flood != 0 &&
|
|
|
|
dns_state->unreplied_cnt > dns_config.request_flood) {
|
|
|
|
DNSSetEvent(dns_state, DNS_DECODER_EVENT_FLOODED);
|
|
|
|
dns_state->givenup = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tx == NULL) {
|
|
|
|
tx = DNSTransactionAlloc(dns_state, tx_id);
|
|
|
|
if (tx == NULL)
|
|
|
|
return;
|
|
|
|
dns_state->transaction_max++;
|
|
|
|
SCLogDebug("dns_state->transaction_max updated to %"PRIu64, dns_state->transaction_max);
|
|
|
|
TAILQ_INSERT_TAIL(&dns_state->tx_list, tx, next);
|
|
|
|
dns_state->curr = tx;
|
|
|
|
tx->tx_num = dns_state->transaction_max;
|
|
|
|
SCLogDebug("new tx %u with internal id %u", tx->tx_id, tx->tx_num);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DNSCheckMemcap((sizeof(DNSQueryEntry) + fqdn_len), dns_state) < 0)
|
|
|
|
return;
|
|
|
|
DNSQueryEntry *q = SCMalloc(sizeof(DNSQueryEntry) + fqdn_len);
|
|
|
|
if (unlikely(q == NULL))
|
|
|
|
return;
|
|
|
|
DNSIncrMemcap((sizeof(DNSQueryEntry) + fqdn_len), dns_state);
|
|
|
|
|
|
|
|
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(dns_state, tx_id);
|
|
|
|
if (tx == NULL)
|
|
|
|
return;
|
|
|
|
TAILQ_INSERT_TAIL(&dns_state->tx_list, tx, next);
|
|
|
|
dns_state->curr = tx;
|
|
|
|
tx->tx_num = dns_state->transaction_max;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DNSCheckMemcap((sizeof(DNSAnswerEntry) + fqdn_len + data_len), dns_state) < 0)
|
|
|
|
return;
|
|
|
|
DNSAnswerEntry *q = SCMalloc(sizeof(DNSAnswerEntry) + fqdn_len + data_len);
|
|
|
|
if (unlikely(q == NULL))
|
|
|
|
return;
|
|
|
|
DNSIncrMemcap((sizeof(DNSAnswerEntry) + fqdn_len + data_len), dns_state);
|
|
|
|
|
|
|
|
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);
|
|
|
|
if (fqdn != NULL && fqdn_len > 0) {
|
|
|
|
memcpy(ptr, fqdn, fqdn_len);
|
|
|
|
ptr += fqdn_len;
|
|
|
|
}
|
|
|
|
if (data != NULL && data_len > 0) {
|
|
|
|
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;
|
|
|
|
|
|
|
|
/* reset unreplied counter */
|
|
|
|
dns_state->unreplied_cnt = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** \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) {
|
|
|
|
SCLogDebug("input buffer too small for domain of len %u", offset);
|
|
|
|
goto insufficient_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
int steps = 0;
|
|
|
|
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>", 6);
|
|
|
|
SCReturnUInt(6U);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (length != 0) {
|
|
|
|
int cnt = 0;
|
|
|
|
while (length & 0xc0) {
|
|
|
|
uint16_t offset = ((length & 0x3f) << 8) + *(qdata+1);
|
|
|
|
qdata = (const uint8_t *)input + offset;
|
|
|
|
|
|
|
|
if (input + input_len < qdata + 1) {
|
|
|
|
SCLogDebug("input buffer too small");
|
|
|
|
goto insufficient_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
length = *qdata;
|
|
|
|
SCLogDebug("qry length %u", length);
|
|
|
|
|
|
|
|
if (cnt++ == 100) {
|
|
|
|
SCLogDebug("too many pointer iterations, loop?");
|
|
|
|
goto bad_data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
qdata++;
|
|
|
|
|
|
|
|
if (length == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (input + input_len < qdata + length) {
|
|
|
|
SCLogDebug("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) {
|
|
|
|
SCLogDebug("input buffer too small for len field");
|
|
|
|
goto insufficient_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
length = *qdata;
|
|
|
|
SCLogDebug("qry length %u", length);
|
|
|
|
steps++;
|
|
|
|
if (steps >= 255)
|
|
|
|
goto bad_data;
|
|
|
|
}
|
|
|
|
if (fqdn_offset) {
|
|
|
|
fqdn_offset--;
|
|
|
|
}
|
|
|
|
//PrintRawDataFp(stdout, fqdn, fqdn_offset);
|
|
|
|
SCReturnUInt(fqdn_offset);
|
|
|
|
bad_data:
|
|
|
|
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) {
|
|
|
|
SCLogDebug("input buffer too small for data of len");
|
|
|
|
goto insufficient_data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sdata++;
|
|
|
|
if (input + input_len < sdata) {
|
|
|
|
SCLogDebug("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) {
|
|
|
|
SCLogDebug("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)) {
|
|
|
|
SCLogDebug("input buffer too small for DNSAnswerHeader");
|
|
|
|
goto insufficient_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
const DNSAnswerHeader *head = (DNSAnswerHeader *)data;
|
|
|
|
|
|
|
|
data += sizeof(DNSAnswerHeader);
|
|
|
|
|
|
|
|
SCLogDebug("head->len %u", ntohs(head->len));
|
|
|
|
|
|
|
|
if (input + input_len < data + ntohs(head->len)) {
|
|
|
|
SCLogDebug("input buffer too small for data of len %u", ntohs(head->len));
|
|
|
|
goto insufficient_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
SCLogDebug("TTL %u", ntohl(head->ttl));
|
|
|
|
|
|
|
|
switch (ntohs(head->type)) {
|
|
|
|
case DNS_RECORD_TYPE_A:
|
|
|
|
{
|
|
|
|
if (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 {
|
|
|
|
SCLogDebug("invalid length for A response data: %u", ntohs(head->len));
|
|
|
|
goto bad_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
data += ntohs(head->len);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DNS_RECORD_TYPE_AAAA:
|
|
|
|
{
|
|
|
|
if (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 {
|
|
|
|
SCLogDebug("invalid length for AAAA response data: %u", ntohs(head->len));
|
|
|
|
goto bad_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
data += ntohs(head->len);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DNS_RECORD_TYPE_MX:
|
|
|
|
case DNS_RECORD_TYPE_CNAME:
|
|
|
|
case DNS_RECORD_TYPE_PTR:
|
|
|
|
{
|
|
|
|
uint8_t name[DNS_MAX_SIZE];
|
|
|
|
uint16_t name_len = 0;
|
|
|
|
uint8_t skip = 0;
|
|
|
|
|
|
|
|
if (ntohs(head->type) == DNS_RECORD_TYPE_MX) {
|
|
|
|
// Skip the preference header
|
|
|
|
skip = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((name_len = DNSResponseGetNameByOffset(input, input_len,
|
|
|
|
data - input + skip, name, sizeof(name))) == 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),
|
|
|
|
name, name_len, ntohs(dns_header->tx_id));
|
|
|
|
|
|
|
|
data += ntohs(head->len);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DNS_RECORD_TYPE_NS:
|
|
|
|
case DNS_RECORD_TYPE_SOA:
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
SCLogDebug("getting pmail");
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
SCLogDebug("pmail_len %u", pmail_len);
|
|
|
|
//PrintRawDataFp(stdout, (uint8_t *)pmail, pmail_len);
|
|
|
|
|
|
|
|
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)) {
|
|
|
|
SCLogDebug("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;
|
|
|
|
}
|
|
|
|
case DNS_RECORD_TYPE_TXT:
|
|
|
|
{
|
|
|
|
uint16_t datalen = ntohs(head->len);
|
|
|
|
uint8_t txtlen = *data;
|
|
|
|
const uint8_t *tdata = data + 1;
|
|
|
|
|
|
|
|
do {
|
|
|
|
//PrintRawDataFp(stdout, (uint8_t*)tdata, txtlen);
|
|
|
|
|
|
|
|
if (txtlen > datalen)
|
|
|
|
goto bad_data;
|
|
|
|
|
|
|
|
DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
|
|
|
|
ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
|
|
|
|
(uint8_t*)tdata, (uint16_t)txtlen, ntohs(dns_header->tx_id));
|
|
|
|
|
|
|
|
datalen -= txtlen;
|
|
|
|
tdata += txtlen;
|
|
|
|
txtlen = *tdata;
|
|
|
|
|
|
|
|
tdata++;
|
|
|
|
datalen--;
|
|
|
|
|
|
|
|
SCLogDebug("datalen %u, txtlen %u", datalen, txtlen);
|
|
|
|
} while (datalen > 1);
|
|
|
|
|
|
|
|
data += ntohs(head->len);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: /* unsupported record */
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
bad_data:
|
|
|
|
insufficient_data:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DNSCreateTypeString(uint16_t type, char *str, size_t str_size)
|
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case DNS_RECORD_TYPE_A:
|
|
|
|
snprintf(str, str_size, "A");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_NS:
|
|
|
|
snprintf(str, str_size, "NS");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_AAAA:
|
|
|
|
snprintf(str, str_size, "AAAA");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_TXT:
|
|
|
|
snprintf(str, str_size, "TXT");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_CNAME:
|
|
|
|
snprintf(str, str_size, "CNAME");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_SOA:
|
|
|
|
snprintf(str, str_size, "SOA");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_MX:
|
|
|
|
snprintf(str, str_size, "MX");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_PTR:
|
|
|
|
snprintf(str, str_size, "PTR");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_ANY:
|
|
|
|
snprintf(str, str_size, "ANY");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_TKEY:
|
|
|
|
snprintf(str, str_size, "TKEY");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_TSIG:
|
|
|
|
snprintf(str, str_size, "TSIG");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_SRV:
|
|
|
|
snprintf(str, str_size, "SRV");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_NAPTR:
|
|
|
|
snprintf(str, str_size, "NAPTR");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_DS:
|
|
|
|
snprintf(str, str_size, "DS");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_RRSIG:
|
|
|
|
snprintf(str, str_size, "RRSIG");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_NSEC:
|
|
|
|
snprintf(str, str_size, "NSEC");
|
|
|
|
break;
|
|
|
|
case DNS_RECORD_TYPE_NSEC3:
|
|
|
|
snprintf(str, str_size, "NSEC3");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
snprintf(str, str_size, "%04x/%u", type, type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DNSCreateRcodeString(uint8_t rcode, char *str, size_t str_size)
|
|
|
|
{
|
|
|
|
switch (rcode) {
|
|
|
|
case DNS_RCODE_NOERROR:
|
|
|
|
snprintf(str, str_size, "NOERROR");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_FORMERR:
|
|
|
|
snprintf(str, str_size, "FORMERR");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_SERVFAIL:
|
|
|
|
snprintf(str, str_size, "SERVFAIL");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_NXDOMAIN:
|
|
|
|
snprintf(str, str_size, "NXDOMAIN");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_NOTIMP:
|
|
|
|
snprintf(str, str_size, "NOTIMP");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_REFUSED:
|
|
|
|
snprintf(str, str_size, "REFUSED");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_YXDOMAIN:
|
|
|
|
snprintf(str, str_size, "YXDOMAIN");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_YXRRSET:
|
|
|
|
snprintf(str, str_size, "YXRRSET");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_NXRRSET:
|
|
|
|
snprintf(str, str_size, "NXRRSET");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_NOTAUTH:
|
|
|
|
snprintf(str, str_size, "NOTAUTH");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_NOTZONE:
|
|
|
|
snprintf(str, str_size, "NOTZONE");
|
|
|
|
break;
|
|
|
|
/* these are the same, need more logic */
|
|
|
|
case DNS_RCODE_BADVERS:
|
|
|
|
//case DNS_RCODE_BADSIG:
|
|
|
|
snprintf(str, str_size, "BADVERS/BADSIG");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_BADKEY:
|
|
|
|
snprintf(str, str_size, "BADKEY");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_BADTIME:
|
|
|
|
snprintf(str, str_size, "BADTIME");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_BADMODE:
|
|
|
|
snprintf(str, str_size, "BADMODE");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_BADNAME:
|
|
|
|
snprintf(str, str_size, "BADNAME");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_BADALG:
|
|
|
|
snprintf(str, str_size, "BADALG");
|
|
|
|
break;
|
|
|
|
case DNS_RCODE_BADTRUNC:
|
|
|
|
snprintf(str, str_size, "BADTRUNC");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
SCLogDebug("could not map DNS rcode to name, bug!");
|
|
|
|
snprintf(str, str_size, "%04x/%u", rcode, rcode);
|
|
|
|
}
|
|
|
|
}
|