diff options
author | June McEnroe <june@causal.agency> | 2020-04-08 16:49:50 -0400 |
---|---|---|
committer | June McEnroe <june@causal.agency> | 2020-04-08 16:49:50 -0400 |
commit | dca3eb7a6a0c7e8a71d26c4390d908f5f6c2f32f (patch) | |
tree | dd82c828c185820ac7c39fc6db8681e1b5bbe649 /imbox.c | |
parent | Simplify mboxrd output (diff) | |
download | imbox-dca3eb7a6a0c7e8a71d26c4390d908f5f6c2f32f.tar.gz imbox-dca3eb7a6a0c7e8a71d26c4390d908f5f6c2f32f.zip |
Use a real IMAP parser
Diffstat (limited to '')
-rw-r--r-- | imbox.c | 267 |
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); } |