about summary refs log tree commit diff
path: root/notemap.c
diff options
context:
space:
mode:
Diffstat (limited to 'notemap.c')
-rw-r--r--notemap.c368
1 files changed, 140 insertions, 228 deletions
diff --git a/notemap.c b/notemap.c
index 61112e5..a7e79c2 100644
--- a/notemap.c
+++ b/notemap.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+/* Copyright (C) 2020  June 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
@@ -12,10 +12,19 @@
  *
  * 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 "compat.h"
-
 #include <err.h>
 #include <inttypes.h>
 #include <stdbool.h>
@@ -29,91 +38,26 @@
 #include <time.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"
-#	else
-#		define DIG_PATH "dig"
-#	endif
+#ifdef __APPLE__
+#include <sys/random.h>
 #endif
 
-typedef unsigned char byte;
-
-static void lookup(const char **host, const char **port, const char *domain) {
-	static char buf[1024];
-	snprintf(buf, sizeof(buf), "_imaps._tcp.%s", domain);
-
-	int rw[2];
-	int error = pipe(rw);
-	if (error) err(EX_OSERR, "pipe");
-
-	pid_t pid = fork();
-	if (pid < 0) err(EX_OSERR, "fork");
-
-	if (!pid) {
-		close(rw[0]);
-		dup2(rw[1], STDOUT_FILENO);
-		dup2(rw[1], STDERR_FILENO);
-		close(rw[1]);
-#ifdef DRILL_PATH
-		execlp(DRILL_PATH, DRILL_PATH, buf, "SRV", NULL);
-		err(EX_CONFIG, "%s", DRILL_PATH);
+#ifdef DECLARE_RPP
+#define RPP_STDIN 1
+char * readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags);
 #else
-		execlp(DIG_PATH, DIG_PATH, "-t", "SRV", "-q", buf, "+short", NULL);
-		err(EX_CONFIG, "%s", DIG_PATH);
+#include <readpassphrase.h>
 #endif
-	}
-
-	int status;
-	pid = wait(&status);
-	if (pid < 0) err(EX_OSERR, "wait");
-
-	close(rw[1]);
-	FILE *pipe = fdopen(rw[0], "r");
-	if (!pipe) err(EX_IOERR, "fdopen");
 
-	fgets(buf, sizeof(buf), pipe);
-	if (ferror(pipe)) err(EX_IOERR, "fgets");
-
-	if (!WIFEXITED(status) || WEXITSTATUS(status)) {
-		fprintf(stderr, "%s", buf);
-		exit(WEXITSTATUS(status));
-	}
+#include "imap.h"
 
-	char *ptr = buf;
-#ifdef DRILL_PATH
-	for (;;) {
-		char *line = fgets(buf, sizeof(buf), pipe);
-		if (!line || !strcmp(line, ";; ANSWER SECTION:\n")) break;
-	}
-	fgets(buf, sizeof(buf), pipe);
-	if (ferror(pipe)) err(EX_IOERR, "fgets");
-	ptr = strrchr(buf, '\t');
-	ptr = (ptr ? ptr + 1 : buf);
-#endif
-	fclose(pipe);
-
-	char *dot = strrchr(ptr, '.');
-	if (dot) *dot = '\0';
-	strsep(&ptr, " \n"); // priority
-	strsep(&ptr, " \n"); // weight
-	*port = strsep(&ptr, " \n");
-	*host = strsep(&ptr, " \n");
-	if (!*host) {
-		*host = domain;
-		*port = "imaps";
-	}
-}
+typedef unsigned char byte;
 
 static const char *uuidGen(void) {
 	byte uuid[16];
-	arc4random_buf(uuid, sizeof(uuid));
+	int error = getentropy(uuid, sizeof(uuid));
+	if (error) err(EX_OSERR, "getentropy");
+
 	uuid[6] &= 0x0F;
 	uuid[6] |= 0x40;
 	uuid[8] &= 0x3F;
@@ -153,33 +97,24 @@ static char *format(const char *from, const char *uuid, const char *path) {
 		localtime(&status.st_mtime)
 	);
 
-#define HEADERS \
-	"From: <%s>\r\n" \
-	"Date: %s\r\n" \
-	"X-Universally-Unique-Identifier: %s\r\n" \
-	"X-Uniform-Type-Identifier: com.apple.mail-note\r\n" \
-	"X-Mailer: notemap\r\n" \
-	"MIME-Version: 1.0\r\n" \
-	"Content-Type: text/plain; charset=\"utf-8\"\r\n" \
-	"Content-Transfer-Encoding: quoted-printable\r\n" \
-	"Subject: =?utf-8?Q?"
-#define HEADERS_END "?=\r\n\r\n"
-
-	size_t max = sizeof(HEADERS)
-		+ strlen(from)
-		+ strlen(date)
-		+ strlen(uuid)
-		+ 3 * strlen(path)
-		+ sizeof(HEADERS_END)
-		+ 3 * status.st_size
-		+ 3 * status.st_size / 76;
-	char *buf = malloc(max);
-	if (!buf) err(EX_OSERR, "malloc");
-
-	FILE *msg = fmemopen(buf, max, "w");
-	if (!msg) err(EX_OSERR, "fmemopen");
-	fprintf(msg, HEADERS, from, date, uuid);
-
+	char *buf;
+	size_t buflen;
+	FILE *msg = open_memstream(&buf, &buflen);
+	if (!msg) err(EX_OSERR, "open_memstream");
+
+	fprintf(
+		msg,
+		"From: <%s>\r\n"
+		"Date: %s\r\n"
+		"X-Universally-Unique-Identifier: %s\r\n"
+		"X-Uniform-Type-Identifier: com.apple.mail-note\r\n"
+		"X-Mailer: notemap\r\n"
+		"MIME-Version: 1.0\r\n"
+		"Content-Type: text/plain; charset=\"utf-8\"\r\n"
+		"Content-Transfer-Encoding: quoted-printable\r\n"
+		"Subject: =?utf-8?Q?",
+		from, date, uuid
+	);
 	for (const char *ch = path; *ch; ++ch) {
 		if ((uint8_t)*ch & 0x80) {
 			fprintf(msg, "=%02hhX", (uint8_t)*ch);
@@ -189,7 +124,7 @@ static char *format(const char *from, const char *uuid, const char *path) {
 			fprintf(msg, "%c", *ch);
 		}
 	}
-	fprintf(msg, HEADERS_END);
+	fprintf(msg, "?=\r\n\r\n");
 
 	int ch;
 	int len = 0;
@@ -213,9 +148,10 @@ static char *format(const char *from, const char *uuid, const char *path) {
 	}
 	if (ferror(note)) err(EX_IOERR, "%s", path);
 	fclose(note);
-	fclose(msg);
 
-	buf[max - 1] = '\0';
+	error = fclose(msg);
+	if (error) err(EX_IOERR, "fclose");
+
 	return buf;
 }
 
@@ -231,7 +167,7 @@ int main(int argc, char *argv[]) {
 	int rppFlags = 0;
 
 	int opt;
-	while (0 < (opt = getopt(argc, argv, "M:afh:m:p:u:vw"))) {
+	while (0 < (opt = getopt(argc, argv, "M:afh:m:p:vw"))) {
 		switch (opt) {
 			break; case 'M': mailbox = optarg;
 			break; case 'a': add = true;
@@ -245,14 +181,15 @@ int main(int argc, char *argv[]) {
 			break; default:  return EX_USAGE;
 		}
 	}
-	if (!user) errx(EX_USAGE, "username required");
+	if (optind < argc) user = argv[optind++];
 	argv += optind;
 	argc -= optind;
 	
+	if (!user) errx(EX_USAGE, "username required");
 	if (!host) {
-		const char *domain = strchr(user, '@');
-		if (!domain) errx(EX_USAGE, "no domain in username");
-		lookup(&host, &port, &domain[1]);
+		host = strchr(user, '@');
+		if (!host) errx(EX_USAGE, "no domain in username");
+		host++;
 	}
 
 	char buf[1024];
@@ -277,153 +214,128 @@ int main(int argc, char *argv[]) {
 	FILE *map = fopen(path, "r");
 	if (!map) err(EX_NOINPUT, "%s", path);
 
-	size_t cap = 0;
-	char *entry = NULL;
-	char *uuid = NULL;
-	char *note = NULL;
-	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");
-
-	FILE *imapRead, *imap;
-	imapOpen(&imapRead, &imap, host, port);
-	for (
-		struct Resp resp;
-		resp = imapResp(imapRead), resp.resp != AtomBye;
-		respFree(resp)
-	) {
-		if (resp.resp == AtomNo || resp.resp == AtomBad) {
-			errx(EX_CONFIG, "%s: %s", Atoms[resp.resp], resp.text);
-		}
+	struct Resp resp;
+	struct IMAP imap = imapOpen(host, port);
+	respFree(respOk(imapResp(&imap)));
 
-		if (!login) {
-			login = atom("login");
-			fprintf(
-				imap, "%s LOGIN \"%s\" \"%s\"\r\n",
-				Atoms[login], user, pass
-			);
-		}
+	enum Atom login = atom("login");
+	fprintf(imap.w, "%s LOGIN \"%s\" \"%s\"\r\n", Atoms[login], user, pass);
+	for (; (resp = respOk(imapResp(&imap))).tag != login; respFree(resp));
+	respFree(resp);
 
-		if (resp.tag == login) {
-			fprintf(imap, "%s SELECT \"%s\"\r\n", Atoms[next], mailbox);
-		}
+	enum Atom select = atom("select");
+	fprintf(imap.w, "%s SELECT \"%s\"\r\n", Atoms[select], mailbox);
+	for (; (resp = respOk(imapResp(&imap))).tag != select; respFree(resp));
+	respFree(resp);
 
-		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';
+	size_t cap = 0;
+	char *entry = NULL;
+	for (ssize_t len; 0 < (len = getline(&entry, &cap, map));) {
+		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);
-			}
+		char *note = entry;
+		char *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 (argc) {
+			int i;
+			for (i = 0; i < argc; ++i) {
+				if (!argv[i]) continue;
+				if (strcmp(argv[i], note)) continue;
+				argv[i] = NULL;
+				break;
 			}
-
-			fprintf(
-				imap,
-				"%s SEARCH HEADER X-Universally-Unique-Identifier \"%s\"\r\n",
-				Atoms[AtomSearch], uuid
-			);
-			continue;
+			if (i == argc) continue;
 		}
 
-		if (resp.resp == AtomSearch) {
+		uint32_t seq = 0;
+		enum Atom search = atom("search");
+		fprintf(
+			imap.w,
+			"%s SEARCH HEADER X-Universally-Unique-Identifier \"%s\"\r\n",
+			Atoms[search], uuid
+		);
+		for (; (resp = respOk(imapResp(&imap))).tag != search; respFree(resp)) {
+			if (resp.resp != AtomSearch) continue;
 			if (resp.data.len > 1) {
 				errx(EX_CONFIG, "multiple messages matching %s", uuid);
 			}
 			if (resp.data.len) {
 				seq = dataCheck(resp.data.ptr[0], Number).number;
-				fprintf(
-					imap, "%s FETCH %" PRIu32 " ENVELOPE\r\n",
-					Atoms[AtomFetch], seq
-				);
-			} else {
-				message = format(user, uuid, note);
-				fprintf(
-					imap, "%s APPEND %s (\\Seen) {%zu}\r\n",
-					Atoms[create], mailbox, strlen(message)
-				);
 			}
 		}
-		
-		if (resp.resp == AtomFetch) {
+		respFree(resp);
+
+		if (!seq) goto append;
+
+		struct tm date = {0};
+		enum Atom fetch = atom("fetch");
+		fprintf(
+			imap.w, "%s FETCH %" PRIu32 " ENVELOPE\r\n",
+			Atoms[fetch], seq
+		);
+		for (; (resp = respOk(imapResp(&imap))).tag != fetch; respFree(resp)) {
+			if (resp.resp != AtomFetch) continue;
 			if (!resp.data.len) errx(EX_PROTOCOL, "missing fetch data");
 			struct List items = dataCheck(resp.data.ptr[0], List).list;
 			if (items.len < 2) errx(EX_PROTOCOL, "missing fetch data items");
 			enum Atom item = dataCheck(items.ptr[0], Atom).atom;
 			if (item != AtomEnvelope) continue;
-
 			struct List envelope = dataCheck(items.ptr[1], List).list;
-			if (envelope.len < 1) errx(EX_PROTOCOL, "missing envelope date");
-
-			struct tm date = {0};
-			char *rest = strptime(
+			if (!envelope.len) errx(EX_PROTOCOL, "missing envelope date");
+			const char *rest = strptime(
 				dataCheck(envelope.ptr[0], String).string, DATE_FORMAT, &date
 			);
 			if (!rest) errx(EX_PROTOCOL, "invalid envelope date format");
-
-			struct stat status;
-			int error = stat(note, &status);
-			if (error) err(EX_NOINPUT, "%s", note);
-
-			if (!force && status.st_mtime < mktime(&date)) {
-				errx(
-					EX_TEMPFAIL,
-					"%s: note modified in mailbox; use -f to overwrite",
-					note
-				);
-			} else if (status.st_mtime == mktime(&date)) {
-				goto next;
-			}
-
-			message = format(user, uuid, note);
-			fprintf(
-				imap, "%s APPEND %s (\\Seen) {%zu}\r\n",
-				Atoms[replace], mailbox, strlen(message)
+		}
+		respFree(resp);
+
+		struct stat status;
+		int error = stat(note, &status);
+		if (error) err(EX_NOINPUT, "%s", note);
+		if (status.st_mtime < mktime(&date) && !force) {
+			errx(
+				EX_TEMPFAIL,
+				"%s: note modified in mailbox; use -f to overwrite", note
 			);
+		} else if (status.st_mtime == mktime(&date)) {
+			continue;
 		}
 
-		if (resp.tag == AtomContinue) {
-			fprintf(imap, "%s\r\n", message);
-			free(message);
+append:;
+		char *message = format(user, uuid, note);
+		enum Atom append = atom("append");
+		fprintf(
+			imap.w, "%s APPEND %s (\\Seen) {%zu}\r\n",
+			Atoms[append], mailbox, strlen(message)
+		);
+		for (; (resp = respOk(imapResp(&imap))).tag != append; respFree(resp)) {
+			if (resp.tag == AtomContinue) fprintf(imap.w, "%s\r\n", message);
 		}
+		respFree(resp);
+		free(message);
 
-		if (resp.tag == create) {
+		if (!seq) {
 			printf("+ %s\n", note);
-			goto next;
+			continue;
 		}
 
-		if (resp.tag == replace) {
-			printf("~ %s\n", note);
-			fprintf(
-				imap, "%s STORE %" PRIu32 " +FLAGS (\\Deleted)\r\n",
-				Atoms[next], seq
-			);
-		}
+		enum Atom delete = atom("delete");
+		fprintf(
+			imap.w, "%s STORE %" PRIu32 " +FLAGS (\\Deleted)\r\n",
+			Atoms[delete], seq
+		);
+		for (; (resp = respOk(imapResp(&imap))).tag != delete; respFree(resp));
+		respFree(resp);
+		printf("~ %s\n", note);
 	}
-	fclose(imapRead);
-	fclose(imap);
+	if (ferror(map)) err(EX_IOERR, "%s", path);
+
+	fprintf(imap.w, "ayy LOGOUT\r\n");
+	fclose(imap.r);
+	fclose(imap.w);
 
 	int ret = EX_OK;
 	for (int i = 0; i < argc; ++i) {