tttm

git clone https://orangeshoelaces.net/git/tttm.git

eb6fa4eae86b97b6035acfccf97376cedcae07cf

Author: Vasily Kolobkov on 04/24/2016

Committer: Vasily Kolobkov on 04/24/2016

Once upon a time...

his whimsical majesty confounded the lazy court inquiring of slim
and simple means of having royal correspondence delivered from IMAP
server to a local MDA.

One week and hopefully under 2K slocs did the usual trick winding
up the imagination of a humble software junkie setting off on yet
another weird journey.

Stats

imap.c | 548 ++++++++
tttm.c |  60 +
2 files changed, 608 insertions(+)

Patch

diff --git a/imap.c b/imap.c
new file mode 100644
index 0000000..4f7e62c
--- /dev/null
+++ b/imap.c
@@ -0,0 +1,548 @@
+/*
+  Be warned! All the parse_* functions bear unconventional call
+  semantics. They return 1 on success and 0 otherwise. Specific
+  error code or 0 when all went smooth will be dispensed to the
+  parse context.
+*/
+
+#include <stdarg.h>
+#include <strings.h>
+
+#define LEN(x) (sizeof x / sizeof x[0])
+#define INPUT_BUFFER_LENGTH 2048
+
+struct imap_con {
+	int in;
+	int out;
+	char inbuf[INPUT_BUFFER_LENGTH];
+	int inlen;
+};
+
+static struct literal {
+	enum { OK, NO, BAD, SP } kind;
+	char  *val;
+	size_t len;
+} literals[] = {
+	{ ALERT, "ALERT", 5 },
+	{ ANSWERED, "\\Answered", 9 },
+	{ ANYFL, "\\*", 2 },
+	{ ASTERISK, "*", 1 },
+	{ AUTHEQ, "AUTH=", 5 },
+	{ BAD, "BAD", 3 },
+	{ BADCS, "BADCHARSET", 10 },
+	{ CAP, "CAPABILITY", 10 },
+	{ CBR, "]", 1 },
+	{ CPAR, ")", 1 },
+	{ DELETED, "\\Deleted", 8 },
+	{ DQUOTE, "\"", 1 },
+	{ DRAFT, "\\Draft", 6 },
+	{ EOL, "\r\n", 2 },
+	{ FLAGGED, "\\Flagged", 8 },
+	{ FLAGS, "FLAGS", 5 },
+	{ LIST, "LIST", 4 },
+	{ NIL, "NIL", 3 },
+	{ NO, "NO", 2 },
+	{ OBR, "[", 1 },
+	{ OK, "OK", 2 },
+	{ OPAR, "(", 1 },
+	{ PARSE, "PARSE", 5 },
+	{ PERMFL, "PERMANENTFLAGS", 14 },
+	{ PLUS, "+", 1 },
+	{ RO, "READ-ONLY", 9 },
+	{ RW, "READ-WRITE", 10 },
+	{ SEEN, "\\Seen", 5 },
+	{ SP, " ", 1 },
+	{ TRYC, "TRYCREATE", 9 },
+	{ UIDNEXT, "UIDNEXT", 7 },
+	{ UIDVAL, "UIDVALIDITY", 11 },
+	{ UNSEEN, "UNSEEN", 6 },
+};
+
+static struct parse_cur {
+	char  *ntok;
+	struct semact *nact;
+};
+
+static struct parse_ctx {
+	char  *tokens;
+	char  *tokend;
+	size_t toklen;
+	struct semaction *acts;
+	struct semaction *actend;
+	size_t actlen;
+	struct parse_cur cur;
+	int    e;
+};
+
+static typedef int parsefn(struct parse_ctx *);
+
+int
+read_respln(struct semaction *acts, size_t actlen)
+{
+	char *resp;
+	size_t resplen;
+	prepare_resp(con, &resp, &resplen);
+	struct parse_ctx p = { resp, resplen, acts, actlen, { resp, acts } };
+	return parse_respln(&p);
+}
+
+int
+mark(struct parse_ctx *p, struct parse_cur *cur)
+{
+	*cur = p->cur;
+	return 1;
+}
+
+int
+rollback(struct parse_ctx *p, struct parse_cur *cur)
+{
+	if (p->e == EPARSE)
+		p->cur = *cur;
+
+	return 0;
+}
+
+int
+opt(struct parse_ctx *p)
+{
+	if (p->e == EPARSE)
+		p->e = 0;
+
+	return !p->e;
+}
+
+/*
+  Combinators and misc meta critters
+*/
+
+/* prod *(SP prod) */
+int
+parse_list(struct parse_ctx *p, parsefn *prod)
+{
+	int res;
+
+	res = prod(p);
+	while(res && parse_2xcombo(p, SP, prod))
+		;
+	return res && opt(p);
+}
+
+/* 1*prod */
+int
+parse_rep(struct parse_ctx *p, parsefn *prod)
+{
+	int res;
+
+	res = prod(p);
+	while(res && prod(p))
+		;
+	return res && opt(p);
+}
+
+/* 1*<any char except those in `except'> */
+int
+parse_repchr(struct parse_ctx *p, char *except)
+{
+	int res;
+	char *t;
+
+	t = p->cur.ntok;
+
+	while (t != p->tokend && binsearch(except, *t) == -1)
+		t++;
+
+	res = t > p->cur.ntok;
+	if (res) {
+		p->cur.ntok = t;
+	} else {
+		p->e = 1;
+	}
+
+	return res;
+}
+
+int
+parse_2xcombo(struct parse_ctx *p, int ilit, parsefn *prod)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, ilit) &&
+	    prod(p) || rollback(p, &s);
+}
+
+int
+parse_3xcombo(struct parse_ctx *p, int ilit1, int ilit2, parsefn *prod)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, ilit1) &&
+	    parse_lit(p, ilit2) && prod(p) || rollback(p, &s);
+}
+
+/*
+  IMAP parsers
+*/
+
+int
+parse_atom(struct parse_ctx *p)
+{
+	return parse_repchr(p, atom_specials);
+}
+
+int
+parse_authtype(struct parse_ctx *p)
+{
+	return parse_atom(p);
+}
+
+int
+parse_badcscode(struct parse_ctx *p)
+{
+	return parse_lit(p, BADCS) && (parse_badcsopt(p) || opt(p));
+}
+
+int
+parse_badcsopt(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, SP) &&
+	    parse_lit(p, OPAR) && parse_list(p, parse_astring) &&
+	    parse_lit(p, CPAR) || rollback(p, &s);
+}
+
+int
+parse_base64(struct parse_ctx *p)
+{
+	/* DO ME */
+}
+
+int
+parse_cap(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, SP) &&
+	    (parse_2xcombo(AUTHEQ, parse_authtype, p) || parse_atom(p)) ||
+	    rollback(p, &s);
+}
+
+int
+parse_capdata(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, CAP) &&
+	    parse_rep(p, parse_cap) || rollback(p, &s);
+}
+
+int
+parse_contreq(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) &&
+	    parse_lit(p, PLUS) && parse_lit(p, SP) &&
+	    (parse_resptext(p) || parse_base64(p)) &&
+	    parse_lit(p, EOL) || rollback(p, &s);
+}
+
+int
+parse_dquotedchar(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, DQUOTE) &&
+	    parse_quotedchar(p) && parse_lit(p, DQUOTE) ||
+	    rollback(p, &s);
+}
+
+int
+parse_escqspec(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, BSLASH) &&
+	    (parse_lit(p, DQUOTE) || parse_lit(p, BSLASH)) ||
+	    rollback(p, &s);
+}
+
+int
+parse_flag(struct parse_ctx *p)
+{
+	return parse_lit(p, ANSWERED) || parse_lit(p, FLAGGED) ||
+	    parse_lit(p, DELETED) || parse_lit(p, SEEN) ||
+	    parse_lit(p, DRAFT) || parse_flkeyword(p) ||
+	    parse_flext(p);
+}
+
+int
+parse_flext(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, BSLASH) &&
+	    parse_atom(p) || rollback(p, &s);
+}
+
+int
+parse_flkeyword(struct parse_ctx *p)
+{
+	return parse_atom(p);
+}
+
+int
+parse_gencode(struct parse_ctx *p)
+{
+	struct parse_cur opt;
+
+	return parse_atom(p) && (mark(p, &opt) &&
+	    parse_lit(p, SP) && parse_genctext(p) ||
+	    rollback(p, &opt) || opt(p));
+}
+
+int
+parse_genctext(struct parse_ctx *p)
+{
+	int res;
+	char *t;
+
+	t = p->cur.ntok;
+
+	while (t != p->tokend && *t >= '\1' && *t <= '\x7f' &&
+	    *t != ']' && *t != '\r' && *t != '\n')
+		t++;
+
+	res = t > p->cur.ntok;
+	if (res)
+		p->cur.ntok = t;
+
+	p->e = !res;
+	return res;
+}
+
+int
+parse_mailbox(struct parse_ctx *p)
+{
+	return parse_lit(p, INBOX) || parse_astring(p);
+}
+
+int
+parse_mbdata(struct parse_ctx *p)
+{
+	return parse_mbflags(p) || parse_mblist(p) ||
+	    parse_mblsub(p) || parse_mbsearch(p) ||
+	    parse_mbstatus(p) || parse_mbexists(p) ||
+	    parse_mbrecent(p);
+}
+
+int
+parse_mbflags(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	rerurn mark(p, &s) && parse_lit(p, FLAGS) &&
+	    parse_lit(p, SP) && parse_lit(p, OPAR) &&
+	    (parse_list(p, parse_flag) || opt(p)) &&
+	    parse_lit(p, CPAR) || rollback(p, &s);
+}
+
+int
+parse_mblist(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, LIST) &&
+	    parse_lit(p, SP) && parse_lit(p, OPAR) &&
+	    (parse_list(p, parse_mblistfl) || opt(p)) &&
+	    parse_list(p, CPAR) && parse_lit(p, SP) &&
+	    (parse_dquotedchar(p) || parse_lit(NIL)) &&
+	    parse_lit(p, SP) && parse_mailbox(p) || rollback(p);
+}
+
+int
+parse_mblistfl(struct parse_ctx *p)
+{
+	/* nice to meet you, `next thing to do after morning coffee' */
+}
+
+int
+parse_nznumber(struct parse_ctx *p)
+{
+	int res;
+	char *t;
+
+	t = p->cur.ntok;
+
+	if (t == p->tokend || *t < '1' || *t > '9')
+		goto exit;
+
+	while (t != p->tokend && *t >= '0' && *t <= '9')
+		t++;
+
+ exit:
+	res = t > p->cur.ntok;
+	if (res)
+		p->cur.ntok = t;
+
+	p->e = !res;
+	return res;
+}
+
+int
+parse_permfl(struct parse_ctx *p)
+{
+	return parse_flag(p) || parse_lit(p, ANYFL);
+}
+
+int
+parse_permflcode(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, PERMFL) &&
+	    parse_lit(p, SP) && parse_lit(p, OPAR) &&
+	    (parse_list(p, parse_permfl) || opt(p)) &&
+	    parse_lit(p, CPAR) || rollback(p, &s);
+}
+
+int
+parse_quotedchar(struct parse_ctx *p)
+{
+	return parse_quotedcharclean(p) || parse_escqspec(p);
+}
+
+int
+parse_quotedcharclean(struct parse_ctx *p)
+{
+	if (p->cur.ntok != p->tokend &&
+	    binsearch(quotedclean, *p->cur.ntok) == -1) {
+		p->cur.ntok++;
+		p->e = 0;
+	} else {
+		p->e = 1;
+	}
+	return !p->e;
+}
+
+int
+parse_respbye(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, BYE) && parse_lit(p, SP) &&
+	    parse_resptext(p) || rollback(p, &s);
+}
+
+int
+parse_respdata(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_lit(p, ASTERISK) &&
+	    parse_lit(p, SP) && (parse_respstate(p) ||
+	    parse_respbye(p) || parse_mbdata(p) || parse_msgdata(p) ||
+	    parse_capdata(p)) && parse_eol(p) || rollback(p &s);
+}
+
+int
+parse_respln(struct parse_ctx *p)
+{
+	return parse_contreq(p) ||
+	    parse_respdata(p) ||
+	    parse_taggedresp(p);
+}
+
+int
+parse_respstate(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && (parse_lit(p, OK) ||
+	    parse_lit(p, NO) || parse_lit(p, BAD)) &&
+	    parse_lit(p, SP) && parse_resptext(p) ||
+	    rollback(p, &s);
+}
+
+int
+parse_resptext(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	mark(p, &s) && parse_lit(p, OBR) &&
+	    parse_resptextcode(p) && parse_lit(p, CBR) &&
+	    parse_lit(p, SP) || rollback(p, &s) || opt(p);
+
+	return parse_text(p);
+}
+
+int
+parse_resptextcode(struct parse_ctx *p)
+{
+	return parse_lit(p, ALERT) || parse_badcscode(p) ||
+	    parse_capdata(p) || parse_lit(p, PARSE) ||
+	    parse_permflcode(p) || parse_lit(p, RO) ||
+	    parse_lit(p, RW) || parse_lit(p, TRYC) ||
+	    parse_uidncode(p) || parse_uidvcode(p) ||
+	    parse_unseencode(p) || parse_gencode(p);
+}
+
+int
+parse_tag(struct parse_ctx *p)
+{
+	return parse_repchr(p, tag_specials);
+}
+
+int
+parse_taggedresp(struct parse_ctx *p)
+{
+	struct parse_cur s;
+
+	return mark(p, &s) && parse_tag(p) && parse_lit(p, SP) &&
+	    parse_respstate(p) && parse_eol(p) ||
+	    rollback(p, &s);
+}
+
+int
+parse_lit(int i, struct parse_ctx *p)
+{
+	size_t left;
+
+	left = p->toklen - (p->cur.ntok - p->tokens);
+	if (left < literals[i].len) {
+		p->e = 1;
+		goto exit;
+	}
+	if (strncasecmp(p->cur.ntok, literals[i].val, literals[i].len) == 0) {
+		if (p->cur.nact == p->actend)) {
+			p->e = 2;
+			goto exit;
+		}
+		*p->cur.nact++ = { ... };
+		p->cur.ntok += literals[i].len;
+		p->e = 0;
+	} else {
+		p->e = 1;
+		goto exit;
+	}
+ exit:
+	return !p->e;
+}
+
+int
+parse_uidncode(struct parse_ctx *p)
+{
+	return parse_3xcombo(UIDNEXT, SP, parse_nznumber);
+}
+
+int
+parse_uidvcode(struct parse_ctx *p)
+{
+	return parse_3xcombo(UIDVAL, SP, parse_nznumber);
+}
+
+int
+parse_unseencode(struct parse_ctx *p)
+{
+	return parse_3xcombo(UNSEEN, SP, parse_nznumber);
+}
diff --git a/tttm.c b/tttm.c
new file mode 100644
index 0000000..ea909b8
--- /dev/null
+++ b/tttm.c
@@ -0,0 +1,60 @@
+enum { TOK_NIL, TOK_ATOM, TOK_SPEC
+int
+imap_readline()
+{
+}
+
+int
+imap_init(struct imap_ctx *c, char *name, char *password)
+{
+	l = imap_readline();
+	if (l.kind != UNTAGGED)
+		return 1;
+	switch (l.cond) {
+	case OK:
+		c.state = NOT_AUTHENTICATED;
+		if (hascap(l))
+			imap_proccap(l.cap);
+		else
+			imap_discocap(c);
+		break;
+	case PREAUTH:
+		c.state = AUTHENTICATED;
+		break;
+	case BYE:
+		return 1;
+	default:
+		return 1;
+	}
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	/*
+		- read greeting and decide on state (OK/PREAUTH/BYE)
+		- auth
+		- ls mailboxes
+		- for each mailbox
+			- open mailbox
+			- fetch all messages (store under /tmp if necessary)
+			- pipe
+			- store +\Deleted
+			- expunge
+	*/
+	ctx = imap_ctxinit();
+	imap_auth(name, password);
+	boxes = imap_list(ctx);
+	for (box = boxes; box; box = box->next) {
+		imap_select(box);
+		while (box->nmsg) {
+			h = imap_fetch(1);
+			pipe(h);
+			imap_delete(h);
+			expunge(ctx);
+		}
+		imap_close(box);
+	}
+	return 0;
+}