summary refs log tree commit diff
path: root/imbox.c
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-04-08 16:49:50 -0400
committerJune McEnroe <june@causal.agency>2020-04-08 16:49:50 -0400
commitdca3eb7a6a0c7e8a71d26c4390d908f5f6c2f32f (patch)
treedd82c828c185820ac7c39fc6db8681e1b5bbe649 /imbox.c
parentSimplify mboxrd output (diff)
downloadimbox-dca3eb7a6a0c7e8a71d26c4390d908f5f6c2f32f.tar.gz
imbox-dca3eb7a6a0c7e8a71d26c4390d908f5f6c2f32f.zip
Use a real IMAP parser
Diffstat (limited to '')
-rw-r--r--imbox.c267
1 files changed, 84 insertions, 183 deletions
diff --git a/imbox.c b/imbox.c
index 24ac67b..64bcfec 100644
--- a/imbox.c
+++ b/imbox.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+/* Copyright (C) 2019, 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
@@ -18,19 +18,21 @@
 
 #include <ctype.h>
 #include <err.h>
+#include <inttypes.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/wait.h>
 #include <sysexits.h>
-#include <tls.h>
 #include <unistd.h>
 
 #ifndef NO_READPASSPHRASE_H
 #include <readpassphrase.h>
 #endif
 
+#include "imap.h"
+
 #if !defined(DIG_PATH) && !defined(DRILL_PATH)
 #	ifdef __FreeBSD__
 #		define DRILL_PATH "/usr/bin/drill"
@@ -39,6 +41,10 @@
 #	endif
 #endif
 
+#define FETCH_HEADERS \
+	"Date From To Cc Subject Message-Id In-Reply-To References " \
+	"Content-Transfer-Encoding"
+
 static void mboxrd(char *headers, char *body) {
 	printf("From mboxrd@z Thu Jan  1 00:00:00 1970\n");
 	for (char *crlf; (crlf = strstr(headers, "\r\n")); headers = &crlf[2]) {
@@ -124,87 +130,6 @@ static void lookup(const char **host, const char **port, const char *domain) {
 	}
 }
 
-static bool verbose;
-
-static int tlsRead(void *_tls, char *ptr, int len) {
-	struct tls *tls = _tls;
-	ssize_t ret;
-	do {
-		ret = tls_read(tls, ptr, len);
-	} while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
-	if (ret < 0) errx(EX_IOERR, "tls_read: %s", tls_error(tls));
-	if (verbose) fprintf(stderr, "%.*s", (int)ret, ptr);
-	return ret;
-}
-
-static int tlsWrite(void *_tls, const char *ptr, int len) {
-	struct tls *tls = _tls;
-	ssize_t ret;
-	do {
-		ret = tls_write(tls, ptr, len);
-	} while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
-	if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(tls));
-	if (verbose) fprintf(stderr, "%.*s", (int)ret, ptr);
-	return ret;
-}
-
-static int tlsClose(void *_tls) {
-	struct tls *tls = _tls;
-	int error = tls_close(tls);
-	if (error) errx(EX_IOERR, "tls_close: %s", tls_error(tls));
-	return error;
-}
-
-#define ENUM_ATOM \
-	X(Unknown, "") \
-	X(Untagged, "*") \
-	X(Ok, "OK") \
-	X(No, "NO") \
-	X(Bad, "BAD") \
-	X(Bye, "BYE") \
-	X(Login, "LOGIN") \
-	X(Examine, "EXAMINE") \
-	X(Search, "SEARCH") \
-	X(Fetch, "FETCH")
-
-enum Atom {
-#define X(id, _) id,
-	ENUM_ATOM
-#undef X
-	AtomsLen,
-};
-
-static const char *Atoms[AtomsLen] = {
-#define X(id, str) [id] = str,
-	ENUM_ATOM
-#undef X
-};
-
-static enum Atom atom(const char *str) {
-	if (!str) return Unknown;
-	for (enum Atom i = 0; i < AtomsLen; ++i) {
-		if (!strcmp(str, Atoms[i])) return i;
-	}
-	return Unknown;
-}
-
-static char *readLiteral(FILE *imap, const char *line) {
-	char *prefix = strrchr(line, '{');
-	if (!prefix) errx(EX_PROTOCOL, "no literal prefix");
-
-	size_t size = strtoul(prefix + 1, NULL, 10);
-	if (!size) errx(EX_PROTOCOL, "invalid literal size");
-
-	char *literal = malloc(size + 1);
-	if (!literal) err(EX_OSERR, "malloc");
-
-	size_t count = fread(literal, size, 1, imap);
-	if (!count) errx(EX_PROTOCOL, "could not read literal");
-
-	literal[size] = '\0';
-	return literal;
-}
-
 int main(int argc, char *argv[]) {
 	const char *host = NULL;
 	const char *port = "imaps";
@@ -225,7 +150,7 @@ int main(int argc, char *argv[]) {
 			break; case 'h': host = optarg;
 			break; case 'm': mailbox = optarg;
 			break; case 'p': port = optarg;
-			break; case 'v': verbose = true;
+			break; case 'v': imapVerbose = true;
 			break; case 'w': rppFlags |= RPP_STDIN;
 			break; default:  return EX_USAGE;
 		}
@@ -247,122 +172,98 @@ int main(int argc, char *argv[]) {
 	);
 	if (!pass) err(EX_UNAVAILABLE, "readpassphrase");
 
-	struct tls *client = tls_client();
-	if (!client) errx(EX_SOFTWARE, "tls_client");
-
-	struct tls_config *config = tls_config_new();
-	if (!config) errx(EX_SOFTWARE, "tls_config_new");
-
-	int error = tls_configure(client, config);
-	if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client));
-	tls_config_free(config);
+	enum Atom login = 0;
+	enum Atom examine = atom("examine");
+	enum Atom headerFields = atom("HEADER.FIELDS");
+	enum Atom text = atom("TEXT");
 
-	error = tls_connect(client, host, port);
-	if (error) errx(EX_NOHOST, "tls_connect: %s", tls_error(client));
-
-	FILE *imap = funopen(client, tlsRead, tlsWrite, NULL, tlsClose);
-	if (!imap) err(EX_SOFTWARE, "funopen");
-	setlinebuf(imap);
-
-	bool login = false;
-	char *nums = NULL;
+	FILE *imap = imapOpen(host, port);
+	for (struct Resp resp; resp = imapResp(imap), resp.resp != AtomBye;) {
+		if (resp.resp == AtomNo || resp.resp == AtomBad) {
+			errx(EX_CONFIG, "%s: %s", Atoms[resp.resp], resp.text);
+		}
 
-	char *line = NULL;
-	size_t cap = 0;
-	while (0 < getline(&line, &cap, imap)) {
-		char *cr = strchr(line, '\r');
-		if (cr) *cr = '\0';
+		if (!login) {
+			login = atom("login");
+			fprintf(
+				imap, "%s LOGIN \"%s\" \"%s\"\r\n",
+				Atoms[login], user, pass
+			);
+		}
 
-		char *rest = line;
-		enum Atom tag = atom(strsep(&rest, " "));
-		if (rest && isdigit(rest[0])) {
-			strsep(&rest, " ");
+		if (resp.tag == login) {
+			fprintf(imap, "%s EXAMINE \"%s\"\r\n", Atoms[examine], mailbox);
 		}
-		enum Atom resp = atom(strsep(&rest, " "));
 
-		if (resp == No || resp == Bad || resp == Bye) {
-			errx(
-				EX_CONFIG, "%s: %s %s",
-				Atoms[tag], Atoms[resp], (rest ? rest : "")
+		if (resp.tag == examine) {
+			fprintf(
+				imap,
+				"%s SEARCH CHARSET UTF-8 OR "
+				"NOT HEADER Content-Type \"\" "
+				"HEADER Content-Type \"text/plain\"",
+				Atoms[AtomSearch]
 			);
+			if (subject) fprintf(imap, " SUBJECT \"%s\"", subject);
+			if (from) fprintf(imap, " FROM \"%s\"", from);
+			if (to) fprintf(imap, " TO \"%s\"", to);
+			if (cc) fprintf(imap, " CC \"%s\"", cc);
+			fprintf(imap, "\r\n");
 		}
 
-		switch (tag) {
-			break; case Untagged: {
-				if (login) break;
-				fprintf(
-					imap, "%s LOGIN \"%s\" \"%s\"\r\n",
-					Atoms[Login], user, pass
-				);
-				login = true;
-			}
-			break; case Login: {
-				fprintf(imap, "%s EXAMINE \"%s\"\r\n", Atoms[Examine], mailbox);
-			}
-			break; case Examine: {
-				fprintf(
-					imap,
-					"%s SEARCH CHARSET UTF-8 OR "
-					"NOT HEADER Content-Type \"\" "
-					"HEADER Content-Type \"text/plain\"",
-					Atoms[Search]
-				);
-				if (subject) fprintf(imap, " SUBJECT \"%s\"", subject);
-				if (from) fprintf(imap, " FROM \"%s\"", from);
-				if (to) fprintf(imap, " TO \"%s\"", to);
-				if (cc) fprintf(imap, " CC \"%s\"", cc);
-				fprintf(imap, "\r\n");
-			}
-			break; case Search: {
-				if (!nums) errx(EX_PROTOCOL, "no search response");
-				for (char *ch = nums; *ch; ++ch) {
-					if (*ch == ' ') *ch = ',';
+		if (resp.resp == AtomSearch) {
+			if (!resp.data.len) errx(EX_TEMPFAIL, "no matching messages");
+			fprintf(imap, "%s FETCH ", Atoms[AtomFetch]);
+			for (size_t i = 0; i < resp.data.len; ++i) {
+				struct Data data = resp.data.ptr[i];
+				if (data.type != Number) {
+					errx(EX_PROTOCOL, "invalid search result");
 				}
-				fprintf(
-					imap,
-					"%s FETCH %s (BODY[HEADER.FIELDS ("
-					"Date From To Cc Subject Message-Id In-Reply-To References "
-					"Content-Transfer-Encoding"
-					")] BODY[TEXT])\r\n",
-					Atoms[Fetch], nums
-				);
-				free(nums);
-				nums = NULL;
+				fprintf(imap, "%s%" PRIu32, (i ? "," : ""), data.number);
 			}
-			break; case Fetch: {
-				fprintf(imap, "ayy LOGOUT\r\n");
-				fclose(imap);
-				return EX_OK;
-			}
-			break; default:;
+			fprintf(
+				imap,
+				" (BODY[HEADER.FIELDS (" FETCH_HEADERS ")] BODY[TEXT])\r\n"
+			);
 		}
 
-		switch (resp) {
-			break; case Search: {
-				if (!rest) errx(EX_TEMPFAIL, "no matching messages");
-				nums = strdup(rest);
-				if (!nums) err(EX_OSERR, "strdup");
+		if (resp.resp == AtomFetch) {
+			if (!resp.data.len) {
+				errx(EX_PROTOCOL, "no fetch data");
+			}
+			if (resp.data.ptr[0].type != List) {
+				errx(EX_PROTOCOL, "invalid fetch data");
 			}
-			break; case Fetch: {
-				char *headers = readLiteral(imap, rest);
-
-				ssize_t len = getline(&line, &cap, imap);
-				if (len <= 0) errx(EX_PROTOCOL, "unexpected eof after headers");
-
-				char *body = readLiteral(imap, line);
 
-				len = getline(&line, &cap, imap);
-				if (len <= 0) errx(EX_PROTOCOL, "unexpected eof after body");
-				if (strcmp(line, ")\r\n")) {
-					errx(EX_PROTOCOL, "trailing data after headers and body");
+			struct Data headers = {0};
+			struct Data body = {0};
+			struct List items = resp.data.ptr[0].list;
+			for (size_t i = 0; i < items.len; ++i) {
+				struct Data item = items.ptr[i];
+				if (item.type != List) continue;
+				if (!item.list.len) continue;
+				if (item.list.ptr[0].type != Atom) continue;
+				if (item.list.ptr[0].atom == headerFields) {
+					if (i + 1 < items.len) headers = items.ptr[i + 1];
 				}
+				if (item.list.ptr[0].atom == text) {
+					if (i + 1 < items.len) body = items.ptr[i + 1];
+				}
+			}
 
-				mboxrd(headers, body);
-				free(headers);
-				free(body);
+			if (headers.type != String) {
+				errx(EX_PROTOCOL, "invalid header data");
 			}
-			break; default:;
+			if (body.type != String) {
+				errx(EX_PROTOCOL, "invalid body data");
+			}
+			mboxrd(headers.string, body.string);
+		}
+
+		if (resp.tag == AtomFetch) {
+			fprintf(imap, "ayy LOGOUT\r\n");
 		}
+
+		respFree(resp);
 	}
-	errx(EX_PROTOCOL, "unexpected eof");
+	fclose(imap);
 }