diff options
author | June McEnroe <june@causal.agency> | 2020-12-04 00:25:51 -0500 |
---|---|---|
committer | June McEnroe <june@causal.agency> | 2020-12-04 17:53:44 -0500 |
commit | 1a57869a9ebc1880e51f829b3b1b63720c52a6aa (patch) | |
tree | a7d4e11846e5818fbe1945b93c67bbae544279eb | |
parent | Clean up variable expansions in shell script (diff) | |
download | imbox-1a57869a9ebc1880e51f829b3b1b63720c52a6aa.tar.gz imbox-1a57869a9ebc1880e51f829b3b1b63720c52a6aa.zip |
Implement getservinfo for SRV lookup
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | getservinfo.c | 150 | ||||
-rw-r--r-- | imap.c | 39 | ||||
-rw-r--r-- | imap.h | 1 |
4 files changed, 189 insertions, 2 deletions
diff --git a/Makefile b/Makefile index 925b713..2c78527 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ MANS = ${BINS:=.1} -include config.mk +OBJS += getservinfo.o OBJS += imap.o OBJS += imbox.o 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; +} diff --git a/imap.c b/imap.c index 10864aa..e21141d 100644 --- a/imap.c +++ b/imap.c @@ -26,12 +26,16 @@ */ #include <err.h> +#include <netdb.h> +#include <netinet/in.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/socket.h> #include <sysexits.h> #include <tls.h> +#include <unistd.h> FILE *funopen( const void *cookie, @@ -43,6 +47,11 @@ FILE *funopen( #include "imap.h" +int getservinfo( + const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res +); + const char *Atoms[AtomCap] = { #define X(id, str) [id] = str, ENUM_ATOM @@ -92,10 +101,36 @@ struct IMAP imapOpen(const char *host, const char *port) { if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client)); tls_config_free(config); - error = tls_connect(client, host, port); - if (error) errx(EX_NOHOST, "tls_connect: %s", tls_error(client)); + struct addrinfo *head; + struct addrinfo hints = { + .ai_family = PF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + error = getservinfo(host, port, &hints, &head); + if (error) errx(EX_NOHOST, "%s:%s: %s", host, port, gai_strerror(error)); + + int sock = -1; + for (struct addrinfo *ai = head; ai; ai = ai->ai_next) { + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) err(EX_OSERR, "socket"); + + error = connect(sock, ai->ai_addr, ai->ai_addrlen); + if (!error) break; + + close(sock); + sock = -1; + } + if (sock < 0) err(EX_UNAVAILABLE, "%s:%s", host, port); + + error = tls_connect_socket( + client, sock, (head->ai_canonname ? head->ai_canonname : host) + ); + if (error) errx(EX_SOFTWARE, "tls_connect_socket: %s", tls_error(client)); + freeaddrinfo(head); struct IMAP imap = { + .sock = sock, .r = funopen(client, imapRead, NULL, NULL, NULL), .w = funopen(client, NULL, imapWrite, NULL, imapClose), }; diff --git a/imap.h b/imap.h index 3f8050b..c6d96f5 100644 --- a/imap.h +++ b/imap.h @@ -165,6 +165,7 @@ static inline void respFree(struct Resp resp) { extern bool imapVerbose; struct IMAP { + int sock; FILE *r; FILE *w; size_t cap; |