/* Copyright (C) 2020 C. 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 #include "archive.h" #include "imap.h" static const char *exportPath(uint32_t uid, const char *type) { static char buf[PATH_MAX]; char str[32]; snprintf(str, sizeof(str), "%" PRIu32, uid); struct Variable vars[] = { { "uid", str }, { "type", type }, {0}, }; templateBuffer(buf, sizeof(buf), PATH_UID, vars, escapePath); return buf; } bool exportFetch(FILE *imap, enum Atom tag, struct List threads) { struct List uids = {0}; listFlatten(&uids, threads); for (size_t i = uids.len - 1; i < uids.len; --i) { uint32_t uid = dataCheck(uids.ptr[i], Number).number; int error = 0 || access(exportPath(uid, "atom"), F_OK) || access(exportPath(uid, "html"), F_OK) || access(exportPath(uid, "mbox"), F_OK); if (!error) uids.ptr[i] = uids.ptr[--uids.len]; } if (!uids.len) { listFree(uids); return false; } fprintf(imap, "%s UID FETCH ", Atoms[tag]); for (size_t i = 0; i < uids.len; ++i) { fprintf(imap, "%s%" PRIu32, (i ? "," : ""), uids.ptr[i].number); } listFree(uids); fprintf( imap, " (UID ENVELOPE BODYSTRUCTURE" " BODY[HEADER.FIELDS (" MBOX_HEADERS ")] BODY[TEXT])\r\n" ); return true; } static void exportMbox( uint32_t uid, const struct Envelope *envelope, const char *header, const char *body ) { const char *path = exportPath(uid, "mbox"); FILE *file = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); int error = 0 || mboxFrom(file) || mboxHeader(file, header) || mboxBody(file, body) || fclose(file); if (error) err(EX_IOERR, "%s", path); char buf[PATH_MAX]; struct Variable vars[] = { { "messageID", envelope->messageID }, { "type", "mbox" }, {0}, }; templateBuffer(buf, sizeof(buf), PATH_MESSAGE, vars, escapePath); unlink(buf); error = link(path, buf); if (error) err(EX_CANTCREAT, "%s", buf); } static bool isInline(const struct BodyPart *part) { if (!bodyPartType(part, "text", "plain")) return false; if (!part->disposition.type) return true; return !strcasecmp(part->disposition.type, "inline"); } static bool isAttachment(const struct BodyPart *part) { if (isInline(part)) return false; return !part->multipart && !part->message.structure; } static void exportAtom( uint32_t uid, const struct Envelope *envelope, const struct BodyPart *structure, struct Data body ) { const char *path = exportPath(uid, "atom"); FILE *file = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); int error = atomEntryOpen(file, envelope); if (error) err(EX_IOERR, "%s", path); const struct BodyPart *part = structure; while (part->multipart) { if (bodyPartType(part, "multipart", "alternative")) { for (size_t i = part->parts.len - 1; i < part->parts.len; --i) { if (!isInline(&part->parts.ptr[i])) continue; part = &part->parts.ptr[i]; body = dataCheck(body, List).list.ptr[i]; break; } if (part->multipart) break; } else { part = &part->parts.ptr[0]; body = dataCheck(body, List).list.ptr[0]; } } if (isInline(part)) { char *content = decodeToString(part, dataCheck(body, String).string); error = atomContent(file, content); if (error) err(EX_IOERR, "%s", path); free(content); } error = atomEntryClose(file) || fclose(file); if (error) err(EX_IOERR, "%s", path); } static const char *sectionName(struct List section) { static char buf[1024]; char str[32]; buf[0] = '\0'; for (size_t i = 0; i < section.len; ++i) { snprintf( str, sizeof(str), "%s%" PRIu32, (i ? "." : ""), dataCheck(section.ptr[i], Number).number ); strlcat(buf, str, sizeof(buf)); } return buf; } static int exportHTMLAttachment( FILE *file, const struct Envelope *envelope, struct List section, const struct BodyPart *part, struct Data body ) { const char *name = paramGet(part->disposition.params, "filename"); if (!name) name = paramGet(part->params, "name"); const char *disposition = part->disposition.type; if (!disposition) disposition = "INLINE"; char path[PATH_MAX]; struct Variable vars[] = { { "messageID", envelope->messageID }, { "section", sectionName(section) }, { "name", (name ? name : "") }, { "disposition", (name ? "" : disposition) }, { ".", (name ? "" : ".") }, { "subtype", (name ? "" : part->subtype) }, {0}, }; templateBuffer(path, sizeof(path), PATH_ATTACHMENT, vars, escapePath); for (char *ch = path; (ch = strchr(ch, '/')); ++ch) { *ch = '\0'; int error = mkdir(path, 0775); if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", path); *ch = '/'; } FILE *attachment = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); int error = 0 || decodeToFile(attachment, part, dataCheck(body, String).string) || fclose(attachment); if (error) err(EX_IOERR, "%s", path); return htmlAttachment(file, part, vars); } static int exportHTMLBody( FILE *file, const struct Envelope *envelope, struct List *section, const struct BodyPart *part, struct Data body ) { if (bodyPartType(part, "multipart", "alternative")) { for (size_t i = part->parts.len - 1; i < part->parts.len; --i) { if (!isInline(&part->parts.ptr[i])) continue; return exportHTMLBody( file, envelope, section, &part->parts.ptr[i], dataCheck(body, List).list.ptr[i] ); } return exportHTMLBody( file, envelope, section, &part->parts.ptr[part->parts.len - 1], dataCheck(body, List).list.ptr[part->parts.len - 1] ); } else if (part->multipart) { int error; bool attached = false; for (size_t i = 0; i < part->parts.len; ++i) { if (attached != isAttachment(&part->parts.ptr[i])) { attached ^= true; error = attached ? htmlAttachmentOpen(file) : htmlAttachmentClose(file); if (error) return error; } struct Data num = { .type = Number, .number = 1 + i }; listPush(section, num); error = exportHTMLBody( file, envelope, section, &part->parts.ptr[i], dataCheck(body, List).list.ptr[i] ); if (error) return error; section->len--; } return (attached ? htmlAttachmentClose(file) : 0); } else if (part->message.structure) { const struct BodyPart *structure = part->message.structure; int error = 0 || htmlMessageOpen(file, part->message.envelope) || exportHTMLBody(file, envelope, section, structure, body) || htmlMessageClose(file); return error; } else if (isInline(part)) { char *content = decodeToString(part, dataCheck(body, String).string); int error = htmlInline(file, part, content); free(content); return error; } else { return exportHTMLAttachment(file, envelope, *section, part, body); } } static void exportHTML( uint32_t uid, const struct Envelope *envelope, const struct BodyPart *structure, struct Data body ) { const char *path = exportPath(uid, "html"); FILE *file = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); int error = htmlMessageOpen(file, envelope); if (error) err(EX_IOERR, "%s", path); struct List section = {0}; error = exportHTMLBody(file, envelope, §ion, structure, body); if (error) err(EX_IOERR, "%s", path); listFree(section); error = htmlMessageClose(file) || fclose(file); if (error) err(EX_IOERR, "%s", path); } static void fetchParts( FILE *imap, struct List *section, const struct BodyPart *structure ) { if (structure->multipart) { for (size_t i = 0; i < structure->parts.len; ++i) { struct Data part = { .type = Number, .number = 1 + i }; listPush(section, part); fetchParts(imap, section, &structure->parts.ptr[i]); section->len--; } } else if ( structure->message.structure && structure->message.structure->multipart ) { fetchParts(imap, section, structure->message.structure); } else { fprintf( imap, " BODY[%s%s]", sectionName(*section), (structure->message.structure ? ".TEXT" : "") ); } } static void checkBodyParts(const struct BodyPart *structure, struct Data body) { if (structure->multipart) { struct List list = dataCheck(body, List).list; if (list.len < structure->parts.len) { errx(EX_PROTOCOL, "missing body parts"); } for (size_t i = 0; i < structure->parts.len; ++i) { checkBodyParts(&structure->parts.ptr[i], list.ptr[i]); } } else if ( structure->message.structure && structure->message.structure->multipart ) { checkBodyParts(structure->message.structure, body); } else if (body.type != String) { errx(EX_PROTOCOL, "missing body part"); } } bool exportData(FILE *imap, enum Atom tag, struct List items) { uint32_t uid = 0; struct Envelope envelope = {0}; struct BodyPart structure = {0}; struct Data bodyHeader = {0}; struct Data bodyText = {0}; struct Data bodyParts = {0}; for (size_t i = 0; i + 1 < items.len; i += 2) { enum Atom name = dataCheck(items.ptr[i], Atom).atom; struct Data data = items.ptr[i + 1]; if (name == AtomUID) { uid = dataCheck(data, Number).number; } else if (name == AtomEnvelope) { parseEnvelope(&envelope, dataCheck(data, List).list); } else if (name == AtomBodyStructure) { parseBodyPart(&structure, dataCheck(data, List).list); } if (name != AtomBody) continue; struct List section = dataCheck(data, List).list; if (!section.len) { errx(EX_PROTOCOL, "missing body data item section"); } i++; if (i + 1 >= items.len) { errx(EX_PROTOCOL, "missing body data item value"); } data = items.ptr[i + 1]; if (section.ptr[0].type == Atom) { name = section.ptr[0].atom; if (name == AtomHeader) { bodyHeader = data; } else if (name == AtomText) { bodyText = data; } continue; } struct Data *dest = &bodyParts; for (size_t i = 0; i < section.len; ++i) { if (section.ptr[i].type != Number) continue; uint32_t num = section.ptr[i].number; if (dest->type != List) { *dest = (struct Data) { .type = List }; } while (dest->list.len < num) { listPush(&dest->list, (struct Data) {0}); } dest = &dest->list.ptr[num - 1]; } // Free with bodyParts: *dest = dataTake(&items.ptr[i + 1]); } if (!uid) { errx(EX_PROTOCOL, "missing UID data item"); } if (!envelope.subject) { errx(EX_PROTOCOL, "missing ENVELOPE data item"); } if (!structure.subtype) { errx(EX_PROTOCOL, "missing BODYSTRUCTURE data item"); } if (bodyParts.type == List) { checkBodyParts(&structure, bodyParts); } if (bodyHeader.type == String && bodyText.type == String) { exportMbox(uid, &envelope, bodyHeader.string, bodyText.string); } bool fetch = false; if (!structure.multipart) { exportAtom(uid, &envelope, &structure, bodyText); exportHTML(uid, &envelope, &structure, bodyText); } else if (bodyParts.type == List) { exportAtom(uid, &envelope, &structure, bodyParts); exportHTML(uid, &envelope, &structure, bodyParts); } else { fetch = true; fprintf( imap, "%s UID FETCH %" PRIu32 " (UID ENVELOPE BODYSTRUCTURE", Atoms[tag], uid ); struct List section = {0}; fetchParts(imap, §ion, &structure); listFree(section); fprintf(imap, ")\r\n"); } envelopeFree(envelope); bodyPartFree(structure); dataFree(bodyParts); return fetch; }