summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-04-09 13:00:39 -0400
committerJune McEnroe <june@causal.agency>2020-04-09 13:00:39 -0400
commit97279adb45f3716f3a4a0c121e40f60b762fffb5 (patch)
tree307756735121d257d9e27b4b7a6209646fef5aff
parentAdd IMAP parser (diff)
downloadbubger-97279adb45f3716f3a4a0c121e40f60b762fffb5.tar.gz
bubger-97279adb45f3716f3a4a0c121e40f60b762fffb5.zip
Implement login and UIDVALIDITY check
-rw-r--r--.gitignore2
-rw-r--r--Makefile13
-rw-r--r--archive.c137
3 files changed, 152 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1edae8a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.o
+bubger
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..cd57ae9
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,13 @@
+CFLAGS += -std=c11 -Wall -Wextra -Wpedantic
+LDLIBS = -ltls
+
+OBJS += archive.o
+OBJS += imap.o
+
+bubger: ${OBJS}
+	${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@
+
+${OBJS}: imap.h
+
+clean:
+	rm -f bubger ${OBJS}
diff --git a/archive.c b/archive.c
new file mode 100644
index 0000000..4facd57
--- /dev/null
+++ b/archive.c
@@ -0,0 +1,137 @@
+/* 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/>.
+ */
+
+#include <err.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "imap.h"
+
+#define ENV_PASSWORD "BUBGER_IMAP_PASSWORD"
+
+static void checkValidity(uint32_t validity) {
+	FILE *file = fopen("UIDVALIDITY", "r");
+	if (file) {
+		uint32_t previous;
+		int n = fscanf(file, "%" SCNu32, &previous);
+		if (n < 1) errx(EX_DATAERR, "invalid UIDVALIDITY file");
+
+		if (validity != previous) {
+			errx(EX_TEMPFAIL, "UIDVALIDITY changed; fresh export required");
+		}
+
+	} else {
+		FILE *file = fopen("UIDVALIDITY", "w");
+		if (!file) err(EX_CANTCREAT, "UIDVALIDITY");
+
+		int n = fprintf(file, "%" PRIu32 "\n", validity);
+		if (n < 0) err(EX_IOERR, "UIDVALIDITY");
+
+		int error = fclose(file);
+		if (error) err(EX_IOERR, "UIDVALIDITY");
+	}
+}
+
+int main(int argc, char *argv[]) {
+	const char *host = NULL;
+	const char *port = "imaps";
+	const char *user = NULL;
+	const char *passPath = NULL;
+
+	const char *mailbox = "Archive";
+	const char *algo = "REFERENCES";
+	const char *search = "ALL";
+
+	const char *title = NULL;
+	const char *headPath = NULL;
+
+	for (int opt; 0 < (opt = getopt(argc, argv, "C:a:h:p:s:t:vw:"));) {
+		switch (opt) {
+			break; case 'C': {
+				int error = chdir(optarg);
+				if (error) err(EX_NOINPUT, "%s", optarg);
+			}
+			break; case 'a': algo = optarg;
+			break; case 'h': headPath = optarg;
+			break; case 'p': port = optarg;
+			break; case 's': search = optarg;
+			break; case 't': title = optarg;
+			break; case 'v': imapVerbose = true;
+			break; case 'w': passPath = optarg;
+		}
+	}
+	if (optind < argc) host = argv[optind++];
+	if (optind < argc) user = argv[optind++];
+	if (optind < argc) mailbox = argv[optind++];
+
+	if (!host) errx(EX_USAGE, "host required");
+	if (!user) errx(EX_USAGE, "user required");
+	if (!title) title = mailbox;
+
+	char *pass = NULL;
+	if (passPath) {
+		FILE *file = fopen(passPath, "r");
+		if (!file) err(EX_NOINPUT, "%s", passPath);
+
+		size_t cap = 0;
+		ssize_t len = getline(&pass, &cap, file);
+		if (len < 0) err(EX_IOERR, "%s", passPath);
+		if (len && pass[len - 1] == '\n') pass[len - 1] = '\0';
+		fclose(file);
+	} else {
+		pass = getenv(ENV_PASSWORD);
+		if (!pass) errx(EX_CONFIG, ENV_PASSWORD " unset");
+	}
+
+	enum Atom login = 0;
+	enum Atom examine = atom("examine");
+
+	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);
+		}
+
+		if (!login) {
+			login = atom("login");
+			fprintf(
+				imap, "%s LOGIN \"%s\" \"%s\"\r\n",
+				Atoms[login], user, pass
+			);
+		}
+
+		if (resp.tag == login) {
+			fprintf(imap, "%s EXAMINE \"%s\"\r\n", Atoms[examine], mailbox);
+		}
+
+		if (
+			resp.resp == AtomOk &&
+			resp.code.len > 1 &&
+			resp.code.ptr[0].type == Atom &&
+			resp.code.ptr[0].atom == AtomUIDValidity
+		) {
+			if (resp.code.ptr[1].type != Number) {
+				errx(EX_PROTOCOL, "invalid UIDVALIDITY");
+			}
+			checkValidity(resp.code.ptr[1].number);
+		}
+	}
+	fclose(imap);
+}