/* Copyright (C) 2019-2021 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 #ifdef DECLARE_RPP #define RPP_STDIN 1 char * readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags); #else #include #endif #include "imap.h" #define FETCH_HEADERS \ "Date Subject From Sender Reply-To To Cc Bcc " \ "Message-Id In-Reply-To References " \ "MIME-Version Content-Type Content-Disposition Content-Transfer-Encoding" static void mboxrd(char *header, char *body) { printf("From mboxrd@z Thu Jan 1 00:00:00 1970\n"); for (char *crlf; (crlf = strstr(header, "\r\n")); header = &crlf[2]) { *crlf = '\0'; printf("%s\n", header); } if (!body) return; for (char *crlf; (crlf = strstr(body, "\r\n")); body = &crlf[2]) { *crlf = '\0'; char *from = body; while (*from == '>') from++; if (!strncmp(from, "From ", 5)) { printf(">%s\n", body); } else { printf("%s\n", body); } } printf("\n"); } static void printNums(FILE *file, struct List nums) { for (size_t i = 0; i < nums.len; ++i) { uint32_t num = dataCheck(nums.ptr[i], Number).number; fprintf(file, "%s%" PRIu32, (i ? "," : ""), num); } } int main(int argc, char *argv[]) { int rppFlags = 0; const char *host = NULL; const char *port = "imaps"; const char *mailbox = "INBOX"; const char *subject = NULL; const char *from = NULL; const char *to = NULL; const char *cc = NULL; bool unseen = false; bool idle = false; bool body = true; const char *move = NULL; bool seen = false; int opt; while (0 < (opt = getopt(argc, argv, "C:F:HM:S:T:Uh:im:p:svw"))) { switch (opt) { break; case 'C': cc = optarg; break; case 'F': from = optarg; break; case 'H': body = false; break; case 'M': move = optarg; break; case 'S': subject = optarg; break; case 'T': to = optarg; break; case 'U': unseen = true; break; case 'h': host = optarg; break; case 'i': idle = true; break; case 'm': mailbox = optarg; break; case 'p': port = optarg; break; case 's': seen = true; break; case 'v': imapVerbose = true; break; case 'w': rppFlags |= RPP_STDIN; break; default: return EX_USAGE; } } const char *user = argv[optind]; if (!user) errx(EX_USAGE, "username required"); if (!host) { host = strchr(user, '@'); if (!host) errx(EX_USAGE, "no domain in username"); host++; } char buf[1024]; char *pass = readpassphrase( (rppFlags & RPP_STDIN ? "" : "Password: "), buf, sizeof(buf), rppFlags ); if (!pass) err(EX_UNAVAILABLE, "readpassphrase"); struct Resp resp; struct IMAP imap = imapOpen(host, port); respFree(respOk(imapResp(&imap))); enum Atom login = atom("login"); fprintf(imap.w, "%s LOGIN \"%s\" \"%s\"\r\n", Atoms[login], user, pass); for (; resp = respOk(imapResp(&imap)), resp.tag != login; respFree(resp)); respFree(resp); enum Atom examine = atom("examine"); fprintf( imap.w, "%s %s \"%s\"\r\n", Atoms[examine], (move || seen ? "SELECT" : "EXAMINE"), mailbox ); for (; resp = respOk(imapResp(&imap)), resp.tag != examine; respFree(resp)); respFree(resp); search:; struct List nums = {0}; enum Atom search = atom("search"); fprintf(imap.w, "%s SEARCH CHARSET UTF-8 ALL", Atoms[search]); if (subject) fprintf(imap.w, " SUBJECT \"%s\"", subject); if (from) fprintf(imap.w, " FROM \"%s\"", from); if (to) fprintf(imap.w, " TO \"%s\"", to); if (cc) fprintf(imap.w, " CC \"%s\"", cc); if (unseen) fprintf(imap.w, " UNSEEN"); fprintf(imap.w, "\r\n"); for (; resp = respOk(imapResp(&imap)), resp.tag != search; respFree(resp)) { if (resp.resp != AtomSearch) continue; nums = resp.data; resp.data = (struct List) {0}; } respFree(resp); if (!nums.len) { if (!idle) errx(EX_TEMPFAIL, "no matching messages"); respFree(respOk(imapIdle(&imap, atom("idle")))); goto search; } enum Atom fetch = atom("fetch"); fprintf(imap.w, "%s FETCH ", Atoms[fetch]); printNums(imap.w, nums); fprintf( imap.w, " (BODY[HEADER.FIELDS (" FETCH_HEADERS ")]%s)\r\n", (body ? " BODY[TEXT]" : "") ); for (; resp = respOk(imapResp(&imap)), resp.tag != fetch; respFree(resp)) { if (resp.resp != AtomFetch) continue; if (!resp.data.len) errx(EX_PROTOCOL, "missing fetch data"); struct List items = dataCheck(resp.data.ptr[0], List).list; struct Data header = {0}; struct Data body = {0}; for (size_t i = 0; i + 2 < items.len; ++i) { struct Data item = items.ptr[i]; if (item.type != Atom || item.atom != AtomBody) continue; if (items.ptr[i + 1].type != List) continue; struct List sect = items.ptr[i + 1].list; if (!sect.len || sect.ptr[0].type != Atom) continue; if (sect.ptr[0].atom == AtomHeader) { header = items.ptr[i + 2]; } else if (sect.ptr[0].atom == AtomText) { body = items.ptr[i + 2]; } } mboxrd( dataCheck(header, String).string, (body.type == String ? body.string : NULL) ); } respFree(resp); if (seen) { enum Atom sto = atom("store"); fprintf(imap.w, "%s STORE ", Atoms[sto]); printNums(imap.w, nums); fprintf(imap.w, " +FLAGS.SILENT (\\Seen)\r\n"); for (; resp = respOk(imapResp(&imap)), resp.tag != sto; respFree(resp)); respFree(resp); } if (move) { enum Atom mov = atom("move"); fprintf(imap.w, "%s MOVE ", Atoms[mov]); printNums(imap.w, nums); fprintf(imap.w, " \"%s\"\r\n", move); for (; resp = respOk(imapResp(&imap)), resp.tag != mov; respFree(resp)); respFree(resp); } fprintf(imap.w, "ayy LOGOUT\r\n"); fclose(imap.r); fclose(imap.w); }