/* Copyright (C) 2020 June McEnroe * * 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 . * * 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 #include #include #include #include #include #include #include #include #include #include FILE *funopen( const void *cookie, int (*readfn)(void *, char *, int), int (*writefn)(void *, const char *, int), fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *) ); #include "imap.h" int getservinfo( const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res ); 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; } struct IMAP 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); struct addrinfo *head; struct addrinfo hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP, }; error = getservinfo(host, port, &hints, &head); if (error) errx(EX_NOHOST, "%s:%s: %s", host, port, gai_strerror(error)); int sock = -1; for (struct addrinfo *ai = head; ai; ai = ai->ai_next) { sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) err(EX_OSERR, "socket"); error = connect(sock, ai->ai_addr, ai->ai_addrlen); if (!error) break; close(sock); sock = -1; } if (sock < 0) err(EX_UNAVAILABLE, "%s:%s", host, port); error = tls_connect_socket( client, sock, (head->ai_canonname ? head->ai_canonname : host) ); if (error) errx(EX_SOFTWARE, "tls_connect_socket: %s", tls_error(client)); freeaddrinfo(head); struct IMAP imap = { .sock = sock, .r = funopen(client, imapRead, NULL, NULL, NULL), .w = funopen(client, NULL, imapWrite, NULL, imapClose), }; if (!imap.r || !imap.w) err(EX_OSERR, "funopen"); setlinebuf(imap.w); return imap; } static void imapLine(struct IMAP *imap) { ssize_t len = getline(&imap->buf, &imap->cap, imap->r); if (len < 0) errx(EX_PROTOCOL, "unexpected eof"); if (len < 1 || imap->buf[len - 1] != '\n') errx(EX_PROTOCOL, "missing LF"); if (len < 2 || imap->buf[len - 2] != '\r') errx(EX_PROTOCOL, "missing CR"); imap->buf[len - 2] = '\0'; imap->ptr = imap->buf; } static struct Data parseAtom(struct IMAP *imap) { size_t len = (*imap->ptr == '.' ? 1 : strcspn(imap->ptr, " .()[]{\"")); struct Data data = { .type = Atom, .atom = atomn(imap->ptr, len), }; imap->ptr += len; return data; } static struct Data parseNumber(struct IMAP *imap) { return (struct Data) { .type = Number, .number = strtoull(imap->ptr, &imap->ptr, 10), }; } static struct Data parseQuoted(struct IMAP *imap) { imap->ptr++; size_t len = strcspn(imap->ptr, "\""); if (imap->ptr[len] != '"') { errx(EX_PROTOCOL, "missing quoted string delimiter"); } struct Data data = { .type = String, .string = strndup(imap->ptr, len), }; if (!data.string) err(EX_OSERR, "strndup"); imap->ptr += len + 1; return data; } static struct Data parseLiteral(struct IMAP *imap) { imap->ptr++; size_t len = strtoull(imap->ptr, &imap->ptr, 10); if (*imap->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->r); if (!n) errx(EX_PROTOCOL, "truncated literal"); imapLine(imap); data.string[len] = '\0'; return data; } static struct Data parseData(struct IMAP *imap); static struct Data parseList(struct IMAP *imap, char close) { if (*imap->ptr) imap->ptr++; struct Data data = { .type = List }; while (*imap->ptr != close) { listPush(&data.list, parseData(imap)); } if (*imap->ptr) imap->ptr++; return data; } static struct Data parseData(struct IMAP *imap) { if (*imap->ptr == ' ') imap->ptr++; if (*imap->ptr == '"') return parseQuoted(imap); if (*imap->ptr == '{') return parseLiteral(imap); if (*imap->ptr == '(') return parseList(imap, ')'); if (*imap->ptr == '[') return parseList(imap, ']'); if (*imap->ptr >= '0' && *imap->ptr <= '9') return parseNumber(imap); if (*imap->ptr) return parseAtom(imap); errx(EX_PROTOCOL, "unexpected eof"); } struct Resp imapResp(struct IMAP *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 (*imap->ptr == ' ') imap->ptr++; resp.text = imap->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 (*imap->ptr == ' ') imap->ptr++; if (*imap->ptr == '[') { data = parseList(imap, ']'); resp.code = data.list; } if (*imap->ptr == ' ') imap->ptr++; resp.text = imap->ptr; } else { data = parseList(imap, '\0'); resp.data = data.list; } return resp; }