diff options
Diffstat (limited to '')
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | getservinfo.c | 122 | ||||
-rw-r--r-- | imap.c | 39 | ||||
-rw-r--r-- | imap.h | 1 |
4 files changed, 161 insertions, 2 deletions
diff --git a/Makefile b/Makefile index 5fac27e..1fd5a03 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ LDLIBS = -ltls -include config.mk +OBJS += getservinfo.o OBJS += imap.o OBJS += notemap.o 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; +} 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 b5343d3..33dfd33 100644 --- a/imap.h +++ b/imap.h @@ -162,6 +162,7 @@ static inline void respFree(struct Resp resp) { extern bool imapVerbose; struct IMAP { + int sock; FILE *r; FILE *w; size_t cap; |