diff options
Diffstat (limited to 'src/resolv')
-rw-r--r-- | src/resolv/ares/ares_data.c | 140 | ||||
-rw-r--r-- | src/resolv/ares/ares_data.h | 68 | ||||
-rw-r--r-- | src/resolv/ares/ares_dns.h | 91 | ||||
-rw-r--r-- | src/resolv/ares/ares_parse_srv_reply.c | 183 | ||||
-rw-r--r-- | src/resolv/ares/ares_parse_srv_reply.h | 35 | ||||
-rw-r--r-- | src/resolv/ares/ares_parse_txt_reply.c | 204 | ||||
-rw-r--r-- | src/resolv/ares/ares_parse_txt_reply.h | 33 | ||||
-rw-r--r-- | src/resolv/async_resolv.c | 1062 | ||||
-rw-r--r-- | src/resolv/async_resolv.h | 95 |
9 files changed, 1911 insertions, 0 deletions
diff --git a/src/resolv/ares/ares_data.c b/src/resolv/ares/ares_data.c new file mode 100644 index 00000000..1cccaa55 --- /dev/null +++ b/src/resolv/ares/ares_data.c @@ -0,0 +1,140 @@ +/* $Id: ares_data.c,v 1.2 2009-11-20 09:06:33 yangtse Exp $ */ + +/* Copyright (C) 2009 by Daniel Stenberg + * + * Permission to use, copy, modify, and distribute this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation, and that the name of M.I.T. not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" + * without express or implied warranty. + */ + + +#include <stddef.h> +#include <stdlib.h> + +#include "ares.h" +#include "ares_data.h" + +/* +** ares_free_data() - c-ares external API function. +** +** This function must be used by the application to free data memory that +** has been internally allocated by some c-ares function and for which a +** pointer has already been returned to the calling application. The list +** of c-ares functions returning pointers that must be free'ed using this +** function is: +** +** ares_parse_srv_reply() +** ares_parse_txt_reply() +*/ + +void _ares_free_data(void *dataptr) +{ + struct ares_data *ptr; + + if (!dataptr) + return; + + ptr = (void *)((char *)dataptr - offsetof(struct ares_data, data)); + + if (ptr->mark != ARES_DATATYPE_MARK) + return; + + switch (ptr->type) + { + case ARES_DATATYPE_SRV_REPLY: + + if (ptr->data.srv_reply.next) + _ares_free_data(ptr->data.srv_reply.next); + if (ptr->data.srv_reply.host) + free(ptr->data.srv_reply.host); + break; + + case ARES_DATATYPE_TXT_REPLY: + + if (ptr->data.txt_reply.next) + _ares_free_data(ptr->data.txt_reply.next); + if (ptr->data.txt_reply.txt) + free(ptr->data.txt_reply.txt); + break; + + default: + return; + } + + free(ptr); +} + + +/* +** ares_malloc_data() - c-ares internal helper function. +** +** This function allocates memory for a c-ares private ares_data struct +** for the specified ares_datatype, initializes c-ares private fields +** and zero initializes those which later might be used from the public +** API. It returns an interior pointer which can be passed by c-ares +** functions to the calling application, and that must be free'ed using +** c-ares external API function ares_free_data(). +*/ + +void *_ares_malloc_data(ares_datatype type) +{ + struct ares_data *ptr; + + ptr = malloc(sizeof(struct ares_data)); + if (!ptr) + return NULL; + + switch (type) + { + case ARES_DATATYPE_SRV_REPLY: + ptr->data.srv_reply.next = NULL; + ptr->data.srv_reply.host = NULL; + ptr->data.srv_reply.priority = 0; + ptr->data.srv_reply.weight = 0; + ptr->data.srv_reply.port = 0; + break; + + case ARES_DATATYPE_TXT_REPLY: + ptr->data.txt_reply.next = NULL; + ptr->data.txt_reply.txt = NULL; + ptr->data.txt_reply.length = 0; + break; + + default: + free(ptr); + return NULL; + } + + ptr->mark = ARES_DATATYPE_MARK; + ptr->type = type; + + return &ptr->data; +} + + +/* +** ares_get_datatype() - c-ares internal helper function. +** +** This function returns the ares_datatype of the data stored in a +** private ares_data struct when given the public API pointer. +*/ + +ares_datatype ares_get_datatype(void * dataptr) +{ + struct ares_data *ptr; + + ptr = (void *)((char *)dataptr - offsetof(struct ares_data, data)); + + if (ptr->mark == ARES_DATATYPE_MARK) + return ptr->type; + + return ARES_DATATYPE_UNKNOWN; +} diff --git a/src/resolv/ares/ares_data.h b/src/resolv/ares/ares_data.h new file mode 100644 index 00000000..d3606314 --- /dev/null +++ b/src/resolv/ares/ares_data.h @@ -0,0 +1,68 @@ +/* $Id: ares_data.h,v 1.2 2009-11-23 12:03:33 yangtse Exp $ */ + +/* Copyright (C) 2009 by Daniel Stenberg + * + * Permission to use, copy, modify, and distribute this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation, and that the name of M.I.T. not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" + * without express or implied warranty. + */ + +#ifndef HAVE_ARES_DATA +#include "resolv/ares/ares_parse_txt_reply.h" +#include "resolv/ares/ares_parse_srv_reply.h" +#endif /* HAVE_ARES_DATA */ + +typedef enum { + ARES_DATATYPE_UNKNOWN = 1, /* unknown data type - introduced in 1.7.0 */ + ARES_DATATYPE_SRV_REPLY, /* struct ares_srv_reply - introduced in 1.7.0 */ + ARES_DATATYPE_TXT_REPLY, /* struct ares_txt_reply - introduced in 1.7.0 */ +#if 0 + ARES_DATATYPE_ADDR6TTL, /* struct ares_addrttl */ + ARES_DATATYPE_ADDRTTL, /* struct ares_addr6ttl */ + ARES_DATATYPE_HOSTENT, /* struct hostent */ + ARES_DATATYPE_OPTIONS, /* struct ares_options */ +#endif + ARES_DATATYPE_LAST /* not used - introduced in 1.7.0 */ +} ares_datatype; + +#define ARES_DATATYPE_MARK 0xbead + +/* + * ares_data struct definition is internal to c-ares and shall not + * be exposed by the public API in order to allow future changes + * and extensions to it without breaking ABI. This will be used + * internally by c-ares as the container of multiple types of data + * dynamically allocated for which a reference will be returned + * to the calling application. + * + * c-ares API functions returning a pointer to c-ares internally + * allocated data will actually be returning an interior pointer + * into this ares_data struct. + * + * All this is 'invisible' to the calling application, the only + * requirement is that this kind of data must be free'ed by the + * calling application using ares_free_data() with the pointer + * it has received from a previous c-ares function call. + */ + +struct ares_data { + ares_datatype type; /* Actual data type identifier. */ + unsigned int mark; /* Private ares_data signature. */ + union { + struct ares_txt_reply txt_reply; + struct ares_srv_reply srv_reply; + } data; +}; + +void *_ares_malloc_data(ares_datatype type); +void _ares_free_data(void *dataptr); + +ares_datatype ares_get_datatype(void * dataptr); diff --git a/src/resolv/ares/ares_dns.h b/src/resolv/ares/ares_dns.h new file mode 100644 index 00000000..c0a9dda6 --- /dev/null +++ b/src/resolv/ares/ares_dns.h @@ -0,0 +1,91 @@ +/* $Id: ares_dns.h,v 1.8 2007-02-16 14:22:08 yangtse Exp $ */ + +/* Copyright 1998 by the Massachusetts Institute of Technology. + * + * Permission to use, copy, modify, and distribute this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation, and that the name of M.I.T. not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" + * without express or implied warranty. + */ + +#ifndef ARES__DNS_H +#define ARES__DNS_H + +#define DNS__16BIT(p) (((p)[0] << 8) | (p)[1]) +#define DNS__32BIT(p) (((p)[0] << 24) | ((p)[1] << 16) | \ + ((p)[2] << 8) | (p)[3]) + +#define DNS__SET16BIT(p, v) (((p)[0] = (unsigned char)(((v) >> 8) & 0xff)), \ + ((p)[1] = (unsigned char)((v) & 0xff))) +#define DNS__SET32BIT(p, v) (((p)[0] = (unsigned char)(((v) >> 24) & 0xff)), \ + ((p)[1] = (unsigned char)(((v) >> 16) & 0xff)), \ + ((p)[2] = (unsigned char)(((v) >> 8) & 0xff)), \ + ((p)[3] = (unsigned char)((v) & 0xff))) + +#if 0 +/* we cannot use this approach on systems where we can't access 16/32 bit + data on un-aligned addresses */ +#define DNS__16BIT(p) ntohs(*(unsigned short*)(p)) +#define DNS__32BIT(p) ntohl(*(unsigned long*)(p)) +#define DNS__SET16BIT(p, v) *(unsigned short*)(p) = htons(v) +#define DNS__SET32BIT(p, v) *(unsigned long*)(p) = htonl(v) +#endif + +/* Macros for parsing a DNS header */ +#define DNS_HEADER_QID(h) DNS__16BIT(h) +#define DNS_HEADER_QR(h) (((h)[2] >> 7) & 0x1) +#define DNS_HEADER_OPCODE(h) (((h)[2] >> 3) & 0xf) +#define DNS_HEADER_AA(h) (((h)[2] >> 2) & 0x1) +#define DNS_HEADER_TC(h) (((h)[2] >> 1) & 0x1) +#define DNS_HEADER_RD(h) ((h)[2] & 0x1) +#define DNS_HEADER_RA(h) (((h)[3] >> 7) & 0x1) +#define DNS_HEADER_Z(h) (((h)[3] >> 4) & 0x7) +#define DNS_HEADER_RCODE(h) ((h)[3] & 0xf) +#define DNS_HEADER_QDCOUNT(h) DNS__16BIT((h) + 4) +#define DNS_HEADER_ANCOUNT(h) DNS__16BIT((h) + 6) +#define DNS_HEADER_NSCOUNT(h) DNS__16BIT((h) + 8) +#define DNS_HEADER_ARCOUNT(h) DNS__16BIT((h) + 10) + +/* Macros for constructing a DNS header */ +#define DNS_HEADER_SET_QID(h, v) DNS__SET16BIT(h, v) +#define DNS_HEADER_SET_QR(h, v) ((h)[2] |= (unsigned char)(((v) & 0x1) << 7)) +#define DNS_HEADER_SET_OPCODE(h, v) ((h)[2] |= (unsigned char)(((v) & 0xf) << 3)) +#define DNS_HEADER_SET_AA(h, v) ((h)[2] |= (unsigned char)(((v) & 0x1) << 2)) +#define DNS_HEADER_SET_TC(h, v) ((h)[2] |= (unsigned char)(((v) & 0x1) << 1)) +#define DNS_HEADER_SET_RD(h, v) ((h)[2] |= (unsigned char)((v) & 0x1)) +#define DNS_HEADER_SET_RA(h, v) ((h)[3] |= (unsigned char)(((v) & 0x1) << 7)) +#define DNS_HEADER_SET_Z(h, v) ((h)[3] |= (unsigned char)(((v) & 0x7) << 4)) +#define DNS_HEADER_SET_RCODE(h, v) ((h)[3] |= (unsigned char)((v) & 0xf)) +#define DNS_HEADER_SET_QDCOUNT(h, v) DNS__SET16BIT((h) + 4, v) +#define DNS_HEADER_SET_ANCOUNT(h, v) DNS__SET16BIT((h) + 6, v) +#define DNS_HEADER_SET_NSCOUNT(h, v) DNS__SET16BIT((h) + 8, v) +#define DNS_HEADER_SET_ARCOUNT(h, v) DNS__SET16BIT((h) + 10, v) + +/* Macros for parsing the fixed part of a DNS question */ +#define DNS_QUESTION_TYPE(q) DNS__16BIT(q) +#define DNS_QUESTION_CLASS(q) DNS__16BIT((q) + 2) + +/* Macros for constructing the fixed part of a DNS question */ +#define DNS_QUESTION_SET_TYPE(q, v) DNS__SET16BIT(q, v) +#define DNS_QUESTION_SET_CLASS(q, v) DNS__SET16BIT((q) + 2, v) + +/* Macros for parsing the fixed part of a DNS resource record */ +#define DNS_RR_TYPE(r) DNS__16BIT(r) +#define DNS_RR_CLASS(r) DNS__16BIT((r) + 2) +#define DNS_RR_TTL(r) DNS__32BIT((r) + 4) +#define DNS_RR_LEN(r) DNS__16BIT((r) + 8) + +/* Macros for constructing the fixed part of a DNS resource record */ +#define DNS_RR_SET_TYPE(r) DNS__SET16BIT(r, v) +#define DNS_RR_SET_CLASS(r) DNS__SET16BIT((r) + 2, v) +#define DNS_RR_SET_TTL(r) DNS__SET32BIT((r) + 4, v) +#define DNS_RR_SET_LEN(r) DNS__SET16BIT((r) + 8, v) + +#endif /* ARES__DNS_H */ diff --git a/src/resolv/ares/ares_parse_srv_reply.c b/src/resolv/ares/ares_parse_srv_reply.c new file mode 100644 index 00000000..086c4dba --- /dev/null +++ b/src/resolv/ares/ares_parse_srv_reply.c @@ -0,0 +1,183 @@ +/* + SSSD + + Async resolver - SRV records parsing + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) Red Hat, Inc 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 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 + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * This code is based on other c-ares parsing licensed as follows: + + * Copyright 1998 by the Massachusetts Institute of Technology. + * + * Permission to use, copy, modify, and distribute this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation, and that the name of M.I.T. not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" + * without express or implied warranty. + */ + + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <arpa/nameser.h> +#include <stdlib.h> +#include <string.h> +#include "ares.h" +/* this drags in some private macros c-ares uses */ +#include "ares_dns.h" +#include "ares_data.h" + +#include "ares_parse_srv_reply.h" + +int _ares_parse_srv_reply (const unsigned char *abuf, int alen, + struct ares_srv_reply **srv_out) +{ + unsigned int qdcount, ancount, i; + const unsigned char *aptr, *vptr; + int status, rr_type, rr_class, rr_len; + long len; + char *hostname = NULL, *rr_name = NULL; + struct ares_srv_reply *srv_head = NULL; + struct ares_srv_reply *srv_last = NULL; + struct ares_srv_reply *srv_curr; + + /* Set *srv_out to NULL for all failure cases. */ + *srv_out = NULL; + + /* Give up if abuf doesn't have room for a header. */ + if (alen < HFIXEDSZ) + return ARES_EBADRESP; + + /* Fetch the question and answer count from the header. */ + qdcount = DNS_HEADER_QDCOUNT (abuf); + ancount = DNS_HEADER_ANCOUNT (abuf); + if (qdcount != 1) + return ARES_EBADRESP; + if (ancount == 0) + return ARES_ENODATA; + + /* Expand the name from the question, and skip past the question. */ + aptr = abuf + HFIXEDSZ; + status = ares_expand_name (aptr, abuf, alen, &hostname, &len); + if (status != ARES_SUCCESS) + return status; + + if (aptr + len + QFIXEDSZ > abuf + alen) + { + free (hostname); + return ARES_EBADRESP; + } + aptr += len + QFIXEDSZ; + + /* Examine each answer resource record (RR) in turn. */ + for (i = 0; i < (int) ancount; i++) + { + /* Decode the RR up to the data field. */ + status = ares_expand_name (aptr, abuf, alen, &rr_name, &len); + if (status != ARES_SUCCESS) + { + break; + } + aptr += len; + if (aptr + RRFIXEDSZ > abuf + alen) + { + status = ARES_EBADRESP; + break; + } + rr_type = DNS_RR_TYPE (aptr); + rr_class = DNS_RR_CLASS (aptr); + rr_len = DNS_RR_LEN (aptr); + aptr += RRFIXEDSZ; + + /* Check if we are really looking at a SRV record */ + if (rr_class == C_IN && rr_type == T_SRV) + { + /* parse the SRV record itself */ + if (rr_len < 6) + { + status = ARES_EBADRESP; + break; + } + + /* Allocate storage for this SRV answer appending it to the list */ + srv_curr = _ares_malloc_data(ARES_DATATYPE_SRV_REPLY); + if (!srv_curr) + { + status = ARES_ENOMEM; + break; + } + if (srv_last) + { + srv_last->next = srv_curr; + } + else + { + srv_head = srv_curr; + } + srv_last = srv_curr; + + vptr = aptr; + srv_curr->priority = ntohs (*((const unsigned short *)vptr)); + vptr += sizeof(const unsigned short); + srv_curr->weight = ntohs (*((const unsigned short *)vptr)); + vptr += sizeof(const unsigned short); + srv_curr->port = ntohs (*((const unsigned short *)vptr)); + vptr += sizeof(const unsigned short); + + status = ares_expand_name (vptr, abuf, alen, &srv_curr->host, &len); + if (status != ARES_SUCCESS) + break; + } + + /* Don't lose memory in the next iteration */ + free(rr_name); + rr_name = NULL; + + /* Move on to the next record */ + aptr += rr_len; + } + + if (hostname) + free (hostname); + if (rr_name) + free (rr_name); + + /* clean up on error */ + if (status != ARES_SUCCESS) + { + if (srv_head) + _ares_free_data (srv_head); + return status; + } + + /* everything looks fine, return the data */ + *srv_out = srv_head; + + return ARES_SUCCESS; +} diff --git a/src/resolv/ares/ares_parse_srv_reply.h b/src/resolv/ares/ares_parse_srv_reply.h new file mode 100644 index 00000000..29c6e08d --- /dev/null +++ b/src/resolv/ares/ares_parse_srv_reply.h @@ -0,0 +1,35 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 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 + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ARES_PARSE_SRV_REPLY_H__ +#define __ARES_PARSE_SRV_REPLY_H__ + +struct ares_srv_reply { + struct ares_srv_reply *next; + char *host; + unsigned short priority; + unsigned short weight; + unsigned short port; +}; + +int _ares_parse_srv_reply (const unsigned char *abuf, int alen, + struct ares_srv_reply **srv_out); + +#endif /* __ARES_PARSE_SRV_REPLY_H__ */ diff --git a/src/resolv/ares/ares_parse_txt_reply.c b/src/resolv/ares/ares_parse_txt_reply.c new file mode 100644 index 00000000..d710e8f9 --- /dev/null +++ b/src/resolv/ares/ares_parse_txt_reply.c @@ -0,0 +1,204 @@ +/* + SSSD + + Async resolver - TXT records parsing + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) Red Hat, Inc 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 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 + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * This code is based on other c-ares parsing licensed as follows: + + * Copyright 1998 by the Massachusetts Institute of Technology. + * + * Permission to use, copy, modify, and distribute this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation, and that the name of M.I.T. not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" + * without express or implied warranty. + */ + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <arpa/nameser.h> +#include <stdlib.h> +#include <string.h> +#include "ares.h" +/* this drags in some private macros c-ares uses */ +#include "ares_dns.h" +#include "ares_data.h" + +#include "ares_parse_txt_reply.h" + +int _ares_parse_txt_reply (const unsigned char *abuf, int alen, + struct ares_txt_reply **txt_out) +{ + size_t substr_len, str_len; + unsigned int qdcount, ancount, i; + const unsigned char *aptr; + const unsigned char *strptr; + int status, rr_type, rr_class, rr_len; + long len; + char *hostname = NULL, *rr_name = NULL; + struct ares_txt_reply *txt_head = NULL; + struct ares_txt_reply *txt_last = NULL; + struct ares_txt_reply *txt_curr; + + /* Set *txt_out to NULL for all failure cases. */ + *txt_out = NULL; + + /* Give up if abuf doesn't have room for a header. */ + if (alen < HFIXEDSZ) + return ARES_EBADRESP; + + /* Fetch the question and answer count from the header. */ + qdcount = DNS_HEADER_QDCOUNT(abuf); + ancount = DNS_HEADER_ANCOUNT(abuf); + if (qdcount != 1) + return ARES_EBADRESP; + if (ancount == 0) + return ARES_ENODATA; + + /* Expand the name from the question, and skip past the question. */ + aptr = abuf + HFIXEDSZ; + status = ares_expand_name(aptr, abuf, alen, &hostname, &len); + if (status != ARES_SUCCESS) + return status; + + if (aptr + len + QFIXEDSZ > abuf + alen) + { + free (hostname); + return ARES_EBADRESP; + } + aptr += len + QFIXEDSZ; + + /* Examine each answer resource record (RR) in turn. */ + for (i = 0; i < (int) ancount; i++) + { + /* Decode the RR up to the data field. */ + status = ares_expand_name(aptr, abuf, alen, &rr_name, &len); + if (status != ARES_SUCCESS) + { + break; + } + aptr += len; + if (aptr + RRFIXEDSZ > abuf + alen) + { + status = ARES_EBADRESP; + break; + } + rr_type = DNS_RR_TYPE(aptr); + rr_class = DNS_RR_CLASS(aptr); + rr_len = DNS_RR_LEN(aptr); + aptr += RRFIXEDSZ; + + /* Check if we are really looking at a TXT record */ + if (rr_class == C_IN && rr_type == T_TXT) + { + /* Allocate storage for this TXT answer appending it to the list */ + txt_curr = _ares_malloc_data(ARES_DATATYPE_TXT_REPLY); + if (!txt_curr) + { + status = ARES_ENOMEM; + break; + } + if (txt_last) + { + txt_last->next = txt_curr; + } + else + { + txt_head = txt_curr; + } + txt_last = txt_curr; + + /* + * There may be multiple substrings in a single TXT record. Each + * substring may be up to 255 characters in length, with a + * "length byte" indicating the size of the substring payload. + * RDATA contains both the length-bytes and payloads of all + * substrings contained therein. + */ + + /* Compute total length to allow a single memory allocation */ + strptr = aptr; + while (strptr < (aptr + rr_len)) + { + substr_len = (unsigned char)*strptr; + txt_curr->length += substr_len; + strptr += substr_len + 1; + } + + /* Including null byte */ + txt_curr->txt = malloc (txt_curr->length + 1); + if (txt_curr->txt == NULL) + { + status = ARES_ENOMEM; + break; + } + + /* Step through the list of substrings, concatenating them */ + str_len = 0; + strptr = aptr; + while (strptr < (aptr + rr_len)) + { + substr_len = (unsigned char)*strptr; + strptr++; + memcpy ((char *) txt_curr->txt + str_len, strptr, substr_len); + str_len += substr_len; + strptr += substr_len; + } + /* Make sure we NULL-terminate */ + *((char *) txt_curr->txt + txt_curr->length) = '\0'; + } + + /* Don't lose memory in the next iteration */ + free(rr_name); + rr_name = NULL; + + /* Move on to the next record */ + aptr += rr_len; + } + + if (hostname) + free (hostname); + if (rr_name) + free (rr_name); + + /* clean up on error */ + if (status != ARES_SUCCESS) + { + if (txt_head) + _ares_free_data (txt_head); + return status; + } + + /* everything looks fine, return the data */ + *txt_out = txt_head; + + return ARES_SUCCESS; +} diff --git a/src/resolv/ares/ares_parse_txt_reply.h b/src/resolv/ares/ares_parse_txt_reply.h new file mode 100644 index 00000000..216e2c0d --- /dev/null +++ b/src/resolv/ares/ares_parse_txt_reply.h @@ -0,0 +1,33 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 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 + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ARES_PARSE_TXT_REPLY_H__ +#define __ARES_PARSE_TXT_REPLY_H__ + +struct ares_txt_reply { + struct ares_txt_reply *next; + unsigned char *txt; + size_t length; /* length excludes null termination */ +}; + +int _ares_parse_txt_reply(const unsigned char* abuf, int alen, + struct ares_txt_reply **txt_out); + +#endif /* __ARES_PARSE_TXT_REPLY_H__ */ diff --git a/src/resolv/async_resolv.c b/src/resolv/async_resolv.c new file mode 100644 index 00000000..70d8d11e --- /dev/null +++ b/src/resolv/async_resolv.c @@ -0,0 +1,1062 @@ +/* + SSSD + + Async resolver + + Authors: + Martin Nagy <mnagy@redhat.com> + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) Red Hat, Inc 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 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 + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <sys/select.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> + +#include <ares.h> +#include <talloc.h> +#include <tevent.h> + +#include <errno.h> +#include <netdb.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "resolv/async_resolv.h" +#include "util/dlinklist.h" +#include "util/util.h" + +#ifndef HAVE_ARES_DATA +#define ares_parse_srv_reply(abuf, alen, srv_out) \ + _ares_parse_srv_reply(abuf, alen, srv_out) +#define ares_parse_txt_reply(abuf, alen, txt_out) \ + _ares_parse_txt_reply(abuf, alen, txt_out) +#define ares_free_data(dataptr) \ + _ares_free_data(dataptr) +#define ares_malloc_data(data) \ + _ares_malloc_data(data) +#endif /* HAVE_ARES_DATA */ + +struct fd_watch { + struct fd_watch *prev; + struct fd_watch *next; + + int fd; + struct resolv_ctx *ctx; + struct tevent_fd *fde; +}; + +struct resolv_ctx { + /* Contexts are linked so we can keep track of them and re-create + * the ares channels in all of them at once if we need to. */ + struct resolv_ctx *prev; + struct resolv_ctx *next; + + struct tevent_context *ev_ctx; + ares_channel channel; + + /* List of file descriptors that are watched by tevent. */ + struct fd_watch *fds; + + /* Time in milliseconds before canceling a DNS request */ + int timeout; + + /* The timeout watcher periodically calls ares_process_fd() to check + * if our pending requests didn't timeout. */ + int pending_requests; + struct tevent_timer *timeout_watcher; +}; + +struct resolv_ctx *context_list; + +static int +return_code(int ares_code) +{ + switch (ares_code) { + case ARES_SUCCESS: + return EOK; + case ARES_ENOMEM: + return ENOMEM; + case ARES_EFILE: + default: + return EIO; + } +} + +const char * +resolv_strerror(int ares_code) +{ + return ares_strerror(ares_code); +} + +static int +fd_watch_destructor(struct fd_watch *f) +{ + DLIST_REMOVE(f->ctx->fds, f); + f->fd = -1; + + return 0; +} + +static void +fd_input_available(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *data) +{ + struct fd_watch *watch = talloc_get_type(data, struct fd_watch); + + if (watch->ctx->channel == NULL) { + DEBUG(1, ("Invalid ares channel - this is likely a bug\n")); + return; + } + + if (flags & TEVENT_FD_READ) { + ares_process_fd(watch->ctx->channel, watch->fd, ARES_SOCKET_BAD); + } + if (flags & TEVENT_FD_WRITE) { + ares_process_fd(watch->ctx->channel, ARES_SOCKET_BAD, watch->fd); + } +} + +static void +check_fd_timeouts(struct tevent_context *ev, struct tevent_timer *te, + struct timeval current_time, void *private_data); + +static void +add_timeout_timer(struct tevent_context *ev, struct resolv_ctx *ctx) +{ + struct timeval tv = { 0 }; + struct timeval *tvp; + + tvp = ares_timeout(ctx->channel, NULL, &tv); + + if (tvp == NULL) { + tvp = &tv; + } + + /* Enforce a minimum of 1 second. */ + if (tvp->tv_sec < 1) { + tv = tevent_timeval_current_ofs(1, 0); + } else { + tv = tevent_timeval_current_ofs(tvp->tv_sec, tvp->tv_usec); + } + + ctx->timeout_watcher = tevent_add_timer(ev, ctx, tv, check_fd_timeouts, + ctx); + if (ctx->timeout_watcher == NULL) { + DEBUG(1, ("Out of memory\n")); + } +} + +static void +check_fd_timeouts(struct tevent_context *ev, struct tevent_timer *te, + struct timeval current_time, void *private_data) +{ + struct resolv_ctx *ctx = talloc_get_type(private_data, struct resolv_ctx); + + DEBUG(9, ("Checking for DNS timeouts\n")); + + /* NULLify the timeout_watcher so we don't + * free it in the _done() function if it + * gets called. Now that we're already in + * the handler, tevent will take care of + * freeing it when it returns. + */ + ctx->timeout_watcher = NULL; + + ares_process_fd(ctx->channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD); + + if (ctx->pending_requests > 0) { + add_timeout_timer(ev, ctx); + } +} + +static void +schedule_timeout_watcher(struct tevent_context *ev, struct resolv_ctx *ctx) +{ + ctx->pending_requests++; + if (ctx->timeout_watcher) { + return; + } + + DEBUG(9, ("Scheduling DNS timeout watcher\n")); + add_timeout_timer(ev, ctx); +} + +static void +unschedule_timeout_watcher(struct resolv_ctx *ctx) +{ + if (ctx->pending_requests <= 0) { + DEBUG(1, ("Pending DNS requests mismatch\n")); + return; + } + + ctx->pending_requests--; + if (ctx->pending_requests == 0) { + DEBUG(9, ("Unscheduling DNS timeout watcher\n")); + talloc_zfree(ctx->timeout_watcher); + } +} + +static void fd_event_add(struct resolv_ctx *ctx, int s, int flags); +static void fd_event_close(struct resolv_ctx *ctx, int s); + +/* + * When ares is ready to read or write to a file descriptor, it will + * call this callback. If both read and write are 0, it means that ares + * will soon close the socket. We are mainly using this function to register + * new file descriptors with tevent. + */ +static void +fd_event(void *data, int s, int fd_read, int fd_write) +{ + struct resolv_ctx *ctx = talloc_get_type(data, struct resolv_ctx); + struct fd_watch *watch; + int flags; + + /* The socket is about to get closed. */ + if (fd_read == 0 && fd_write == 0) { + fd_event_close(ctx, s); + return; + } + + flags = fd_read ? TEVENT_FD_READ : 0; + flags |= fd_write ? TEVENT_FD_WRITE : 0; + + /* Are we already watching this file descriptor? */ + watch = ctx->fds; + while (watch) { + if (watch->fd == s) { + tevent_fd_set_flags(watch->fde, flags); + return; + } + watch = watch->next; + } + + fd_event_add(ctx, s, flags); +} + +static void +fd_event_add(struct resolv_ctx *ctx, int s, int flags) +{ + struct fd_watch *watch; + + /* The file descriptor is new, register it with tevent. */ + watch = talloc(ctx, struct fd_watch); + if (watch == NULL) { + DEBUG(1, ("Out of memory allocating fd_watch structure\n")); + return; + } + talloc_set_destructor(watch, fd_watch_destructor); + + watch->fd = s; + watch->ctx = ctx; + + watch->fde = tevent_add_fd(ctx->ev_ctx, watch, s, flags, + fd_input_available, watch); + if (watch->fde == NULL) { + DEBUG(1, ("tevent_add_fd() failed\n")); + talloc_free(watch); + return; + } + DLIST_ADD(ctx->fds, watch); +} + +static void +fd_event_close(struct resolv_ctx *ctx, int s) +{ + struct fd_watch *watch; + + /* Remove the socket from list */ + watch = ctx->fds; + while (watch) { + if (watch->fd == s) { + talloc_free(watch); + return; + } + watch = watch->next; + } +} + +static int +resolv_ctx_destructor(struct resolv_ctx *ctx) +{ + ares_channel channel; + + DLIST_REMOVE(context_list, ctx); + + if (ctx->channel == NULL) { + DEBUG(1, ("Ares channel already destroyed?\n")); + return -1; + } + + /* Set ctx->channel to NULL first, so that callbacks that get + * ARES_EDESTRUCTION won't retry. */ + channel = ctx->channel; + ctx->channel = NULL; + ares_destroy(channel); + + return 0; +} + +static int +recreate_ares_channel(struct resolv_ctx *ctx) +{ + int ret; + ares_channel new_channel; + ares_channel old_channel; + struct ares_options options; + + DEBUG(4, ("Initializing new c-ares channel\n")); + /* FIXME: the options would contain + * the nameservers to contact, the domains + * to search, timeout... => get from confdb + */ + options.sock_state_cb = fd_event; + options.sock_state_cb_data = ctx; + options.timeout = ctx->timeout * 1000; + options.tries = 1; + ret = ares_init_options(&new_channel, &options, + ARES_OPT_SOCK_STATE_CB | + ARES_OPT_TIMEOUTMS | + ARES_OPT_TRIES); + if (ret != ARES_SUCCESS) { + DEBUG(1, ("Failed to initialize ares channel: %s\n", + resolv_strerror(ret))); + return return_code(ret); + } + + old_channel = ctx->channel; + ctx->channel = new_channel; + if (old_channel != NULL) { + DEBUG(4, ("Destroying the old c-ares channel\n")); + ares_destroy(old_channel); + } + + return EOK; +} + +int +resolv_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev_ctx, + int timeout, struct resolv_ctx **ctxp) +{ + int ret; + struct resolv_ctx *ctx; + + ctx = talloc_zero(mem_ctx, struct resolv_ctx); + if (ctx == NULL) + return ENOMEM; + + ctx->ev_ctx = ev_ctx; + ctx->timeout = timeout; + + ret = recreate_ares_channel(ctx); + if (ret != EOK) { + goto done; + } + + DLIST_ADD(context_list, ctx); + talloc_set_destructor(ctx, resolv_ctx_destructor); + + *ctxp = ctx; + return EOK; + +done: + talloc_free(ctx); + return ret; +} + +void +resolv_reread_configuration(void) +{ + struct resolv_ctx *ctx; + + DEBUG(4, ("Recreating all c-ares channels\n")); + DLIST_FOR_EACH(ctx, context_list) { + recreate_ares_channel(ctx); + } +} + +struct hostent * +resolv_copy_hostent(TALLOC_CTX *mem_ctx, struct hostent *src) +{ + struct hostent *ret; + int len; + int i; + + ret = talloc_zero(mem_ctx, struct hostent); + if (ret == NULL) { + return NULL; + } + + if (src->h_name != NULL) { + ret->h_name = talloc_strdup(ret, src->h_name); + if (ret->h_name == NULL) { + goto fail; + } + } + if (src->h_aliases != NULL) { + for (len = 0; src->h_aliases[len] != NULL; len++); + ret->h_aliases = talloc_size(ret, sizeof(char *) * (len + 1)); + if (ret->h_aliases == NULL) { + goto fail; + } + for (i = 0; i < len; i++) { + ret->h_aliases[i] = talloc_strdup(ret->h_aliases, src->h_aliases[i]); + if (ret->h_aliases[i] == NULL) { + goto fail; + } + } + ret->h_aliases[len] = NULL; + } + ret->h_addrtype = src->h_addrtype; + ret->h_length = src->h_length; + if (src->h_addr_list != NULL) { + for (len = 0; src->h_addr_list[len] != NULL; len++); + ret->h_addr_list = talloc_size(ret, sizeof(char *) * (len + 1)); + if (ret->h_addr_list == NULL) { + goto fail; + } + for (i = 0; i < len; i++) { + ret->h_addr_list[i] = talloc_memdup(ret->h_addr_list, + src->h_addr_list[i], + ret->h_length); + if (ret->h_addr_list[i] == NULL) { + goto fail; + } + } + ret->h_addr_list[len] = NULL; + } + + return ret; + +fail: + talloc_free(ret); + return NULL; +} + +/******************************************************************* + * Get host by name. * + *******************************************************************/ + +struct gethostbyname_state { + struct resolv_ctx *resolv_ctx; + /* Part of the query. */ + const char *name; + int family; + /* These are returned by ares. The hostent struct will be freed + * when the user callback returns. */ + struct hostent *hostent; + int status; + int timeouts; + int retrying; +}; + +static void +ares_gethostbyname_wakeup(struct tevent_req *req); + +struct tevent_req * +resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct resolv_ctx *ctx, const char *name) +{ + struct tevent_req *req, *subreq; + struct gethostbyname_state *state; + struct timeval tv = { 0, 0 }; + + DEBUG(4, ("Trying to resolve A record of '%s'\n", name)); + + if (ctx->channel == NULL) { + DEBUG(1, ("Invalid ares channel - this is likely a bug\n")); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct gethostbyname_state); + if (req == NULL) + return NULL; + + state->resolv_ctx = ctx; + state->name = name; + state->family = AF_INET; + state->hostent = NULL; + state->status = 0; + state->timeouts = 0; + state->retrying = 0; + + /* We need to have a wrapper around ares_gethostbyname(), because + * ares_gethostbyname() can in some cases call it's callback immediately. + * This would not let our caller to set a callback for req. */ + subreq = tevent_wakeup_send(req, ev, tv); + if (subreq == NULL) { + DEBUG(1, ("Failed to add critical timer to run next operation!\n")); + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ares_gethostbyname_wakeup, req); + schedule_timeout_watcher(ev, ctx); + + return req; +} + +static void +resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *hostent); + +static void +resolv_gethostbyname_done(void *arg, int status, int timeouts, struct hostent *hostent) +{ + struct tevent_req *req = talloc_get_type(arg, struct tevent_req); + struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state); + + if (state->retrying == 0 && status == ARES_EDESTRUCTION + && state->resolv_ctx->channel != NULL) { + state->retrying = 1; + ares_gethostbyname(state->resolv_ctx->channel, state->name, + state->family, resolv_gethostbyname_done, req); + return; + } + + unschedule_timeout_watcher(state->resolv_ctx); + + if (hostent != NULL) { + state->hostent = resolv_copy_hostent(req, hostent); + if (state->hostent == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + } else { + state->hostent = NULL; + } + state->status = status; + state->timeouts = timeouts; + + if (status != ARES_SUCCESS) { + if (status == ARES_ENOTFOUND || status == ARES_ENODATA) { + /* IPv4 failure. Try IPv6 */ + state->family = AF_INET6; + state->retrying = 0; + state->timeouts = 0; + DEBUG(4, ("Trying to resolve AAAA record of '%s'\n", + state->name)); + ares_gethostbyname(state->resolv_ctx->channel, state->name, + state->family, resolv_gethostbyname6_done, + req); + return; + } + + /* Any other error indicates a server error, + * so don't bother trying again + */ + tevent_req_error(req, return_code(status)); + } + else { + tevent_req_done(req); + } +} + +static void +resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *hostent) +{ + struct tevent_req *req = talloc_get_type(arg, struct tevent_req); + struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state); + + if (state->retrying == 0 && status == ARES_EDESTRUCTION) { + state->retrying = 1; + ares_gethostbyname(state->resolv_ctx->channel, state->name, + state->family, resolv_gethostbyname6_done, req); + return; + } + + if (hostent != NULL) { + state->hostent = resolv_copy_hostent(req, hostent); + if (state->hostent == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + } else { + state->hostent = NULL; + } + state->status = status; + state->timeouts = timeouts; + + if (status != ARES_SUCCESS) { + tevent_req_error(req, return_code(status)); + } + else { + tevent_req_done(req); + } +} + +int +resolv_gethostbyname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + int *status, int *timeouts, + struct hostent **hostent) +{ + struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state); + + /* Fill in even in case of error as status contains the + * c-ares return code */ + if (status) { + *status = state->status; + } + if (timeouts) { + *timeouts = state->timeouts; + } + if (hostent) { + *hostent = talloc_steal(mem_ctx, state->hostent); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void +ares_gethostbyname_wakeup(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct gethostbyname_state *state = tevent_req_data(req, + struct gethostbyname_state); + + if (!tevent_wakeup_recv(subreq)) { + return; + } + talloc_zfree(subreq); + + if (state->resolv_ctx->channel == NULL) { + DEBUG(1, ("Invalid ares channel - this is likely a bug\n")); + tevent_req_error(req, EIO); + return; + } + + ares_gethostbyname(state->resolv_ctx->channel, state->name, + state->family, resolv_gethostbyname_done, req); +} + +/* SRV and TXT parsing is not used anywhere in the code yet, so we disable it + * for now + */ +#ifdef BUILD_TXT_SRV + +/* + * A simple helper function that will take an array of struct ares_srv_reply that + * was allocated by malloc() in c-ares and copies it using talloc. The old one + * is freed and the talloc one is put into 'reply_list' instead. + */ +static int +rewrite_talloc_srv_reply(TALLOC_CTX *mem_ctx, struct ares_srv_reply **reply_list) +{ + struct ares_srv_reply *ptr = NULL; + struct ares_srv_reply *new_list = NULL; + struct ares_srv_reply *old_list = *reply_list; + + /* Nothing to do, but not an error */ + if (!old_list) { + return EOK; + } + + /* Copy the linked list */ + while (old_list) { + /* Special case for the first node */ + if (!new_list) { + new_list = talloc_zero(mem_ctx, struct ares_srv_reply); + if (new_list == NULL) { + ares_free_data(*reply_list); + return ENOMEM; + } + ptr = new_list; + } else { + ptr->next = talloc_zero(new_list, struct ares_srv_reply); + if (ptr->next == NULL) { + ares_free_data(*reply_list); + talloc_free(new_list); + return ENOMEM; + } + ptr = ptr->next; + } + + ptr->weight = old_list->weight; + ptr->priority = old_list->priority; + ptr->port = old_list->port; + ptr->host = talloc_strdup(ptr, old_list->host); + if (ptr->host == NULL) { + ares_free_data(*reply_list); + talloc_free(new_list); + return ENOMEM; + } + + old_list = old_list->next; + } + + /* Free the old one (uses malloc). */ + ares_free_data(*reply_list); + + /* And now put our own new_list in place. */ + *reply_list = new_list; + + return EOK; +} + +/******************************************************************* + * Get SRV record * + *******************************************************************/ + +struct getsrv_state { + struct resolv_ctx *resolv_ctx; + /* the SRV query - for example _ldap._tcp.example.com */ + const char *query; + + /* parsed data returned by ares */ + struct ares_srv_reply *reply_list; + int status; + int timeouts; + int retrying; +}; + +static void +ares_getsrv_wakeup(struct tevent_req *subreq); + +struct tevent_req * +resolv_getsrv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct resolv_ctx *ctx, const char *query) +{ + struct tevent_req *req, *subreq; + struct getsrv_state *state; + struct timeval tv = { 0, 0 }; + + DEBUG(4, ("Trying to resolve SRV record of '%s'\n", query)); + + if (ctx->channel == NULL) { + DEBUG(1, ("Invalid ares channel - this is likely a bug\n")); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct getsrv_state); + if (req == NULL) + return NULL; + + state->resolv_ctx = ctx; + state->query = query; + state->reply_list = NULL; + state->status = 0; + state->timeouts = 0; + state->retrying = 0; + + subreq = tevent_wakeup_send(req, ev, tv); + if (subreq == NULL) { + DEBUG(1, ("Failed to add critical timer to run next operation!\n")); + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ares_getsrv_wakeup, req); + schedule_timeout_watcher(ev, ctx); + + return req; +} + +static void +resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen) +{ + struct tevent_req *req = talloc_get_type(arg, struct tevent_req); + struct getsrv_state *state = tevent_req_data(req, struct getsrv_state); + int ret; + struct ares_srv_reply *reply_list; + + if (state->retrying == 0 && status == ARES_EDESTRUCTION + && state->resolv_ctx->channel != NULL) { + state->retrying = 1; + ares_query(state->resolv_ctx->channel, state->query, + ns_c_in, ns_t_srv, resolv_getsrv_done, req); + return; + } + + unschedule_timeout_watcher(state->resolv_ctx); + + state->status = status; + state->timeouts = timeouts; + + if (status != ARES_SUCCESS) { + tevent_req_error(req, return_code(status)); + ret = return_code(status); + goto fail; + } + + ret = ares_parse_srv_reply(abuf, alen, &reply_list); + if (status != ARES_SUCCESS) { + DEBUG(2, ("SRV record parsing failed: %d: %s\n", ret, ares_strerror(ret))); + ret = return_code(ret); + goto fail; + } + ret = rewrite_talloc_srv_reply(req, &reply_list); + if (ret != EOK) { + goto fail; + } + state->reply_list = reply_list; + + tevent_req_done(req); + return; + +fail: + state->reply_list = NULL; + tevent_req_error(req, ret); +} + +int +resolv_getsrv_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *status, + int *timeouts, struct ares_srv_reply **reply_list) +{ + struct getsrv_state *state = tevent_req_data(req, struct getsrv_state); + + if (status) + *status = state->status; + if (timeouts) + *timeouts = state->timeouts; + if (reply_list) + *reply_list = talloc_steal(mem_ctx, state->reply_list); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void +ares_getsrv_wakeup(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct getsrv_state *state = tevent_req_data(req, + struct getsrv_state); + + if (!tevent_wakeup_recv(subreq)) { + return; + } + talloc_zfree(subreq); + + if (state->resolv_ctx->channel == NULL) { + DEBUG(1, ("Invalid ares channel - this is likely a bug\n")); + tevent_req_error(req, EIO); + return; + } + + ares_query(state->resolv_ctx->channel, state->query, + ns_c_in, ns_t_srv, resolv_getsrv_done, req); +} + +/* + * A simple helper function that will take an array of struct txt_reply that + * was allocated by malloc() in c-ares and copies it using talloc. The old one + * is freed and the talloc one is put into 'reply_list' instead. + */ +static int +rewrite_talloc_txt_reply(TALLOC_CTX *mem_ctx, struct ares_txt_reply **reply_list) +{ + struct ares_txt_reply *ptr = NULL; + struct ares_txt_reply *new_list = NULL; + struct ares_txt_reply *old_list = *reply_list; + + /* Nothing to do, but not an error */ + if (!old_list) { + return EOK; + } + + /* Copy the linked list */ + while (old_list) { + + /* Special case for the first node */ + if (!new_list) { + new_list = talloc_zero(mem_ctx, struct ares_txt_reply); + if (new_list == NULL) { + ares_free_data(*reply_list); + talloc_free(new_list); + return ENOMEM; + } + ptr = new_list; + } else { + ptr->next = talloc_zero(new_list, struct ares_txt_reply); + if (ptr->next == NULL) { + ares_free_data(*reply_list); + talloc_free(new_list); + return ENOMEM; + } + ptr = ptr->next; + } + + ptr->length = old_list->length; + ptr->txt = talloc_memdup(ptr, old_list->txt, + old_list->length); + if (ptr->txt == NULL) { + ares_free_data(*reply_list); + talloc_free(new_list); + return ENOMEM; + } + + old_list = old_list->next; + } + + ares_free_data(*reply_list); + + /* And now put our own new_list in place. */ + *reply_list = new_list; + + return EOK; +} + +/******************************************************************* + * Get TXT record * + *******************************************************************/ + +struct gettxt_state { + struct resolv_ctx *resolv_ctx; + /* the TXT query */ + const char *query; + + /* parsed data returned by ares */ + struct ares_txt_reply *reply_list; + int status; + int timeouts; + int retrying; +}; + +static void +ares_gettxt_wakeup(struct tevent_req *subreq); + +struct tevent_req * +resolv_gettxt_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct resolv_ctx *ctx, const char *query) +{ + struct tevent_req *req, *subreq; + struct gettxt_state *state; + struct timeval tv = { 0, 0 }; + + DEBUG(4, ("Trying to resolve TXT record of '%s'\n", query)); + + if (ctx->channel == NULL) { + DEBUG(1, ("Invalid ares channel - this is likely a bug\n")); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct gettxt_state); + if (req == NULL) + return NULL; + + state->resolv_ctx = ctx; + state->query = query; + state->reply_list = NULL; + state->status = 0; + state->timeouts = 0; + state->retrying = 0; + + subreq = tevent_wakeup_send(req, ev, tv); + if (subreq == NULL) { + DEBUG(1, ("Failed to add critical timer to run next operation!\n")); + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ares_gettxt_wakeup, req); + schedule_timeout_watcher(ev, ctx); + + return req; +} + +static void +resolv_gettxt_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen) +{ + struct tevent_req *req = talloc_get_type(arg, struct tevent_req); + struct gettxt_state *state = tevent_req_data(req, struct gettxt_state); + int ret; + struct ares_txt_reply *reply_list; + + if (state->retrying == 0 && status == ARES_EDESTRUCTION + && state->resolv_ctx->channel != NULL) { + state->retrying = 1; + ares_query(state->resolv_ctx->channel, state->query, + ns_c_in, ns_t_txt, resolv_gettxt_done, req); + return; + } + + unschedule_timeout_watcher(state->resolv_ctx); + + state->status = status; + state->timeouts = timeouts; + + if (status != ARES_SUCCESS) { + ret = return_code(status); + goto fail; + } + + ret = ares_parse_txt_reply(abuf, alen, &reply_list); + if (status != ARES_SUCCESS) { + DEBUG(2, ("TXT record parsing failed: %d: %s\n", ret, ares_strerror(ret))); + ret = return_code(ret); + goto fail; + } + ret = rewrite_talloc_txt_reply(req, &reply_list); + if (ret != EOK) { + goto fail; + } + state->reply_list = reply_list; + + tevent_req_done(req); + return; + +fail: + state->reply_list = NULL; + tevent_req_error(req, ret); +} + +int +resolv_gettxt_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *status, + int *timeouts, struct ares_txt_reply **reply_list) +{ + struct gettxt_state *state = tevent_req_data(req, struct gettxt_state); + + if (status) + *status = state->status; + if (timeouts) + *timeouts = state->timeouts; + if (reply_list) + *reply_list = talloc_steal(mem_ctx, state->reply_list); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void +ares_gettxt_wakeup(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct gettxt_state *state = tevent_req_data(req, + struct gettxt_state); + + if (!tevent_wakeup_recv(subreq)) { + return; + } + talloc_zfree(subreq); + + if (state->resolv_ctx->channel == NULL) { + DEBUG(1, ("Invalid ares channel - this is likely a bug\n")); + tevent_req_error(req, EIO); + return; + } + + ares_query(state->resolv_ctx->channel, state->query, + ns_c_in, ns_t_txt, resolv_gettxt_done, req); +} + +#endif diff --git a/src/resolv/async_resolv.h b/src/resolv/async_resolv.h new file mode 100644 index 00000000..2ba6449b --- /dev/null +++ b/src/resolv/async_resolv.h @@ -0,0 +1,95 @@ +/* + SSSD + + Async resolver header + + Authors: + Martin Nagy <mnagy@redhat.com> + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) Red Hat, Inc 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 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 + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ASYNC_RESOLV_H__ +#define __ASYNC_RESOLV_H__ + +#include <netdb.h> +#include <ares.h> + +#include "config.h" + +#ifndef HAVE_ARES_DATA +#include "resolv/ares/ares_parse_srv_reply.h" +#include "resolv/ares/ares_parse_txt_reply.h" +#include "resolv/ares/ares_data.h" +#endif /* HAVE_ARES_DATA */ + +/* + * An opaque structure which holds context for a module using the async + * resolver. Is should be used as a "local-global" variable - in sssd, + * every backend should have its own. + + * Do NOT free the context until there are any pending resolv_ calls + */ +struct resolv_ctx; + +int resolv_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev_ctx, + int timeout, struct resolv_ctx **ctxp); + +void resolv_reread_configuration(void); + +const char *resolv_strerror(int ares_code); + +struct hostent *resolv_copy_hostent(TALLOC_CTX *mem_ctx, + struct hostent *src); + +/** Get host by name **/ +struct tevent_req *resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resolv_ctx *ctx, + const char *name); + +int resolv_gethostbyname_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + int *status, + int *timeouts, + struct hostent **hostent); + +/** Get SRV record **/ +struct tevent_req *resolv_getsrv_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resolv_ctx *ctx, + const char *query); + +int resolv_getsrv_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + int *status, + int *timeouts, + struct ares_srv_reply **reply_list); + +/** Get TXT record **/ +struct tevent_req *resolv_gettxt_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resolv_ctx *ctx, + const char *query); + +int resolv_gettxt_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + int *status, + int *timeouts, + struct ares_txt_reply **reply_list); + +#endif /* __ASYNC_RESOLV_H__ */ |