summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--getservinfo.c122
-rw-r--r--imap.c39
-rw-r--r--imap.h1
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;