X-Git-Url: http://git.samba.org/samba.git/?p=obnox%2Fcwrap%2Fresolv_wrapper.git;a=blobdiff_plain;f=src%2Fresolv_wrapper.c;h=8d0d67e4b1eb54c484ceb192d83efa792ef09d8a;hp=b687551337f822e32fe9befa82b02b19e23b6f80;hb=1e5333cb1ea789f1e5cc01433ec7cf01e4ec530f;hpb=efa9e872974733dbceb785238aff070f35b98d98 diff --git a/src/resolv_wrapper.c b/src/resolv_wrapper.c index b687551..8d0d67e 100644 --- a/src/resolv_wrapper.c +++ b/src/resolv_wrapper.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2014 Andreas Schneider + * Copyright (c) 2014 Jakub Hrozek * * All rights reserved. * @@ -60,6 +61,10 @@ #define DESTRUCTOR_ATTRIBUTE #endif /* HAVE_DESTRUCTOR_ATTRIBUTE */ +#ifndef RWRAP_DEFAULT_FAKE_TTL +#define RWRAP_DEFAULT_FAKE_TTL 600 +#endif /* RWRAP_DEFAULT_FAKE_TTL */ + enum rwrap_dbglvl_e { RWRAP_LOG_ERROR = 0, RWRAP_LOG_WARN, @@ -69,7 +74,7 @@ enum rwrap_dbglvl_e { #ifdef NDEBUG # define RWRAP_LOG(...) -#else +#else /* NDEBUG */ static void rwrap_log(enum rwrap_dbglvl_e dbglvl, const char *func, const char *format, ...) PRINTF_ATTRIBUTE(3, 4); # define RWRAP_LOG(dbglvl, ...) rwrap_log((dbglvl), __func__, __VA_ARGS__) @@ -120,6 +125,516 @@ static void rwrap_log(enum rwrap_dbglvl_e dbglvl, } #endif /* NDEBUG RWRAP_LOG */ +#ifndef SAFE_FREE +#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); (x)=NULL;} } while(0) +#endif + +#define NEXT_KEY(buf, key) do { \ + (key) = (buf) ? strpbrk((buf), " \t") : NULL; \ + if ((key) != NULL) { \ + (key)[0] = '\0'; \ + (key)++; \ + } \ + while ((key) != NULL \ + && (isblank((int)(key)[0]))) { \ + (key)++; \ + } \ +} while(0); + + +/* Prepares a fake header with a single response. Advances header_blob */ +static ssize_t rwrap_fake_header(uint8_t **header_blob, size_t remaining, + size_t rdata_size) +{ + uint8_t *hb; + HEADER *h; + int answers; + + /* If rdata_size is zero, the answer is empty */ + answers = rdata_size > 0 ? 1 : 0; + + if (remaining < NS_HFIXEDSZ) { + RWRAP_LOG(RWRAP_LOG_ERROR, "Buffer too small!\n"); + return -1; + } + + hb = *header_blob; + memset(hb, 0, NS_HFIXEDSZ); + + h = (HEADER *) hb; + h->id = res_randomid(); /* random query ID */ + h->qr = 1; /* response flag */ + h->rd = 1; /* recursion desired */ + h->ra = 1; /* resursion available */ + + h->qdcount = htons(1); /* no. of questions */ + h->ancount = htons(answers); /* no. of answers */ + + hb += NS_HFIXEDSZ; /* move past the header */ + *header_blob = hb; + + return NS_HFIXEDSZ; +} + +static ssize_t rwrap_fake_question(const char *question, + uint16_t type, + uint8_t **question_ptr, + size_t remaining) +{ + uint8_t *qb = *question_ptr; + int n; + + n = ns_name_compress(question, qb, remaining, NULL, NULL); + if (n < 0) { + RWRAP_LOG(RWRAP_LOG_ERROR, + "Failed to compress [%s]\n", question); + return -1; + } + + qb += n; + remaining -= n; + + if (remaining < 2 * sizeof(uint16_t)) { + RWRAP_LOG(RWRAP_LOG_ERROR, "Buffer too small!\n"); + return -1; + } + + NS_PUT16(type, qb); + NS_PUT16(ns_c_in, qb); + + *question_ptr = qb; + return n + 2 * sizeof(uint16_t); +} + +static ssize_t rwrap_fake_rdata_common(uint16_t type, + size_t rdata_size, + const char *key, + size_t remaining, + uint8_t **rdata_ptr) +{ + uint8_t *rd = *rdata_ptr; + ssize_t written = 0; + + written = ns_name_compress(key, rd, remaining, NULL, NULL); + if (written < 0) { + RWRAP_LOG(RWRAP_LOG_ERROR, + "Failed to compress [%s]\n", key); + return -1; + } + rd += written; + remaining -= written; + + if (remaining < 3 * sizeof(uint16_t) + sizeof(uint32_t)) { + RWRAP_LOG(RWRAP_LOG_ERROR, "Buffer too small\n"); + return -1; + } + + NS_PUT16(type, rd); + NS_PUT16(ns_c_in, rd); + NS_PUT32(RWRAP_DEFAULT_FAKE_TTL, rd); + NS_PUT16(rdata_size, rd); + + if (remaining < rdata_size) { + RWRAP_LOG(RWRAP_LOG_ERROR, "Buffer too small\n"); + return -1; + } + + *rdata_ptr = rd; + return written + 3 * sizeof(uint16_t) + sizeof(uint32_t); +} + +static ssize_t rwrap_fake_common(uint16_t type, + const char *question, + size_t rdata_size, + uint8_t **answer_ptr, + size_t anslen) +{ + uint8_t *a = *answer_ptr; + ssize_t written; + size_t remaining; + + remaining = anslen; + + written = rwrap_fake_header(&a, remaining, rdata_size); + if (written < 0) { + return -1; + } + remaining -= written; + + written = rwrap_fake_question(question, type, &a, remaining); + if (written < 0) { + return -1; + } + remaining -= written; + + /* rdata_size = 0 denotes an empty answer */ + if (rdata_size > 0) { + written = rwrap_fake_rdata_common(type, rdata_size, question, + remaining, &a); + if (written < 0) { + return -1; + } + } + + *answer_ptr = a; + return written; +} + +static int rwrap_fake_a(const char *key, + const char *value, + uint8_t *answer_ptr, + size_t anslen) +{ + uint8_t *a = answer_ptr; + struct in_addr a_rec; + int rc; + int ok; + + if (value == NULL) { + RWRAP_LOG(RWRAP_LOG_ERROR, "Malformed record, no value!\n"); + return -1; + } + + rc = rwrap_fake_common(ns_t_a, key, sizeof(a_rec), &a, anslen); + if (rc < 0) { + return -1; + } + + ok = inet_pton(AF_INET, value, &a_rec); + if (!ok) { + RWRAP_LOG(RWRAP_LOG_ERROR, + "Failed to convert [%s] to binary\n", value); + return -1; + } + memcpy(a, &a_rec, sizeof(struct in_addr)); + + return 0; +} + +static int rwrap_fake_aaaa(const char *key, + const char *value, + uint8_t *answer, + size_t anslen) +{ + uint8_t *a = answer; + struct in6_addr aaaa_rec; + int rc; + int ok; + + if (value == NULL) { + RWRAP_LOG(RWRAP_LOG_ERROR, "Malformed record, no value!\n"); + return -1; + } + + rc = rwrap_fake_common(ns_t_aaaa, key, sizeof(aaaa_rec), &a, anslen); + if (rc < 0) { + return -1; + } + + ok = inet_pton(AF_INET6, value, &aaaa_rec); + if (!ok) { + RWRAP_LOG(RWRAP_LOG_ERROR, + "Failed to convert [%s] to binary\n", value); + return -1; + } + memcpy(a, &aaaa_rec, sizeof(struct in6_addr)); + + return 0; +} + +/* + * Priority and weight can be omitted from the hosts file, but need to be part + * of the output + */ +#define DFL_SRV_PRIO 1 +#define DFL_SRV_WEIGHT 100 + +static int rwrap_fake_srv(const char *key, + const char *value, + uint8_t *answer, + size_t anslen) +{ + uint8_t *a = answer; + int rv; + size_t rdata_size; + char *str_prio; + char *str_weight; + char *str_port; + const char *hostname; + unsigned char hostname_compressed[MAXDNAME]; + ssize_t compressed_len; + + /* + * Parse the value into priority, weight, port and hostname + * and check the validity. + */ + hostname = value; + NEXT_KEY(hostname, str_port); + NEXT_KEY(str_port, str_prio); + NEXT_KEY(str_prio, str_weight); + if (str_port == NULL || hostname == NULL) { + RWRAP_LOG(RWRAP_LOG_ERROR, + "Malformed SRV entry [%s]\n", value); + return -1; + } + rdata_size = 3 * sizeof(uint16_t); + + /* Prepare the data to write */ + compressed_len = ns_name_compress(hostname, + hostname_compressed, MAXDNAME, + NULL, NULL); + if (compressed_len < 0) { + return -1; + } + rdata_size += compressed_len; + + rv = rwrap_fake_common(ns_t_srv, key, rdata_size, &a, anslen); + if (rv < 0) { + return -1; + } + + if (str_prio) { + NS_PUT16(atoi(str_prio), a); + } else { + NS_PUT16(DFL_SRV_PRIO, a); + } + if (str_weight) { + NS_PUT16(atoi(str_weight), a); + } else { + NS_PUT16(DFL_SRV_WEIGHT, a); + } + NS_PUT16(atoi(str_port), a); + memcpy(a, hostname_compressed, compressed_len); + + return 0; +} + +static int rwrap_fake_soa(const char *key, + const char *value, + uint8_t *answer, + size_t anslen) +{ + uint8_t *a = answer; + int rv; + const char *nameserver; + char *mailbox; + char *str_serial; + char *str_refresh; + char *str_retry; + char *str_expire; + char *str_minimum; + size_t rdata_size; + unsigned char nameser_compressed[MAXDNAME]; + ssize_t compressed_ns_len; + unsigned char mailbox_compressed[MAXDNAME]; + ssize_t compressed_mb_len; + + /* + * parse the value into nameserver, mailbox, serial, refresh, + * retry, expire, minimum and check the validity + */ + nameserver = value; + NEXT_KEY(nameserver, mailbox); + NEXT_KEY(mailbox, str_serial); + NEXT_KEY(str_serial, str_refresh); + NEXT_KEY(str_refresh, str_retry); + NEXT_KEY(str_retry, str_expire); + NEXT_KEY(str_expire, str_minimum); + if (nameserver == NULL || mailbox == NULL || str_serial == NULL || + str_refresh == NULL || str_retry == NULL || str_expire == NULL || + str_minimum == NULL) + { + RWRAP_LOG(RWRAP_LOG_ERROR, + "Malformed SOA entry [%s]\n", value); + return -1; + } + rdata_size = 5 * sizeof(uint16_t); + + compressed_ns_len = ns_name_compress(nameserver, nameser_compressed, + MAXDNAME, NULL, NULL); + if (compressed_ns_len < 0) { + return -1; + } + rdata_size += compressed_ns_len; + + compressed_mb_len = ns_name_compress(mailbox, mailbox_compressed, + MAXDNAME, NULL, NULL); + if (compressed_mb_len < 0) { + return -1; + } + rdata_size += compressed_mb_len; + + rv = rwrap_fake_common(ns_t_soa, key, rdata_size, &a, anslen); + if (rv < 0) { + return -1; + } + + memcpy(a, nameser_compressed, compressed_ns_len); + a += compressed_ns_len; + memcpy(a, mailbox_compressed, compressed_mb_len); + a += compressed_mb_len; + NS_PUT32(atoi(str_serial), a); + NS_PUT32(atoi(str_refresh), a); + NS_PUT32(atoi(str_retry), a); + NS_PUT32(atoi(str_expire), a); + NS_PUT32(atoi(str_minimum), a); + + return 0; +} + +static int rwrap_fake_cname(const char *key, + const char *value, + uint8_t *answer, + size_t anslen) +{ + uint8_t *a = answer; + int rv; + unsigned char hostname_compressed[MAXDNAME]; + ssize_t rdata_size; + + if (value == NULL) { + RWRAP_LOG(RWRAP_LOG_ERROR, "Malformed record, no value!\n"); + return -1; + } + + /* Prepare the data to write */ + rdata_size = ns_name_compress(value, + hostname_compressed, MAXDNAME, + NULL, NULL); + if (rdata_size < 0) { + return -1; + } + + rv = rwrap_fake_common(ns_t_cname, key, rdata_size, &a, anslen); + if (rv < 0) { + return -1; + } + + memcpy(a, hostname_compressed, rdata_size); + + return 0; +} + +static int rwrap_fake_empty_query(const char *key, + uint16_t type, + uint8_t *answer, + size_t anslen) +{ + int rc; + + rc = rwrap_fake_common(type, key, 0, &answer, anslen); + if (rc < 0) { + return -1; + } + + return 0; +} + +#define RESOLV_MATCH(line, name) \ + (strncmp(line, name, sizeof(name) - 1) == 0 && \ + (line[sizeof(name) - 1] == ' ' || \ + line[sizeof(name) - 1] == '\t')) + +#define TYPE_MATCH(type, ns_type, rec_type, str_type, key, query) \ + ((type) == (ns_type) && \ + (strncmp((rec_type), (str_type), sizeof(str_type)) == 0) && \ + (strcmp(key, query)) == 0) + + +/* Reads in a file in the following format: + * TYPE RDATA + * + * Malformed entried are silently skipped. + * Allocates answer buffer of size anslen that has to be freed after use. + */ +static int rwrap_res_fake_hosts(const char *hostfile, + const char *query, + int type, + unsigned char *answer, + size_t anslen) +{ + FILE *fp = NULL; + char buf[BUFSIZ]; + int rc = ENOENT; + char *key = NULL; + char *value = NULL; + + RWRAP_LOG(RWRAP_LOG_TRACE, + "Searching in fake hosts file %s\n", hostfile); + + fp = fopen(hostfile, "r"); + if (fp == NULL) { + RWRAP_LOG(RWRAP_LOG_ERROR, + "Opening %s failed: %s", + hostfile, strerror(errno)); + return -1; + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + char *rec_type; + char *q; + + rec_type = buf; + key = value = NULL; + + NEXT_KEY(rec_type, key); + NEXT_KEY(key, value); + + q = value; + while(q[0] != '\n' && q[0] != '\0') { + q++; + } + q[0] = '\0'; + + if (key == NULL || value == NULL) { + RWRAP_LOG(RWRAP_LOG_WARN, + "Malformed line: not enough parts, use \"rec_type key data\n" + "For example \"A cwrap.org 10.10.10.10\""); + continue; + } + + if (TYPE_MATCH(type, ns_t_a, rec_type, "A", key, query)) { + rc = rwrap_fake_a(key, value, answer, anslen); + break; + } else if (TYPE_MATCH(type, ns_t_aaaa, + rec_type, "AAAA", key, query)) { + rc = rwrap_fake_aaaa(key, value, answer, anslen); + break; + } else if (TYPE_MATCH(type, ns_t_srv, + rec_type, "SRV", key, query)) { + rc = rwrap_fake_srv(key, value, answer, anslen); + break; + } else if (TYPE_MATCH(type, ns_t_soa, + rec_type, "SOA", key, query)) { + rc = rwrap_fake_soa(key, value, answer, anslen); + break; + } else if (TYPE_MATCH(type, ns_t_cname, + rec_type, "CNAME", key, query)) { + rc = rwrap_fake_cname(key, value, answer, anslen); + break; + } + } + + switch (rc) { + case 0: + RWRAP_LOG(RWRAP_LOG_TRACE, + "Successfully faked answer for [%s]\n", query); + break; + case -1: + RWRAP_LOG(RWRAP_LOG_ERROR, + "Error faking answer for [%s]\n", query); + break; + case ENOENT: + RWRAP_LOG(RWRAP_LOG_TRACE, + "Record for [%s] not found\n", query); + rc = rwrap_fake_empty_query(key, type, answer, anslen); + break; + } + + fclose(fp); + return rc; +} + /********************************************************* * RWRAP LOADING LIBC FUNCTIONS *********************************************************/ @@ -317,7 +832,12 @@ static int libc_res_init(void) static int libc_res_ninit(struct __res_state *state) { #if defined(HAVE_RES_NINIT) + +#if defined(HAVE_RES_NINIT_IN_LIBRESOLV) + rwrap_load_lib_function(RWRAP_LIBRESOLV, res_ninit); +#else /* HAVE_RES_NINIT_IN_LIBRESOLV */ rwrap_load_lib_function(RWRAP_LIBC, res_ninit); +#endif /* HAVE_RES_NINIT_IN_LIBRESOLV */ return rwrap.fns.libc_res_ninit(state); #elif defined(HAVE___RES_NINIT) @@ -332,7 +852,12 @@ static int libc_res_ninit(struct __res_state *state) static void libc_res_nclose(struct __res_state *state) { #if defined(HAVE_RES_NCLOSE) + +#if defined(HAVE_RES_NCLOSE_IN_LIBRESOLV) + rwrap_load_lib_function(RWRAP_LIBRESOLV, res_nclose); +#else /* HAVE_RES_NCLOSE_IN_LIBRESOLV */ rwrap_load_lib_function(RWRAP_LIBC, res_nclose); +#endif /* HAVE_RES_NCLOSE_IN_LIBRESOLV */ rwrap.fns.libc_res_nclose(state); #elif defined(HAVE___RES_NCLOSE) @@ -408,11 +933,6 @@ static int libc_res_nsearch(struct __res_state *state, * RES_HELPER ***************************************************************************/ -#define RESOLV_MATCH(line, name) \ - (strncmp(line, name, sizeof(name) - 1) == 0 && \ - (line[sizeof(name) - 1] == ' ' || \ - line[sizeof(name) - 1] == '\t')) - static int rwrap_parse_resolv_conf(struct __res_state *state, const char *resolv_conf) { @@ -460,6 +980,7 @@ static int rwrap_parse_resolv_conf(struct __res_state *state, .sin_family = AF_INET, .sin_addr = a, .sin_port = htons(53), + .sin_zero = { 0 }, }; state->nscount++; @@ -474,6 +995,7 @@ static int rwrap_parse_resolv_conf(struct __res_state *state, sa6 = malloc(sizeof(*sa6)); if (sa6 == NULL) { + fclose(fp); return -1; } @@ -513,9 +1035,11 @@ static int rwrap_parse_resolv_conf(struct __res_state *state, RWRAP_LOG(RWRAP_LOG_ERROR, "Reading from %s failed", resolv_conf); + fclose(fp); return -1; } + fclose(fp); return 0; } @@ -594,6 +1118,16 @@ int __res_init(void) static void rwrap_res_nclose(struct __res_state *state) { +#ifdef HAVE_RESOLV_IPV6_NSADDRS + int i; + + if (state != NULL) { + for (i = 0; i < state->_u._ext.nscount; i++) { + SAFE_FREE(state->_u._ext.nsaddrs[i]); + state->_u._ext.nssocks[i] = 0; + } + } +#endif libc_res_nclose(state); } @@ -636,6 +1170,7 @@ static int rwrap_res_nquery(struct __res_state *state, int anslen) { int rc; + const char *fake_hosts; #ifndef NDEBUG int i; #endif @@ -654,7 +1189,13 @@ static int rwrap_res_nquery(struct __res_state *state, } #endif - rc = libc_res_nquery(state, dname, class, type, answer, anslen); + fake_hosts = getenv("RESOLV_WRAPPER_HOSTS"); + if (fake_hosts != NULL) { + rc = rwrap_res_fake_hosts(fake_hosts, dname, type, answer, anslen); + } else { + rc = libc_res_nquery(state, dname, class, type, answer, anslen); + } + RWRAP_LOG(RWRAP_LOG_TRACE, "The returned response length is: %d", @@ -738,6 +1279,7 @@ static int rwrap_res_nsearch(struct __res_state *state, int anslen) { int rc; + const char *fake_hosts; #ifndef NDEBUG int i; #endif @@ -756,7 +1298,12 @@ static int rwrap_res_nsearch(struct __res_state *state, } #endif - rc = libc_res_nsearch(state, dname, class, type, answer, anslen); + fake_hosts = getenv("RESOLV_WRAPPER_HOSTS"); + if (fake_hosts != NULL) { + rc = rwrap_res_fake_hosts(fake_hosts, dname, type, answer, anslen); + } else { + rc = libc_res_nsearch(state, dname, class, type, answer, anslen); + } RWRAP_LOG(RWRAP_LOG_TRACE, "The returned response length is: %d",