diff options
Diffstat (limited to '')
-rw-r--r-- | getservinfo.c | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/getservinfo.c b/getservinfo.c new file mode 100644 index 0000000..3fc87b8 --- /dev/null +++ b/getservinfo.c @@ -0,0 +1,150 @@ +/* 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 + +static uint16_t u16(uint8_t **ptr) { + uint16_t x = *(*ptr)++; + x = x << 8 | *(*ptr)++; + return x; +} + +static void nameSkip(uint8_t **ptr) { + for (uint8_t len; (len = *(*ptr)++); *ptr += len) { + if (len & 0xC0) { + (*ptr)++; + break; + } + } +} + +static char *nameString(uint8_t **ptr) { + char *name = (char *)(*ptr + 1); + for (uint8_t len; (len = **ptr); *ptr += len) { + *(*ptr)++ = '.'; + } + return name; +} + +/* 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. + */ +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; + if (hints->ai_flags & AI_NUMERICHOST) return EAI_BADFLAGS; + if (hints->ai_flags & AI_NUMERICSERV) return EAI_BADFLAGS; + + char *rest; + strtoul(servname, &rest, 10); + if (!*rest) 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 < 0) return getaddrinfo(hostname, servname, hints, res); + + uint8_t *ptr = msg; + u16(&ptr); // ID + uint16_t rcode = u16(&ptr) & 0x000F; + uint16_t qdcount = u16(&ptr); + uint16_t ancount = u16(&ptr); + u16(&ptr); // NSCOUNT + u16(&ptr); // ARCOUNT + + if (rcode || !ancount) return getaddrinfo(hostname, servname, hints, res); + + for (uint16_t q = 0; q < qdcount; ++q) { + nameSkip(&ptr); // QNAME + u16(&ptr); // QTYPE + u16(&ptr); // QCLASS + } + + // Read only one answer, ignoring Priority and Weight. Does anyone actually + // use them? + nameSkip(&ptr); // NAME + u16(&ptr); // TYPE + u16(&ptr); // CLASS + u16(&ptr); // TTL + u16(&ptr); // TTL + u16(&ptr); // RDLENGTH + u16(&ptr); // Priority + u16(&ptr); // Weight + + uint16_t port = u16(&ptr); + hostname = nameString(&ptr); + if (!hostname[0]) return EAI_NONAME; + + char myServ[sizeof("65535")]; + snprintf(myServ, sizeof(myServ), "%hu", port); + + struct addrinfo myHints = *hints; +#ifdef AI_DEFAULT + if (!myHints.ai_flags) myHints.ai_flags = AI_DEFAULT; +#endif + myHints.ai_flags |= AI_NUMERICSERV; + myHints.ai_flags &= ~AI_CANONNAME; + + int error = getaddrinfo(hostname, myServ, &myHints, res); + if (error) return error; + + (*res)->ai_canonname = strdup(hostname); + if (!(*res)->ai_canonname) { + freeaddrinfo(*res); + return EAI_MEMORY; + } + return 0; +} |