Working on libcldc's functionality requires moving all cldc_host
searching and manipulation out from chunkd/tabled and into libcldc
proper.  Part of this involves changing the integration points related
to SRV record lookups, so as to accomodate input from a file during
testing (thus avoiding the need for a DNS server w/ SRV records during
testsuite runs).

While working on that, I updated a lot of zaitcev's code in
cld/lib/cldc-dns.c into a libc-like function called getsrvinfo().
getsrvinfo() is intended as an analogue to getaddrinfo(3), for services
based on SRV records (ours, many LDAP/Active Directory services, several
VoIP/SIP services, exim and SMTP, ...).

Maybe this will stay within Project Hail, maybe it will move into POSIX
in ten years or so.  But it was a fun mini-project within the scope of
my libcldc work, so I am posting it here, even if hail-devel is perhaps
not the best forum.

        Jeff






#ifndef __GETSRVINFO_H__
#define __GETSRVINFO_H__

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

struct srvinfo;

enum srvinfo_error_codes {
        ESI_NONE                = 0,    /* no error */
        ESI_CORRUPT             = 1,    /* server returned bad data */
        ESI_FAIL                = 2,    /* server returned permanent failure */
        ESI_AGAIN               = 3,    /* server returned temporary failure;
                                         * try again later.*/
        ESI_OOM                 = 4,    /* internal memory alloc failed */
        ESI_INVAL               = 5,    /* invalid argument(s) */
};

enum srvinfo_flags {
        FSI_NO_ADDR             = (1U << 0),  /* skip host->addrs lookup */
};

struct srvinfo {
        unsigned int            si_prio;    /* SRV priority */
        unsigned int            si_weight;  /* SRV weight */
        char                    *si_target; /* SRV target domainname */
        unsigned short          si_port;    /* SRV port */

        struct addrinfo         *si_addr;   /* addresses returned
                                             * from getaddrinfo(3) lookup
                                             * on si_target */
        struct srvinfo          *si_next;   /* next srvinfo in result list */
};

extern int getsrvinfo(const char *service_name, const char *domain_name,
                      const struct addrinfo *hints,
                      unsigned int flags, struct srvinfo **res);
extern void freesrvinfo(struct srvinfo *res);
extern const char *gsi_strerror(int errcode);

#endif /* __GETSRVINFO_H__ */





/*
 * getsrvinfo.c
 *
 * Copyright 2009 Red Hat, Inc.
 *
 * 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.
 *
 * 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <resolv.h>
#include "getsrvinfo.h"

#define __must_be_array(a) \
        (__builtin_types_compatible_p(typeof(a), typeof(&a[0])))
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))

enum {
        gsi_max_dom_name_sz             = 64,
        gsi_dns_buf_sz                  = 1024,
};

static int fill_srvinfo(struct srvinfo *si,
                        const struct addrinfo *hints,
                        unsigned int gsi_flags,
                        unsigned int priority,
                        unsigned int weight, unsigned int port,
                        unsigned int nlen, const char *name)
{
        char portstr[11];
        char *hostname;
        struct addrinfo *res0 = NULL;
        int rc = 0;

        sprintf(portstr, "%u", port);

        hostname = malloc(nlen + 1);
        if (!hostname) {
                rc = -ENOMEM;
                goto err_name;
        }
        memcpy(hostname, name, nlen);
        hostname[nlen] = 0;

        if (!(gsi_flags & FSI_NO_ADDR)) {
                rc = getaddrinfo(hostname, portstr, hints, &res0);
                if (rc) {
                        rc = -EINVAL;
                        goto err_addr;
                }
        }

        si->si_prio = priority;
        si->si_weight = weight;
        si->si_target = hostname;
        si->si_port = port;

        si->si_addr = res0;
        si->si_next = NULL;

        return 0;

err_addr:
        free(hostname);
err_name:
        return rc;
}

void freesrvinfo(struct srvinfo *res)
{
        while (res) {
                struct srvinfo *tmp;
                
                tmp = res;
                res = res->si_next;

                if (tmp->si_addr)
                        freeaddrinfo(tmp->si_addr);
                free(tmp->si_target);
                free(tmp);
        }
}

int getsrvinfo(const char *service_name, const char *domain_name,
               const struct addrinfo *hints, unsigned int gsi_flags,
               struct srvinfo **res_out)
{
        unsigned char resp[gsi_dns_buf_sz];
        int rlen;
        ns_msg nsb;
        ns_rr rrb;
        int rrlen;
        char hostb[gsi_max_dom_name_sz];
        struct srvinfo *si, *res = NULL, *res_last = NULL;
        const unsigned char *p;
        int rc, gsi_rc, i;

        if (!domain_name || !res_out)
                return ESI_INVAL;

        *res_out = NULL;

        /* we concatencate service_name and domain_name as a helpful
         * service for the caller, because it is very common
         * that service_name is either completely static, or at least
         * stored in a separate variable from domain_name.
         */
        if (service_name) {
                char *name;
                size_t name_len;
                int has_dot;

                has_dot = (service_name[strlen(service_name) - 1] == '.');
                name_len = strlen(service_name) + strlen(domain_name) +
                           (has_dot ? 0 : 1) + 1;

                name = malloc(name_len);
                if (!name)
                        return ESI_OOM;

                snprintf(name, name_len, "%s%s%s", service_name,
                             has_dot ? "" : ".",
                             domain_name);
                        
                rc = res_search(name, ns_c_in, ns_t_srv, resp, sizeof(resp));

                free(name);
        } else {
                rc = res_search(domain_name, ns_c_in, ns_t_srv,
                                resp, sizeof(resp));
        }

        /* parse resolver return value */
        if (rc < 0) {
                switch (h_errno) {
                case TRY_AGAIN:
                        return ESI_AGAIN;
                case HOST_NOT_FOUND:
                case NO_DATA:
                case NO_RECOVERY:
                default:
                        return ESI_FAIL;
                }
        }
        rlen = rc;

        if (rlen == 0)
                return ESI_FAIL;

        /* set up DNS result parse */
        if (ns_initparse(resp, rlen, &nsb) < 0)
                return ESI_CORRUPT;

        /* iterate through each answer.  Because DNS packets may
         * be truncated, we do not signal an error on
         * short-length faults found during packet parsing
         */
        for (i = 0; i < ns_msg_count(nsb, ns_s_an); i++) {
                rc = ns_parserr(&nsb, ns_s_an, i, &rrb);
                if (rc < 0)
                        continue;

                if (ns_rr_class(rrb) != ns_c_in)
                        continue;

                switch (ns_rr_type(rrb)) {
                case ns_t_srv:
                        rrlen = ns_rr_rdlen(rrb);
                        if (rrlen < 8) {        /* 2+2+2 and 2 for host */
                                break;
                        }
                        p = ns_rr_rdata(rrb);
                        rc = dn_expand(resp, resp+rlen, p+6,
                                       hostb, gsi_max_dom_name_sz);
                        if (rc < 0) {
                                break;
                        }
                        if (rc < 2) {
                                break;
                        }

                        si = malloc(sizeof(*si));
                        if (!si) {
                                gsi_rc = ESI_OOM;
                                goto err_out;
                        }

                        if (fill_srvinfo(si, hints, gsi_flags,
                                         ns_get16(p+0),
                                         ns_get16(p+2),
                                         ns_get16(p+4),
                                         rc, hostb)) {
                                free(si);
                                gsi_rc = ESI_OOM;
                                goto err_out;
                        }

                        /* if first item, set BOL-ptr */
                        if (!res)
                                res = si;

                        /* append to EOL */
                        if (res_last)
                                res_last->si_next = si;
                        res_last = si;

                        break;

                case ns_t_cname:        /* impossible, but ... ? */
                default:
                        break;
                }
        }

        *res_out = res;
        return ESI_NONE;

err_out:
        freesrvinfo(res);
        return gsi_rc;
}

static const char *gsi_error_str[] = {
        [ESI_NONE]              = "no error",
        [ESI_CORRUPT]           = "server returned bad data",
        [ESI_FAIL]              = "server returned permanent failure",
        [ESI_AGAIN]             = "server returned temporary failure",
        [ESI_OOM]               = "internal memory alloc failed",
        [ESI_INVAL]             = "invalid argument(s)",
};

const char *gsi_strerror(int errcode)
{
        if (errcode < 0 || errcode >= ARRAY_SIZE(gsi_error_str))
                return NULL;
        
        return gsi_error_str[errcode];
}

--
To unsubscribe from this list: send the line "unsubscribe hail-devel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to