about summary refs log tree commit diff
path: root/notemap.c
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-04-08 19:08:57 -0400
committerJune McEnroe <june@causal.agency>2020-04-08 19:08:57 -0400
commit1e24334b33092aa6303e7f3ae3957a0c7fc2240c (patch)
tree163206c0023e5cf4c4fc96fc4be784af5aa3bc60 /notemap.c
parentCall fopencookie with a+ (diff)
downloadnotemap-1e24334b33092aa6303e7f3ae3957a0c7fc2240c.tar.gz
notemap-1e24334b33092aa6303e7f3ae3957a0c7fc2240c.zip
Use a real IMAP parser
Diffstat (limited to 'notemap.c')
-rw-r--r--notemap.c304
1 files changed, 113 insertions, 191 deletions
diff --git a/notemap.c b/notemap.c
index 3d5d853..dfe9872 100644
--- a/notemap.c
+++ b/notemap.c
@@ -16,9 +16,10 @@
 
 #include "compat.h"
 
-#include <ctype.h>
 #include <err.h>
+#include <inttypes.h>
 #include <stdbool.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -26,13 +27,14 @@
 #include <sys/wait.h>
 #include <sysexits.h>
 #include <time.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"
@@ -43,37 +45,6 @@
 
 typedef unsigned char byte;
 
-static bool verbose;
-
-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;
-}
-
-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;
-}
-
-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;
-}
-
 static void lookup(const char **host, const char **port, const char *domain) {
 	static char buf[1024];
 	snprintf(buf, sizeof(buf), "_imaps._tcp.%s", domain);
@@ -166,46 +137,9 @@ static bool uuidCheck(const char *uuid) {
 	return true;
 }
 
-#define ENUM_ATOM \
-	X(Unknown, "") \
-	X(Untagged, "*") \
-	X(Ok, "OK") \
-	X(No, "NO") \
-	X(Bad, "BAD") \
-	X(Bye, "BYE") \
-	X(Login, "LOGIN") \
-	X(Search, "SEARCH") \
-	X(Fetch, "FETCH") \
-	X(Append, "APPEND") \
-	X(Next, "next")
-
-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;
-}
-
 #define DATE_FORMAT "%a, %e %b %Y %H:%M:%S %z"
 
-static void append(
-	FILE *imap, const char *mailbox,
-	const char *from, const char *uuid, const char *path
-) {
+static char *format(const char *from, const char *uuid, const char *path) {
 	FILE *note = fopen(path, "r");
 	if (!note) err(EX_NOINPUT, "%s", path);
 
@@ -277,15 +211,7 @@ static void append(
 	fclose(msg);
 
 	buf[max - 1] = '\0';
-	fprintf(
-		imap, "%s APPEND %s (\\Seen) {%zu}\r\n",
-		Atoms[Append], mailbox, strlen(buf)
-	);
-	if (fgetc(imap) == '+') {
-		ungetc('+', imap);
-		fprintf(imap, "%s\r\n", buf);
-	}
-	free(buf);
+	return buf;
 }
 
 int main(int argc, char *argv[]) {
@@ -309,7 +235,7 @@ int main(int argc, char *argv[]) {
 			break; case 'm': path = optarg;
 			break; case 'p': port = optarg;
 			break; case 'u': user = optarg;
-			break; case 'v': verbose = true;
+			break; case 'v': imapVerbose = true;
 			break; case 'w': rppFlags |= RPP_STDIN;
 			break; default:  return EX_USAGE;
 		}
@@ -346,136 +272,109 @@ int main(int argc, char *argv[]) {
 	FILE *map = fopen(path, "r");
 	if (!map) err(EX_NOINPUT, "%s", path);
 
-	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);
-
-	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);
-
-	char *line = NULL;
-	size_t lineCap = 0;
-
+	size_t cap = 0;
 	char *entry = NULL;
-	size_t entryCap = 0;
 	char *uuid = NULL;
 	char *note = NULL;
-	int seq = 0;
-
-	bool login = false;
-	while (0 < getline(&line, &lineCap, imap)) {
-		char *cr = strchr(line, '\r');
-		if (cr) *cr = '\0';
-
-		char *rest = line;
-		enum Atom tag = atom(strsep(&rest, " "));
-		if (rest && isdigit(rest[0])) {
-			strsep(&rest, " ");
+	uint32_t seq = 0;
+	char *message = NULL;
+
+	enum Atom login = 0;
+	enum Atom next = atom("next");
+	enum Atom create = atom("create");
+	enum Atom replace = atom("replace");
+	enum Atom envelope = atom("ENVELOPE");
+	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);
 		}
-		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 (!login) {
+			login = atom("login");
+			fprintf(
+				imap, "%s LOGIN \"%s\" \"%s\"\r\n",
+				Atoms[login], user, pass
 			);
 		}
 
-		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 SELECT \"%s\"\r\n", Atoms[Next], mailbox);
-			}
-
-			break; case Next: Next: {
-				ssize_t len;
-				len = getline(&entry, &entryCap, map);
-				if (ferror(map)) err(EX_IOERR, "%s", path);
-				if (len < 1) goto done;
-				if (entry[len - 1] == '\n') entry[len - 1] = '\0';
-
-				note = entry;
-				uuid = strsep(&note, " ");
-				if (!note || !uuid || !uuidCheck(uuid)) {
-					errx(EX_CONFIG, "invalid map entry: %s", entry);
-				}
-
-				if (argc) {
-					int i;
-					for (i = 0; i < argc; ++i) {
-						if (!argv[i]) continue;
-						if (strcmp(argv[i], note)) continue;
-						argv[i] = NULL;
-						break;
-					}
-					if (i == argc) goto Next;
-				}
+		if (resp.tag == login) {
+			fprintf(imap, "%s SELECT \"%s\"\r\n", Atoms[next], mailbox);
+		}
 
-				fprintf(
-					imap,
-					"%s SEARCH HEADER X-Universally-Unique-Identifier %s\r\n",
-					Atoms[Search], uuid
-				);
+		ssize_t len;
+		if (resp.tag == next) {
+next:
+			len = getline(&entry, &cap, map);
+			if (ferror(map)) err(EX_IOERR, "%s", path);
+			if (len < 1) {
+				fprintf(imap, "ayy LOGOUT\r\n");
+				continue;
 			}
+			if (entry[len - 1] == '\n') entry[len - 1] = '\0';
 
-			break; case Search: {
-				if (!seq) goto Fetch;
-				fprintf(imap, "%s FETCH %d ENVELOPE\r\n", Atoms[Fetch], seq);
+			note = entry;
+			uuid = strsep(&note, " ");
+			if (!note || !uuid || !uuidCheck(uuid)) {
+				errx(EX_CONFIG, "invalid map entry: %s", entry);
 			}
 
-			break; case Fetch: Fetch: {
-				append(imap, mailbox, user, uuid, note);
+			if (argc) {
+				int i;
+				for (i = 0; i < argc; ++i) {
+					if (!argv[i]) continue;
+					if (strcmp(argv[i], note)) continue;
+					argv[i] = NULL;
+					break;
+				}
+				if (i == argc) goto next;
 			}
 
-			break; case Append: {
-				printf("%c %s\n", (seq ? '~' : '+'), note);
-				if (!seq) goto Next;
-				fprintf(
-					imap, "%s STORE %d +FLAGS.SILENT (\\Deleted)\r\n",
-					Atoms[Next], seq
-				);
-			}
+			fprintf(
+				imap,
+				"%s SEARCH HEADER X-Universally-Unique-Identifier \"%s\"\r\n",
+				Atoms[AtomSearch], uuid
+			);
 
-			break; default:;
+			respFree(resp);
+			continue;
 		}
 
-		if (resp == Search) {
-			if (rest) {
-				seq = strtol(rest, &rest, 10);
-				if (*rest) {
-					errx(EX_CONFIG, "multiple messages matching %s", uuid);
+		if (resp.resp == AtomSearch) {
+			if (resp.data.len > 1) {
+				errx(EX_CONFIG, "multiple messages matching %s", uuid);
+			}
+			if (resp.data.len) {
+				if (resp.data.ptr[0].type != Number) {
+					errx(EX_PROTOCOL, "invalid search result");
 				}
+				seq = resp.data.ptr[0].number;
+				fprintf(
+					imap, "%s FETCH %" PRIu32 " ENVELOPE\r\n",
+					Atoms[AtomFetch], seq
+				);
 			} else {
-				seq = 0;
+				message = format(user, uuid, note);
+				fprintf(
+					imap, "%s APPEND %s (\\Seen) {%zu}\r\n",
+					Atoms[create], mailbox, strlen(message)
+				);
 			}
 		}
 
-		if (resp == Fetch) {
-			if (strncmp(rest, "(ENVELOPE", 9)) continue;
-
+		if (
+			resp.resp == AtomFetch &&
+			resp.data.len &&
+			resp.data.ptr[0].type == List &&
+			resp.data.ptr[0].list.len > 1 &&
+			resp.data.ptr[0].list.ptr[0].type == Atom &&
+			resp.data.ptr[0].list.ptr[0].atom == envelope &&
+			resp.data.ptr[0].list.ptr[1].type == List
+		) {
+			struct List envelope = resp.data.ptr[0].list.ptr[1].list;
 			struct tm date = {0};
-			rest = strptime(
-				rest, "(ENVELOPE (\"" DATE_FORMAT "\"", &date
-			);
-			if (!rest) errx(EX_PROTOCOL, "invalid envelope date");
+			char *rest = strptime(envelope.ptr[0].string, DATE_FORMAT, &date);
+			if (!rest) errx(EX_PROTOCOL, "invalid envelope date format");
 
 			struct stat status;
 			int error = stat(note, &status);
@@ -488,13 +387,36 @@ int main(int argc, char *argv[]) {
 					note
 				);
 			} else if (status.st_mtime == mktime(&date)) {
-				goto Next;
+				goto next;
 			}
+
+			message = format(user, uuid, note);
+			fprintf(
+				imap, "%s APPEND %s (\\Seen) {%zu}\r\n",
+				Atoms[replace], mailbox, strlen(message)
+			);
+		}
+
+		if (resp.tag == AtomContinue) {
+			fprintf(imap, "%s\r\n", message);
+			free(message);
+		}
+
+		if (resp.tag == create) {
+			printf("+ %s\n", note);
+			goto next;
 		}
-	}
 
-done:
-	fprintf(imap, "ayy LOGOUT\r\n");
+		if (resp.tag == replace) {
+			printf("~ %s\n", note);
+			fprintf(
+				imap, "%s STORE %" PRIu32 " +FLAGS (\\Deleted)\r\n",
+				Atoms[next], seq
+			);
+		}
+
+		respFree(resp);
+	}
 	fclose(imap);
 
 	int ret = EX_OK;