diff options
Diffstat (limited to 'contrib/impcap/dns_parser.c')
-rw-r--r-- | contrib/impcap/dns_parser.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/contrib/impcap/dns_parser.c b/contrib/impcap/dns_parser.c new file mode 100644 index 0000000..f9f4e68 --- /dev/null +++ b/contrib/impcap/dns_parser.c @@ -0,0 +1,372 @@ +/* dns_parser.c + * + * This file contains functions to parse DNS headers. + * + * File begun on 2018-11-13 + * + * Created by: + * - Kevin Guillemot (kevin.guillemot@advens.fr) + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" +#include "parsers.h" + + +/* List of RCodes defined in RFC6895 : https://tools.ietf.org/html/rfc6895 */ +static const char *dns_rcodes[] = { + "NoError", // 0 + "FormErr", // 1 + "ServFail", // 2 + "NXDomain", // 3 + "NotImp", // 4 + "Refused", // 5 + "YXDomain", // 6 + "YXRRSet", // 7 + "NXRRSet", // 8 + "NotAuth", // 9 + "NotZone", // 10 + "", // 11 - Reserved + "", // 12 - Reserved + "", // 13 - Reserved + "", // 14 - Reserved + "", // 15 - Reserved + "BADVERS|BADSIG", // 16 + "BADKEY", // 17 + "BADTIME", // 18 + "BADMODE", // 19 + "BADNAME", // 20 + "BADALG", // 21 + "BADTRUNC", // 22 + /* Reserved for private use */ + NULL +}; + +/* List of record types (maybe not complete) */ +static const char *dns_types[] = { + 0, + "A", // 1 + "NS", // 2 + "MD", // 3 + "MF", // 4 + "CNAME", // 5 + "SOA", // 6 + "MB", // 7 + "MG", // 8 + "MR", // 9 + "NULL", // 10 + "WKS", // 11 + "PTR", // 12 + "HINFO", // 13 + "MINFO", // 14 + "MX", // 15 + "TXT", // 16 + "RP", // 17 + "AFSDB", // 18 + "X25", // 19 + "ISDN", // 20 + "RT", // 21 + "NSAP", // 22 + "NSAP-PTR", // 23 + "SIG", // 24 + "KEY", // 25 + "PX", // 26 + "GPOS", // 27 + "AAAA", // 28 + "LOC", // 29 + "NXT", // 30 + "EID", // 31 + "NIMLOC", // 32 + "SRV", // 33 + "ATMA", // 34 + "NAPTR", // 35 + "KX", // 36 + "CERT", // 37 + "A6", // 38 + "DNAME", // 39 + "SINK", // 40 + "OPT", // 41 + "APL", // 42 + "DS", // 43 + "SSHFP", // 44 + "IPSECKEY", // 45 + "RRSIG", // 46 + "NSEC", // 47 + "DNSKEY", // 48 + "DHCID", // 49 + "NSEC3", // 50 + "NSEC3PARAM", // 51 + "TLSA", // 51 + "SMIMEA", // 52 + "Unassigned", // 53 + "HIP", // 53 + "NINFO", // 54 + "RKEY", // 55 + "TALINK", // 56 + "CDS", // 57 + "CDNSKEY", // 58 + "OPENPGPKEY", // 59 + "CSYNC", // 60 + "ZONEMD", // 61 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + "SPF", // 99 + "UINFO", // 100 + "UID", // 101 + "GID", // 102 + "UNSPEC", // 103 + "NID", // 104 + "L32", // 105 + "L64", // 106 + "LP", // 107 + "EUI48", // 108 + "EUI64", // 109 + /* Reserved for private use */ + NULL +}; +/* Part 2, since 249. To prevent useless large buffer in memory */ +static const char *dns_types2[] = { + "TKEY", + "TSIG", + "IXFR", + "AXFR", + "MAILB", + "MAILA", + "*", + "URI", + "CAA", + "AVC", + "DOA", + "AMTRELAY", + NULL +}; +/* Part 3, since 32768. To prevent useless large buffer in memory */ +static const char *dns_types3[] = { + "TA", + "DLV", + NULL +}; + + +/* This function takes an integer as parameter + * and returns the corresponding string type of DNS query + */ +static const char *get_type(uint16_t x) { + const char **types = NULL; + uint16_t len_types3 = (sizeof(dns_types3) / sizeof(char *)) - 1; + uint16_t len_types2 = (sizeof(dns_types2) / sizeof(char *)) - 1; + uint16_t len_types = (sizeof(dns_types) / sizeof(char *)) - 1; + if (x >= 32768 && x < 32768 + len_types3) { + types = dns_types3; + x -= 32768; + } + else if (x >= 249 && x < 249 + len_types2) { + types = dns_types2; + x -= 249; + } + else if (x > 0 && x < len_types) + types = dns_types; + else + return "UNKNOWN"; + if (types[x] != NULL) + return types[x]; + return "UNKNOWN"; +} + + +/* This function takes an integer as parameter + * and returns the corresponding string class of DNS query + */ +static const char *get_class(uint16_t x) { + switch (x) { + case 1: + return "IN"; + case 3: + return "CH"; + case 4: + return "HS"; + case 254: + return "QCLASS NONE"; + case 255: + return "QCLASS *"; + } + return "UNKNOWN"; +} + + +/* + * This function parses the bytes in the received packet to extract DNS metadata. + * + * its parameters are: + * - a pointer on the list of bytes representing the packet + * - the size of the list passed as first parameter + * - a pointer on a json_object, containing all the metadata recovered so far + * this is also where DNS metadata will be added + * + * This function returns a structure containing the data unprocessed by this parser + * or the ones after (as a list of bytes), and the length of this data. +*/ +data_ret_t *dns_parse(const uchar *packet, int pktSize, struct json_object *jparent) { + const uchar *packet_ptr = packet; + const uchar *end_packet = packet + pktSize; + DBGPRINTF("dns_parse\n"); + DBGPRINTF("packet size %d\n", pktSize); + + /* Union to prevent cast from uchar to smb_header_t */ + union { + unsigned short int *two_bytes; + const uchar *pckt; + } union_short_int; + + /* Get transaction id */ + union_short_int.pckt = packet_ptr; + unsigned short int transaction_id = ntohs(*(union_short_int.two_bytes)); + //DBGPRINTF("transaction_id = %02x \n", transaction_id); + union_short_int.pckt += 2; + + /* Get flags */ + unsigned short int flags = ntohs(*(union_short_int.two_bytes)); + //DBGPRINTF("flags = %02x \n", flags); + + /* Get response flag */ + unsigned short int response_flag = (flags >> 15) & 0b1; // Get the left bit + //DBGPRINTF("response_flag = %02x \n", response_flag); + + /* Get Opcode */ + unsigned short int opcode = (flags >> 11) & 0b1111; + //DBGPRINTF("opcode = %02x \n", opcode); + + /* Verify Z: reserved bit */ + unsigned short int reserved = (flags >> 6) & 0b1; + //DBGPRINTF("reserved = %02x \n", reserved); + /* Reserved bit MUST be 0 */ + if (reserved != 0) { + DBGPRINTF("DNS packet reserved bit (Z) is not 0, aborting message. \n"); + RETURN_DATA_AFTER(0) + } + + /* Get reply code : 4 last bits */ + unsigned short int reply_code = flags & 0b1111; + //DBGPRINTF("reply_code = %02x \n", reply_code); + + union_short_int.pckt += 2; + + /* Get QDCOUNT */ + unsigned short int query_count = ntohs(*(union_short_int.two_bytes)); + //DBGPRINTF("query_count = %02x \n", query_count); + union_short_int.pckt += 2; + + /* Get ANCOUNT */ + unsigned short int answer_count = ntohs(*(union_short_int.two_bytes)); + //DBGPRINTF("answer_count = %02x \n", answer_count); + union_short_int.pckt += 2; + + /* Get NSCOUNT */ + unsigned short int authority_count = ntohs(*(union_short_int.two_bytes)); + //DBGPRINTF("authority_count = %02x \n", authority_count); + union_short_int.pckt += 2; + + /* Get ARCOUNT */ + unsigned short int additionnal_count = ntohs(*(union_short_int.two_bytes)); + //DBGPRINTF("additionnal_count = %02x \n", additionnal_count); + union_short_int.pckt += 2; + packet_ptr = union_short_int.pckt; + + fjson_object *queries = NULL; + if ((queries = json_object_new_array()) == NULL) { + DBGPRINTF("impcap::dns_parser: Cannot create new json array. Stopping.\n"); + RETURN_DATA_AFTER(0) + } + + // For each query of query_count + int query_cpt = 0; + while (query_cpt < query_count && packet_ptr < end_packet) { + size_t query_size = strnlen((const char *)packet_ptr, (size_t)(end_packet - packet_ptr)); + // Check if query is valid (max 255 bytes, plus a '\0') + if (query_size >= 256) { + DBGPRINTF("impcap::dns_parser: Length of domain queried is > 255. Stopping.\n"); + break; + } + // Check if remaining data is enough to hold query + '\0' + 4 bytes (QTYPE and QCLASS fields) + if (query_size + 5 > (size_t)(end_packet - packet_ptr)) { + DBGPRINTF("impcap::dns_parser: packet size too small to parse query. Stopping.\n"); + break; + } + fjson_object *query = NULL; + if ((query = json_object_new_object()) == NULL) { + DBGPRINTF("impcap::dns_parser: Cannot create new json object. Stopping.\n"); + break; + } + char domain_query[256] = {0}; + uchar nb_char = *packet_ptr; + packet_ptr++; + size_t cpt = 0; + while (cpt + 1 < query_size) { + if (nb_char == 0) { + nb_char = *packet_ptr; + domain_query[cpt] = '.'; + } else { + domain_query[cpt] = (char)*packet_ptr; + nb_char--; + } + cpt++; + packet_ptr++; + } + domain_query[cpt] = '\0'; + if (cpt) + packet_ptr++; // pass the last \0, only if query was not empty + // DBGPRINTF("Requested domain : '%s' \n", domain_query); + + /* Register the name in dict */ + json_object_object_add(query, "qname", json_object_new_string(domain_query)); + /* Get QTYPE */ + union_short_int.pckt = packet_ptr; + unsigned short int qtype = ntohs(*(union_short_int.two_bytes)); + //DBGPRINTF("qtype = %02x \n", qtype); + json_object_object_add(query, "qtype", json_object_new_int((int)qtype)); + json_object_object_add(query, "type", json_object_new_string(get_type(qtype))); + union_short_int.pckt += 2; + /* Retrieve QCLASS */ + unsigned short int qclass = ntohs(*(union_short_int.two_bytes)); + //DBGPRINTF("qclass = %02x \n", qclass); + json_object_object_add(query, "qclass", json_object_new_int((int)qclass)); + json_object_object_add(query, "class", json_object_new_string(get_class(qclass))); + packet_ptr = union_short_int.pckt + 2; + /* Register the query in json array */ + json_object_array_add(queries, query); + query_cpt++; + } + + json_object_object_add(jparent, "DNS_transaction_id", json_object_new_int((int)transaction_id)); + + json_bool is_reponse = FALSE; + if (response_flag) + is_reponse = TRUE; + json_object_object_add(jparent, "DNS_response_flag", json_object_new_boolean(is_reponse)); + + json_object_object_add(jparent, "DNS_opcode", json_object_new_int(opcode)); + json_object_object_add(jparent, "DNS_rcode", json_object_new_int((int)reply_code)); + json_object_object_add(jparent, "DNS_error", json_object_new_string(dns_rcodes[reply_code])); + json_object_object_add(jparent, "DNS_QDCOUNT", json_object_new_int((int)query_count)); + json_object_object_add(jparent, "DNS_ANCOUNT", json_object_new_int((int)answer_count)); + json_object_object_add(jparent, "DNS_NSCOUNT", json_object_new_int((int)authority_count)); + json_object_object_add(jparent, "DNS_ARCOUNT", json_object_new_int((int)additionnal_count)); + json_object_object_add(jparent, "DNS_Names", queries); + + /* Packet has been successfully parsed, there still can be some responses left, but do not process them */ + RETURN_DATA_AFTER(0); +} |