diff options
Diffstat (limited to '')
-rw-r--r-- | imap.c | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/imap.c b/imap.c new file mode 100644 index 0000000..6894319 --- /dev/null +++ b/imap.c @@ -0,0 +1,222 @@ +/* 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 "compat.h" + +#include <err.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <tls.h> + +#include "imap.h" + +const char *Atoms[AtomCap] = { +#define X(id, str) [id] = str, + ENUM_ATOM +#undef X +}; + +bool imapVerbose; + +static int imapRead(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 (imapVerbose) fprintf(stderr, "%.*s", (int)ret, ptr); + return ret; +} + +static int imapWrite(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 (imapVerbose) fprintf(stderr, "%.*s", (int)ret, ptr); + return ret; +} + +static int imapClose(void *_tls) { + struct tls *tls = _tls; + int error = tls_close(tls); + if (error) errx(EX_IOERR, "tls_close: %s", tls_error(tls)); + return error; +} + +FILE *imapOpen(const char *host, const char *port) { + 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, imapRead, imapWrite, NULL, imapClose); + if (!imap) err(EX_SOFTWARE, "funopen"); + + setlinebuf(imap); + return imap; +} + +static size_t cap; +static char *buf; +static char *ptr; + +static void imapLine(FILE *imap) { + ssize_t len = getline(&buf, &cap, imap); + if (len < 0) errx(EX_PROTOCOL, "unexpected eof"); + if (len < 1 || buf[len - 1] != '\n') errx(EX_PROTOCOL, "missing LF"); + if (len < 2 || buf[len - 2] != '\r') errx(EX_PROTOCOL, "missing CR"); + buf[len - 2] = '\0'; + ptr = buf; +} + +static struct Data parseAtom(void) { + size_t len = strcspn(ptr, " ()[]{\""); + struct Data data = { + .type = Atom, + .atom = atomn(ptr, len), + }; + ptr += len; + return data; +} + +static struct Data parseNumber(void) { + return (struct Data) { + .type = Number, + .number = strtoull(ptr, &ptr, 10), + }; +} + +static struct Data parseQuoted(void) { + ptr++; + size_t len = strcspn(ptr, "\""); + if (ptr[len] != '"') errx(EX_PROTOCOL, "missing quoted string delimiter"); + struct Data data = { + .type = String, + .string = strndup(ptr, len), + }; + if (!data.string) err(EX_OSERR, "strndup"); + ptr += len + 1; + return data; +} + +static struct Data parseLiteral(FILE *imap) { + ptr++; + size_t len = strtoull(ptr, &ptr, 10); + if (*ptr != '}') errx(EX_PROTOCOL, "missing literal prefix delimiter"); + struct Data data = { + .type = String, + .string = malloc(len + 1), + }; + if (!data.string) err(EX_OSERR, "malloc"); + size_t n = fread(data.string, len, 1, imap); + if (!n) errx(EX_PROTOCOL, "truncated literal"); + imapLine(imap); + data.string[len] = '\0'; + return data; +} + +static struct Data parseData(FILE *imap); + +static struct Data parseList(FILE *imap, char close) { + if (*ptr) ptr++; + struct Data data = { .type = List }; + while (*ptr != close) { + if (data.list.len == data.list.cap) { + if (data.list.cap) { + data.list.cap *= 2; + } else { + data.list.cap = 4; + } + data.list.ptr = realloc( + data.list.ptr, sizeof(*data.list.ptr) * data.list.cap + ); + if (!data.list.ptr) err(EX_OSERR, "realloc"); + } + data.list.ptr[data.list.len++] = parseData(imap); + } + if (*ptr) ptr++; + return data; +} + +static struct Data parseData(FILE *imap) { + if (*ptr == ' ') ptr++; + if (*ptr == '"') return parseQuoted(); + if (*ptr == '{') return parseLiteral(imap); + if (*ptr == '(') return parseList(imap, ')'); + if (*ptr == '[') return parseList(imap, ']'); + if (*ptr >= '0' && *ptr <= '9') return parseNumber(); + if (*ptr) return parseAtom(); + errx(EX_PROTOCOL, "unexpected eof"); +} + +struct Resp imapResp(FILE *imap) { + struct Data data; + struct Resp resp = {0}; + imapLine(imap); + + data = parseData(imap); + if (data.type != Atom) errx(EX_PROTOCOL, "expected tag atom"); + resp.tag = data.atom; + if (resp.tag == AtomContinue) { + if (*ptr == ' ') ptr++; + resp.text = ptr; + return resp; + } + + data = parseData(imap); + if (data.type == Number) { + resp.number = data.number; + data = parseData(imap); + } + if (data.type != Atom) errx(EX_PROTOCOL, "expected response atom"); + resp.resp = data.atom; + + if ( + resp.resp == AtomOk || + resp.resp == AtomNo || + resp.resp == AtomBad || + resp.resp == AtomPreauth || + resp.resp == AtomBye + ) { + if (*ptr == ' ') ptr++; + if (*ptr == '[') { + data = parseList(imap, ']'); + resp.code = data.list; + } + if (*ptr == ' ') ptr++; + resp.text = ptr; + } else { + data = parseList(imap, '\0'); + resp.data = data.list; + } + + return resp; +} |