/* 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 <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));
tls_free(tls);
return error;
}
void imapOpen(FILE **read, FILE **write, 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));
*read = funopen(client, imapRead, NULL, NULL, NULL);
*write = funopen(client, NULL, imapWrite, NULL, imapClose);
if (!*read || !*write) err(EX_SOFTWARE, "funopen");
setlinebuf(*write);
}
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 = (*ptr == '.' ? 1 : 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) {
listPush(&data.list, 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;
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;
}