diff options
Diffstat (limited to 'notemap.c')
-rw-r--r-- | notemap.c | 368 |
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(¬e, " "); - if (!note || !uuid || !uuidCheck(uuid)) { - errx(EX_CONFIG, "invalid map entry: %s", entry); - } + char *note = entry; + char *uuid = strsep(¬e, " "); + 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) { |