/* Copyright (C) 2019, 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 . */ #include "compat.h" #include #include #include #include #include #include #include #include #ifndef NO_READPASSPHRASE_H #include #endif #include "imap.h" #if !defined(DIG_PATH) && !defined(DRILL_PATH) # ifdef __FreeBSD__ # define DRILL_PATH "/usr/bin/drill" # else # define DIG_PATH "dig" # endif #endif #define FETCH_HEADERS \ "Date From To Cc Subject Message-Id In-Reply-To References " \ "Content-Transfer-Encoding" static void mboxrd(char *headers, char *body) { printf("From mboxrd@z Thu Jan 1 00:00:00 1970\n"); for (char *crlf; (crlf = strstr(headers, "\r\n")); headers = &crlf[2]) { *crlf = '\0'; printf("%s\n", headers); } 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 lookup(const char **host, const char **port, const char *domain) { static char buf[1024]; snprintf(buf, sizeof(buf), "_imaps._tcp.%s", domain); int rw[2]; int error = pipe(rw); if (error) err(EX_OSERR, "pipe"); pid_t pid = fork(); if (pid < 0) err(EX_OSERR, "fork"); if (!pid) { close(rw[0]); dup2(rw[1], STDOUT_FILENO); dup2(rw[1], STDERR_FILENO); close(rw[1]); #ifdef DRILL_PATH execlp(DRILL_PATH, DRILL_PATH, buf, "SRV", NULL); err(EX_CONFIG, "%s", DRILL_PATH); #else execlp(DIG_PATH, DIG_PATH, "-t", "SRV", "-q", buf, "+short", NULL); err(EX_CONFIG, "%s", DIG_PATH); #endif } int status; pid = wait(&status); if (pid < 0) err(EX_OSERR, "wait"); close(rw[1]); FILE *pipe = fdopen(rw[0], "r"); if (!pipe) err(EX_IOERR, "fdopen"); fgets(buf, sizeof(buf), pipe); if (ferror(pipe)) err(EX_IOERR, "fgets"); if (!WIFEXITED(status) || WEXITSTATUS(status)) { fprintf(stderr, "%s", buf); exit(WEXITSTATUS(status)); } char *ptr = buf; #ifdef DRILL_PATH for (;;) { char *line = fgets(buf, sizeof(buf), pipe); if (!line || !strcmp(line, ";; ANSWER SECTION:\n")) break; } fgets(buf, sizeof(buf), pipe); if (ferror(pipe)) err(EX_IOERR, "fgets"); ptr = strrchr(buf, '\t'); ptr = (ptr ? ptr + 1 : buf); #endif fclose(pipe); char *dot = strrchr(ptr, '.'); if (dot) *dot = '\0'; strsep(&ptr, " \n"); // priority strsep(&ptr, " \n"); // weight *port = strsep(&ptr, " \n"); *host = strsep(&ptr, " \n"); if (!*host) { *host = domain; *port = "imaps"; } } int main(int argc, char *argv[]) { const char *host = NULL; const char *port = "imaps"; const char *mailbox = "INBOX"; const char *subject = "[PATCH"; const char *from = NULL; const char *to = NULL; const char *cc = NULL; int rppFlags = 0; int opt; while (0 < (opt = getopt(argc, argv, "C:F:S:T:h:m:p:vw"))) { switch (opt) { break; case 'C': cc = optarg; break; case 'F': from = optarg; break; case 'S': subject = optarg; break; case 'T': to = optarg; break; case 'h': host = optarg; break; case 'm': mailbox = optarg; break; case 'p': port = optarg; 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) { const char *domain = strchr(user, '@'); if (!domain) errx(EX_USAGE, "no domain in username"); lookup(&host, &port, &domain[1]); } char buf[1024]; char *pass = readpassphrase( (rppFlags & RPP_STDIN ? "" : "Password: "), buf, sizeof(buf), rppFlags ); if (!pass) err(EX_UNAVAILABLE, "readpassphrase"); enum Atom login = 0; enum Atom examine = atom("examine"); enum Atom headerFields = atom("HEADER.FIELDS"); enum Atom text = atom("TEXT"); FILE *imap = imapOpen(host, port); for (struct Resp resp; resp = imapResp(imap), resp.resp != AtomBye;) { if (resp.resp == AtomNo || resp.resp == AtomBad) { errx(EX_CONFIG, "%s: %s", Atoms[resp.resp], resp.text); } if (!login) { login = atom("login"); fprintf( imap, "%s LOGIN \"%s\" \"%s\"\r\n", Atoms[login], user, pass ); } if (resp.tag == login) { fprintf(imap, "%s EXAMINE \"%s\"\r\n", Atoms[examine], mailbox); } if (resp.tag == examine) { fprintf( imap, "%s SEARCH CHARSET UTF-8 OR " "NOT HEADER Content-Type \"\" " "HEADER Content-Type \"text/plain\"", Atoms[AtomSearch] ); if (subject) fprintf(imap, " SUBJECT \"%s\"", subject); if (from) fprintf(imap, " FROM \"%s\"", from); if (to) fprintf(imap, " TO \"%s\"", to); if (cc) fprintf(imap, " CC \"%s\"", cc); fprintf(imap, "\r\n"); } if (resp.resp == AtomSearch) { if (!resp.data.len) errx(EX_TEMPFAIL, "no matching messages"); fprintf(imap, "%s FETCH ", Atoms[AtomFetch]); for (size_t i = 0; i < resp.data.len; ++i) { struct Data data = resp.data.ptr[i]; if (data.type != Number) { errx(EX_PROTOCOL, "invalid search result"); } fprintf(imap, "%s%" PRIu32, (i ? "," : ""), data.number); } fprintf( imap, " (BODY[HEADER.FIELDS (" FETCH_HEADERS ")] BODY[TEXT])\r\n" ); } if (resp.resp == AtomFetch) { if (!resp.data.len) { errx(EX_PROTOCOL, "no fetch data"); } if (resp.data.ptr[0].type != List) { errx(EX_PROTOCOL, "invalid fetch data"); } struct Data headers = {0}; struct Data body = {0}; struct List items = resp.data.ptr[0].list; for (size_t i = 0; i < items.len; ++i) { struct Data item = items.ptr[i]; if (item.type != List) continue; if (!item.list.len) continue; if (item.list.ptr[0].type != Atom) continue; if (item.list.ptr[0].atom == headerFields) { if (i + 1 < items.len) headers = items.ptr[i + 1]; } if (item.list.ptr[0].atom == text) { if (i + 1 < items.len) body = items.ptr[i + 1]; } } if (headers.type != String) { errx(EX_PROTOCOL, "invalid header data"); } if (body.type != String) { errx(EX_PROTOCOL, "invalid body data"); } mboxrd(headers.string, body.string); } if (resp.tag == AtomFetch) { fprintf(imap, "ayy LOGOUT\r\n"); } respFree(resp); } fclose(imap); }