diff options
Diffstat (limited to 'getservinfo.c')
-rw-r--r-- | getservinfo.c | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/getservinfo.c b/getservinfo.c new file mode 100644 index 0000000..219f72a --- /dev/null +++ b/getservinfo.c @@ -0,0 +1,122 @@ +/* Copyright (C) 2020 C. McEnroe <june@causal.agency> + * + * 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 <https://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this Program, or any covered work, by linking or + * combining it with OpenSSL (or a modified version of that library), + * containing parts covered by the terms of the OpenSSL License and the + * original SSLeay license, the licensors of this Program grant you + * additional permission to convey the resulting work. Corresponding + * Source for a non-source form of such a combination shall include the + * source code for the parts of OpenSSL used as well as that of the + * covered work. + */ + +#include <netdb.h> +#include <netinet/in.h> +#include <resolv.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifndef EAI_BADHINTS +#define EAI_BADHINTS EAI_BADFLAGS +#endif + +#ifndef EAI_PROTOCOL +#define EAI_PROTOCOL EAI_BADFLAGS +#endif + +/* A wrapper around getaddrinfo(3) which first performs SRV record (RFC 2782) + * lookup. hints must be provided and hints->ai_protocol must be set. SRV + * lookup is skipped if servname is numerical. If SRV lookup is successful, the + * ai_canonname field of the first addrinfo structure returned is set to the + * target name. Only the first SRV record is used. Priority and weight are + * ignored. + */ +int getservinfo( + const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res +) { + if (!hints) return EAI_BADHINTS; + if (!hints->ai_protocol) return EAI_PROTOCOL; + + char *rest; + strtoul(servname, &rest, 10); + if (!*rest || hints->ai_flags & (AI_NUMERICHOST | AI_NUMERICSERV)) { + return getaddrinfo(hostname, servname, hints, res); + } + + struct protoent *proto = getprotobynumber(hints->ai_protocol); + if (!proto) return EAI_PROTOCOL; + + char dname[256]; + int len = snprintf( + dname, sizeof(dname), "_%s._%s.%s", + servname, proto->p_name, hostname + ); + if ((size_t)len >= sizeof(dname)) return EAI_OVERFLOW; + + uint8_t msg[512]; + len = res_query(dname, 1 /* IN */, 33 /* SRV */, msg, sizeof(msg)); + if (len < 12) return getaddrinfo(hostname, servname, hints, res); + uint8_t *ptr = &msg[12]; + +#define NAME_SKIP \ + for (uint8_t n; ptr < &msg[len] && (n = *ptr++); ptr += n) { \ + if (n & 0xC0) { \ + ptr++; \ + break; \ + } \ + } + + uint16_t qdcount = msg[4] << 8 | msg[5]; + for (uint16_t q = 0; ptr < &msg[len] && q < qdcount; ++q) { + NAME_SKIP; // QNAME + ptr += 4; // QTYPE, QCLASS + } + + NAME_SKIP; // NAME + ptr += 14; // TYPE, CLASS, TTL, RDLENGTH, Priority, Weight + if (&msg[len] < ptr + 3) { + return getaddrinfo(hostname, servname, hints, res); + } + + char port[sizeof("65535")]; + snprintf(port, sizeof(port), "%d", ptr[0] << 8 | ptr[1]); + ptr += 2; + + // Name compression is not used for Target. + if (!ptr[0]) return EAI_NONAME; + hostname = (const char *)&ptr[1]; + for (uint8_t n; ptr < &msg[len] && (n = *ptr); ptr += n) { + *ptr++ = '.'; + } + + struct addrinfo myHints = *hints; + myHints.ai_flags |= AI_NUMERICSERV; + myHints.ai_flags &= ~AI_CANONNAME; + int error = getaddrinfo(hostname, port, &myHints, res); + if (error) return error; + + (*res)->ai_canonname = strdup(hostname); + if (!(*res)->ai_canonname) { + freeaddrinfo(*res); + return EAI_MEMORY; + } + return 0; +} |