commit 81c42ca212c93182ef77d20cbb4e58ac4b0de3c7 from: David Williams date: Tue Oct 6 20:00:11 2026 UTC maildir indexing; login answers; compile flags; voice; architecture Change 1 of 3: Give UIDs to message files found in cur/ imapd gave a message a UID only when it found it in new/ or wrote it itself with APPEND, COPY or MOVE. Nothing read cur/, and the new/ scan skipped any name holding ':'. So the mail in a maildir brought from another server was invisible, and every message in a mailbox whose imapd.index was lost disappeared with it, while SELECT answered OK. On the test host, a mailbox with four files in cur/ and no index showed 0 EXISTS, and one whose index had been removed showed 0 EXISTS under a new UIDVALIDITY. Each refresh of the index (SELECT, EXAMINE, STATUS, and an IDLE refresh with something pending) now also reads cur/, looks each base name up in a hash table of the index's names, and gives the files the index does not hold UIDs in name order, keeping the flags in their names. Like mail from new/, each is first rewritten with CRLF line endings, before it has a UID (RFC 9051 section 2.3.1.1). A file with no maildir info gets ":2," added, so it can be found. A file in new/ whose name carries ":2," info is moved to cur/ and indexed there, instead of being skipped. On a 100,000-message mailbox the scan measured 0.13 s warm and 0.19 s straight after a reboot, against an EXAMINE of about 0.40 s. EXPUNGE saves the index before it unlinks the files, so a file it failed to unlink, or one left by a crash in between, would come back as a new message. In a mailbox that already has an index, an unindexed cur/ file flagged T (\Deleted) is therefore left out and logged. A mailbox with no index takes every file. A file put in cur/ while a session is in IDLE is noticed at the next refresh that runs for another reason; imapd.8 says so. Change 2 of 3: Answer login OK only once its mailbox store has started An account's store child is started after its password is accepted, and only then enters the account's maildir. The parent handed the listener its channel to a new store at once, and the listener answered the login OK on receiving it, before the store had started. If the maildir was missing, as when its filesystem was not mounted, the store exited, and the client got OK and then a closed connection, with no BYE and no reason. On the test host every login to an account with no maildir was answered OK and then dropped. The parent now holds back the store channel from every session waiting on a store until the store reports that it is set up. A login is answered OK only once its store is in the maildir, and a store that cannot start refuses each waiting login with NO [UNAVAILABLE], the code RFC 9051 section 7.1 gives for a login whose backend is down. Change 3 of 3: Miscellaneous cold code review items A refused login, for that reason or because the account is at its "account sessions" limit, now gets an untagged BYE with the same response code before its tagged NO, as RFC 9051 section 3.4 asks of a server about to close the connection. The store's fatal message for a missing maildir now names the session. Warning flags: -Wextra and -Wcast-qual leave CFLAGS, and with them the 60 "(void)param;" casts that existed to quiet them. The six libc: strtonum(3) replaces the errno/strtoul/end-pointer dance at 15 sites, keeping the leading-digit guard since strtonum(3) accepts a sign; nitems() replaces sizeof(a)/sizeof(a[0]); getline(3) replaces three fgets-and-peek loops; a new opendirat() replaces nine inline openat/fdopendir/close sequences and, unlike them, preserves errno. Declarations: the anonymous { } scoping blocks are gone, as are declarations initialised from a call; blocks are sorted and aligned. The return conventions are stated once, in listener.h and store_internal.h: the parsers return 0, -1 (BAD) or -2 (NO), the handlers 1 while the session lives and 0 once torn down. Structure: parse_fetch_atts() takes a struct fetch_atts in place of fourteen out-parameters; lock_copy_move_mailboxes()'s fourteen parameters are copy_move_lock()'s four, and parse_content_type()'s eleven are three. parse_search_key_inner() is a keyword table and a switch. session_dispatch_client() and fetch_walk_step() are split into one function per state and per item; store_dispatch() and session_store_dispatch() keep their switches, as the OpenBSD daemons do, but each case is now the receive sequence once, through recv_fixed(), recv_header() and a union of the request types. Voice: "SS" in comments is "section", as the vendored sources write it. RFC citations are out of the text clients see. Log lines take the "%s: what" shape; the operational advice they carried is in imapd(8) and imapd.conf(5), where it already mostly was. Branches that said "can't happen" are either gone, where a same-size copy cannot truncate, or fatalx(), where a logic invariant would have to fail. Four fatalx() calls are new. Reachable conditions that had been labelled impossible keep their branches with honest messages. The listener is one process, one session: a static struct session, no session list, no session_find(). The licence headers of the six files that carried lineage essays are the copyright lines and the ISC text, with the essays moved verbatim to CREDITS. imapd gains -n, -v is cumulative, and the default "listen on" address is "*", so a configuration without the directive binds both address families; imapd.conf.example says so. commit - 58a006b393da74d6143bdf2a5378f87f87bd70be commit + 81c42ca212c93182ef77d20cbb4e58ac4b0de3c7 blob - 152b3f5b275cc7d4db1e7535377d06795b9e40fa blob + 8c49ea0424b08f1a24a575117dad815777540219 --- README.md +++ README.md @@ -2,7 +2,7 @@ A from-scratch IMAP4rev2 ([RFC 9051](https://www.rfc-editor.org/rfc/rfc9051)) server for OpenBSD, written in C in the privilege-separated tradition of `smtpd(8)`, `httpd(8)`, and `ntpd(8)`. No third-party IMAP library. -**Status:** 0.2.2 Pre-release, actively developed. Not a port. See [Getting the source](#getting-the-source) below for the repository. +**Status:** 0.2.3 Pre-release, actively developed. Not a port. See [Getting the source](#getting-the-source) below for the repository. ## What it is @@ -88,7 +88,7 @@ doas rcctl start imapd `SIGHUP` reloads `spool`, `append max`, `attachment max`, `account sessions`, `connections max`, `idle poll`, `lock timeout`, `login grace`, `startups`, and the TLS certificate/key without dropping connected sessions, `listen on` and `credentials` changes still require a restart. See `imapd.conf(5)`. -IPv6 is supported (`listen on ::` or `listen on *` for dual-stack) but not the default, see the `listen on` directive in `imapd.conf(5)`. +imapd listens on both IPv4 and IPv6 by default (`listen on *`); use `listen on 0.0.0.0` for IPv4 only or `listen on ::` for IPv6 only. See the `listen on` directive in `imapd.conf(5)`. ## Getting the source @@ -110,7 +110,7 @@ Report security issues to security@openimapd.dev. Gene ## License -ISC. See the copyright header in each source file. +ISC. See the copyright header in each source file, and `CREDITS` for what each third-party copyright in them is for. ## More blob - /dev/null blob + 7e624d172740c7fcc358be72fe06639b4ba8ea66 (mode 644) --- /dev/null +++ CREDITS @@ -0,0 +1,121 @@ +OpenIMAPD: credits and lineage + +Each file under src/ carries its copyright lines and the ISC licence text. +Where a file carries a copyright other than David Williams', the notes +below say what was borrowed from whom and why that copyright is carried. +They were the files' header comments until 2026-10-05, and are kept here +so the headers can take the form the rest of the tree uses; the +attribution they record is unchanged. + +log.c +----- +This file's log_init()/log_procinit()/log_setverbose()/log_getverbose()/ +vlog()/logit()/log_warn()/log_warnx()/log_info()/log_debug()/fatal()/ +fatalx() API is the same daemon-logging idiom Henning Brauer wrote and +that is carried, nearly verbatim, across essentially every privsep +daemon in the OpenBSD base system (smtpd, bgpd, and many others all +credit him in their own log.c). This file's implementation details +(log_procname storage, log_init()'s parameters) are this project's own, +but the API shape and name are that same shared idiom, so his copyright +is carried forward here too, same rationale as parse.y's copyright +chain. + +log.h +----- +The declarations here, log_init(), log_procinit(), log_setverbose(), +log_getverbose(), log_warn(), log_warnx(), log_info(), log_debug(), +logit(), vlog(), fatal() and fatalx(), have the names, argument types +and return types of those in smtpd's log.h (usr.sbin/smtpd/log.h:26-55). +That is the daemon-logging idiom Henning Brauer wrote and that smtpd, +bgpd and most other privsep daemons in the OpenBSD base system carry +his copyright for. smtpd's _r variants are not declared here. His +copyright is carried forward for the same reason as log.c's. + +imsgev.c +-------- +This file's name and its "struct imsgev" wrapper-around-imsgbuf+ +event(3) concept match Eric Faurot's imsgev.c in OpenBSD's ldapd +(usr.sbin/ldapd/imsgev.c), not a generic/obvious name, so his copyright +is carried forward here even though this file's actual function +signatures and dispatch design (a caller-supplied raw libevent handler, +vs. ldapd's callback+needfd model) were written independently and differ +from his implementation. + +imsgev_add() below is the exception: it is a verbatim copy (up to +whitespace and the choice of iev vs. iev->data as event_set()'s last +argument) of the imsg_event_add() idiom shared across OpenBSD privsep +daemons, traceable to usr.sbin/ospfd/ospfd.c:525-534 (Claudio Jeker +2005, Esben Norby 2004, Henning Brauer 2003-2004) and copied with only +that one-argument variation into dvmrpd.c, npppd.c, and rad.c. Their +copyright is carried forward for that function specifically, same +rationale as log.c's and parse.y's shared-idiom copyright chains. + +parent.c +-------- +This file's boot-time peer-wiring handshake, setup_peer_send() and +setup_done_send(), and the IMSG_SETUP_PEER/IMSG_SETUP_DONE message names +they and imapd.h use, follows smtpd's setup_peers()/ setup_done() +(usr.sbin/smtpd/smtpd.c:861-903) and its identically-named +IMSG_SETUP_PEER/IMSG_SETUP_DONE enumerators (smtpd.h:212-213), which no +other daemon in the OpenBSD base system defines under those names. The +functions here are reworked onto imapd's own struct imsgev and multi- +child bookkeeping rather than copied verbatim, but the message protocol +and handshake shape are smtpd's; see the inline citations at this file's +setup_peer_send() and setup_done_send(). + +send_keymgr_init()'s TLS key permission check, fstat(2), then st_uid +!= 0 and st_mode & (S_IRWXU|S_IRWXG|S_IRWXO) & ~0740, reuses smtpd's +ssl_load_key() check (usr.sbin/smtpd/ssl.c:132-145) verbatim for that +mask and rejection order; only the fixed 0740 bound and the imsg +delivery around it are this file's own. + +listener.c +---------- +The RSA_METHOD/EC_KEY_METHOD engine override below (keymgr_engine_ +init() and everything it installs) adapts smtpd's ca.c +(rsa_engine_init()/ecdsa_engine_init()/rsae_priv_enc()/rsae_priv_ +dec()/ecdsae_do_sign(), ca.c:329-508) to imapd's own imsg conventions: +the OpenSSL API shape leaves little room for independent structure, and +this project's own licensing precedent (log.c, imsgev.c) already treats +a borrow this close as needing the original author's copyright even +where the implementation differs. keymgr_use_fake_private_key()'s two- +line call shape is lifted from smtpd's smtp.c:187-194 (Gilles Chehade, +Pierre-Yves Ritschard, Jacek Masiulaniec); see that function's own +comment. + +keymgr.c +-------- +This file's overall architecture, a dedicated process holding the real +TLS private key, a "fake private key" installed in the TLS-terminating +process instead, and a process-wide OpenSSL RSA_METHOD/EC_KEY_METHOD +engine override that forwards every private-key operation here as a +synchronous imsg round-trip, is smtpd's ca.c (Reyk Floeter, Gilles +Chehade), ported to imapd's own imsg/privsep conventions rather than +copied verbatim. The RSA_METHOD/ EC_KEY_METHOD engine-override code +itself (the code this file's request/reply pair answers) lives in +listener.c, not here; see the listener.c entry above for the matching +attribution. + +keymgr_pubkey_hash() below additionally replicates, byte-for-byte, +smtpd's ssl.c hash_x509()/ssl_pubkey_hash() algorithm (Pierre-Yves +Ritschard, Reyk Floeter, Gilles Chehade): SHA256 of the certificate's +DER-encoded SubjectPublicKeyInfo, formatted "SHA256:" plus lowercase +hex. That exact format is what libtls's own (unexported) +tls_cert_pubkey_hash() tags onto the fake key's OpenSSL ex_data slot 0 +at TLS-config time (lib/libtls/tls.c); this process's key lookup only +works if it derives the identical string from the same certificate. + +parse.y +------- +The parser skeleton below (lgetc()/lungetc()/findeol(), the hand- +written yylex() built on top of them, pushfile()/popfile(), and +symset()/symget()) follows the structure common to ripd's, smtpd's, and +httpd's own parse.y in the OpenBSD base system, all of which carry this +same four-name copyright chain at their root. This file's grammar and +domain-specific rules are original; the above four names are carried +forward for the shared parser-skeleton lineage only. + +mbox_manage.c +------------- +Carries Copyright (c) 2011 Gilles Chehade in its +header. blob - 28d70578cab1d0af11363ec0d4b50101db9f8375 blob + f38b0c9a173bb56afa6dc4a4b3e3ce0e9cce1bea --- src/Makefile +++ src/Makefile @@ -39,9 +39,9 @@ DPADD+= ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} MAN= imapd.8 imapd.conf.5 -CFLAGS+= -Wall -Wextra -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes CFLAGS+= -Wmissing-declarations -Wshadow -Wpointer-arith -CFLAGS+= -Wsign-compare -Wcast-qual -Wcast-align +CFLAGS+= -Wsign-compare -Wcast-align # Explicit -g: bsd.prog.mk's DEBUG?=-g default apparently isn't reaching # the actual compile line in this tree (the crash-diagnosis gdb session blob - 596c4dfcef0195bd1299f70fbea1d32586cee463 blob + 1bab52693fc3f4c5eb7ed1bcee3f795343e99c8b --- src/append_cmd.c +++ src/append_cmd.c @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - #include #include #include @@ -27,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -41,7 +41,7 @@ #include "listener.h" #include "mboxname.h" -/* RFC 9051 SS9 date-time */ +/* RFC 9051 section 9 date-time */ int parse_date_time(const char *s, int64_t *out) { @@ -102,11 +102,17 @@ struct append_parsed { int litnonsync; }; -/* RFC 9051 SS6.3.12 APPEND */ +/* RFC 9051 section 6.3.12 APPEND */ int parse_append_args(char *args, struct append_parsed *out, const char **errmsg) { - char *p = args; + char *p = args, *start, *end, *digits_stop; + const char *sub_err, *errstr; + char datebuf[64]; + char digitsbuf[24]; + size_t dlen, digits_len; + char saved; + int rc; memset(out, 0, sizeof(*out)); *errmsg = NULL; @@ -127,13 +133,8 @@ parse_append_args(char *args, struct append_parsed *ou while (*p == ' ') p++; if (*p == '(') { - char *start = p; - char *end = strchr(p, ')'); - char saved; - int rc; - const char *sub_err; - - if (end == NULL) { + start = p; + if ((end = strchr(p, ')')) == NULL) { *errmsg = "unterminated flag list"; return (-1); } @@ -153,12 +154,8 @@ parse_append_args(char *args, struct append_parsed *ou } if (*p == '"') { - const char *start = p + 1; - char *end = strchr(start, '"'); - size_t dlen; - char datebuf[64]; - - if (end == NULL) { + start = p + 1; + if ((end = strchr(start, '"')) == NULL) { *errmsg = "unterminated date-time string"; return (-1); } @@ -183,48 +180,36 @@ parse_append_args(char *args, struct append_parsed *ou *errmsg = "expected a message literal"; return (-1); } - { - const char *start = p + 1; - char *end = strchr(start, '}'); - char *digits_end; - const char *digits_stop; - char digitsbuf[24]; - size_t digits_len; - unsigned long long litlen; + start = p + 1; + if ((end = strchr(start, '}')) == NULL) { + *errmsg = "malformed literal announcement"; + return (-1); + } - if (end == NULL) { - *errmsg = "malformed literal announcement"; - return (-1); - } + out->litnonsync = (end > start && end[-1] == '+'); + digits_stop = out->litnonsync ? end - 1 : end; + digits_len = (size_t)(digits_stop - start); - out->litnonsync = (end > start && end[-1] == '+'); - digits_stop = out->litnonsync ? end - 1 : end; - digits_len = (size_t)(digits_stop - start); + if (digits_len == 0 || digits_len >= sizeof(digitsbuf)) { + *errmsg = "malformed literal octet count"; + return (-1); + } + memcpy(digitsbuf, start, digits_len); + digitsbuf[digits_len] = '\0'; - if (digits_len == 0 || digits_len >= sizeof(digitsbuf)) { - *errmsg = "malformed literal octet count"; - return (-1); - } - memcpy(digitsbuf, start, digits_len); - digitsbuf[digits_len] = '\0'; + if (digitsbuf[0] < '0' || digitsbuf[0] > '9') { + *errmsg = "malformed literal octet count"; + return (-1); + } + out->litlen = strtonum(digitsbuf, 0, LLONG_MAX, &errstr); + if (errstr != NULL) { + *errmsg = "malformed literal octet count"; + return (-1); + } - /* strtoull(3) accepts a sign */ - if (digitsbuf[0] < '0' || digitsbuf[0] > '9') { - *errmsg = "malformed literal octet count"; - return (-1); - } - errno = 0; - litlen = strtoull(digitsbuf, &digits_end, 10); - if (*digits_end != '\0' || errno == ERANGE) { - *errmsg = "malformed literal octet count"; - return (-1); - } - out->litlen = (uint64_t)litlen; - - if (end[1] != '\0') { - *errmsg = "literal must be the final argument"; - return (-1); - } + if (end[1] != '\0') { + *errmsg = "literal must be the final argument"; + return (-1); } return (0); @@ -249,7 +234,7 @@ cmd_append(struct session *s, const char *tag, char *a if (parsed.litlen > listener_append_max) { char text[96]; - /* RFC 5530 LIMIT, stating the number as RFC 9051 SS7.1 does */ + /* RFC 5530 LIMIT; RFC 9051 section 7.1 states the number */ (void)snprintf(text, sizeof(text), "[LIMIT] message exceeds " "this server's %llu octet limit", (unsigned long long)listener_append_max); @@ -298,7 +283,7 @@ cmd_append(struct session *s, const char *tag, char *a s->literal_remaining = parsed.litlen; s->literal_pending = 1; - /* RFC 9051 SS4.3: "+" only for synchronizing literals */ + /* RFC 9051 section 4.3: "+" only for synchronizing literals */ if (!parsed.litnonsync) { static const char cont[] = "+ Ready for literal data\r\n"; @@ -339,12 +324,14 @@ void session_handle_mbox_appended(struct session *s, const struct imsg_mbox_appended *res) { + char buf[96]; + s->state = s->append_prev_state; if (res->error != MBOX_OP_OK) { if (res->error == MBOX_OP_ERR_NO_SUCH_MAILBOX) session_reply(s, s->pending_tag, "NO", - /* RFC 9051 SS6.3.12 */ + /* RFC 9051 section 6.3.12 */ "[TRYCREATE] no such mailbox"); else if (res->error == MBOX_OP_ERR_BUSY) session_reply(s, s->pending_tag, "NO", @@ -358,12 +345,7 @@ session_handle_mbox_appended(struct session *s, return; } - { - char buf[96]; - - snprintf(buf, sizeof(buf), - "[APPENDUID %u %u] APPEND completed", res->uidvalidity, - res->uid); - session_reply(s, s->pending_tag, "OK", buf); - } + snprintf(buf, sizeof(buf), "[APPENDUID %u %u] APPEND completed", + res->uidvalidity, res->uid); + session_reply(s, s->pending_tag, "OK", buf); } blob - 21da97eeae47457e4c695fb195895f59f99d0393 blob + d5a6e53fece5ca9abd5fc56e604051fabac867ed --- src/auth.c +++ src/auth.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -97,6 +98,7 @@ auth_main(void) int peer_fd; char *slash; char chrootdir[1024]; + char unveil_path[512]; ssize_t n; imsgev_ibuf_init(&ibuf3, 3); @@ -161,22 +163,16 @@ auth_main(void) imsgev_init_from_ibuf(&iev_parent, &ibuf3, auth_dispatch_parent, NULL); - { - char unveil_path[512]; + (void)snprintf(unveil_path, sizeof(unveil_path), "/%s", + cred_file_basename); + if (unveil(unveil_path, "r") == -1) + fatal("unveil %s", unveil_path); + if (unveil(NULL, NULL) == -1) + fatal("unveil lock"); - (void)snprintf(unveil_path, sizeof(unveil_path), "/%s", - cred_file_basename); - if (unveil(unveil_path, "r") == -1) - fatal("unveil %s", unveil_path); - if (unveil(NULL, NULL) == -1) - fatal("unveil lock"); - } - /* no recvfd or sendfd: the one peer fd came before this */ -#ifdef __OpenBSD__ if (pledge("stdio rpath", NULL) == -1) fatal("pledge"); -#endif event_dispatch(); fatalx("auth: exited event loop"); @@ -225,7 +221,6 @@ auth_dispatch(int fd, short event, void *arg) log_warnx("bad IMSG_AUTH_REQUEST"); break; } - /* imsg_get_data() does not NUL-terminate */ req.username[sizeof(req.username) - 1] = '\0'; req.password[sizeof(req.password) - 1] = '\0'; @@ -233,9 +228,8 @@ auth_dispatch(int fd, short event, void *arg) setproctitle("session %u auth", req.session_id); if (auth_session_already_resolved(req.session_id)) { - log_warnx("session %u: IMSG_AUTH_REQUEST for a " - "session already successfully " - "authenticated, refusing", req.session_id); + log_warnx("session %u: IMSG_AUTH_REQUEST: " + "already authenticated", req.session_id); explicit_bzero(req.password, sizeof(req.password)); break; @@ -278,7 +272,6 @@ auth_dispatch(int fd, short event, void *arg) imsg_free(&imsg); } imsgev_rearm_read(iev); - (void)fd; } static void @@ -322,7 +315,6 @@ auth_dispatch_parent(int fd, short event, void *arg) imsg_free(&imsg); } imsgev_rearm_read(iev); - (void)fd; } /* network usernames reach syslog only through here */ @@ -354,9 +346,8 @@ auth_verify(struct imsg_auth_request *req, struct imsg res->ok = 0; auth_safe_name(req->username, overname, sizeof(overname)); - log_info("session %u: authentication failed for \"%s\" " - "(over AUTH_MAX_TRIES, not checked)", req->session_id, - overname); + log_info("session %u: authentication failed for \"%s\" (over " + "AUTH_MAX_TRIES)", req->session_id, overname); return; } @@ -422,8 +413,10 @@ cred_lookup(const char *path, const char *username, st int *maxcost) { FILE *fp; - char line[1024]; + struct stat st; struct cred_entry ce; + char *line = NULL; + size_t linesize = 0; int found = 0, cost; *maxcost = 0; @@ -431,45 +424,26 @@ cred_lookup(const char *path, const char *username, st log_warn("fopen %s", path); return (-1); } - - { - struct stat st; - - if (fstat(fileno(fp), &st) == -1) { - log_warn("fstat %s", path); - fclose(fp); - return (-1); - } - if (!cred_file_secure(&st)) { - log_warnx("%s: insecure (mode %04o, owner uid %u), " - "must be owned by root or the current user and " - "not group-writable, group-executable, or " - "world-accessible; refusing all authentication " - "until this is fixed", - path, (unsigned int)(st.st_mode & 07777), - (unsigned int)st.st_uid); - fclose(fp); - return (-1); - } + if (fstat(fileno(fp), &st) == -1) { + log_warn("fstat %s", path); + fclose(fp); + return (-1); } + if (!cred_file_secure(&st)) { + log_warnx("%s: insecure mode %04o, owner uid %u; " + "authentication refused", + path, (unsigned int)(st.st_mode & 07777), + (unsigned int)st.st_uid); + fclose(fp); + return (-1); + } - while (fgets(line, sizeof(line), fp) != NULL) { + while (getline(&line, &linesize, fp) != -1) { char *p = line; char *fields[5]; + const char *errstr; int i; - char *ep; - unsigned long ulval; - /* fgets(3) splits an over-long line */ - if (strchr(line, '\n') == NULL && - strlen(line) == sizeof(line) - 1) { - log_warnx("%s: over-long line, refusing to parse the " - "credential file", path); - explicit_bzero(line, sizeof(line)); - explicit_bzero(&ce, sizeof(ce)); - fclose(fp); - return (-1); - } line[strcspn(line, "\n")] = '\0'; if (line[0] == '\0' || line[0] == '#') continue; @@ -493,22 +467,16 @@ cred_lookup(const char *path, const char *username, st /* crypt_checkpass(3) passes an empty hash and password */ if (fields[1][0] != '$' || fields[1][1] != '2') continue; - /* strtoul(3) accepts "-1", and uid 0 is root */ + /* strtonum(3) takes "-1"; 0 is root, (uid_t)-1 is not an id */ if (fields[2][0] < '0' || fields[2][0] > '9' || fields[3][0] < '0' || fields[3][0] > '9') continue; - errno = 0; - ulval = strtoul(fields[2], &ep, 10); - if (*ep != '\0' || errno != 0 || ulval == 0 || - ulval >= (unsigned long)(uid_t)-1) + ce.uid = strtonum(fields[2], 1, UINT32_MAX - 1, &errstr); + if (errstr != NULL) continue; - ce.uid = (uid_t)ulval; - errno = 0; - ulval = strtoul(fields[3], &ep, 10); - if (*ep != '\0' || errno != 0 || ulval == 0 || - ulval >= (unsigned long)(gid_t)-1) + ce.gid = strtonum(fields[3], 1, UINT32_MAX - 1, &errstr); + if (errstr != NULL) continue; - ce.gid = (gid_t)ulval; if (strlcpy(ce.maildir, fields[4], sizeof(ce.maildir)) >= sizeof(ce.maildir)) continue; @@ -521,8 +489,7 @@ cred_lookup(const char *path, const char *username, st } } - /* line[] and ce held credential records */ - explicit_bzero(line, sizeof(line)); + freezero(line, linesize); explicit_bzero(&ce, sizeof(ce)); fclose(fp); return (found ? 0 : -1); blob - 295dbfe792a76217279f47edda320d56449332aa blob + 2c7a5f12af243cb6db3e03d426ed7bf90cbc842f --- src/auth_cmd.c +++ src/auth_cmd.c @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - #include #include #include @@ -40,18 +39,15 @@ #include "log.h" #include "listener.h" -/* RFC 9051 SS6.1.1 capability strings, selected by session->tls_active. */ +/* RFC 9051 section 6.1.1 capability strings, by session->tls_active */ #define CAPABILITY_PRE_TLS "IMAP4rev2 STARTTLS LOGINDISABLED ID " \ "CONDSTORE QRESYNC" #define CAPABILITY_POST_TLS "IMAP4rev2 AUTH=PLAIN LOGINDISABLED ID " \ "CONDSTORE QRESYNC" - int cmd_capability(struct session *s, const char *tag, char *args) { - (void)args; - session_untagged(s, s->tls_active ? "CAPABILITY " CAPABILITY_POST_TLS : "CAPABILITY " CAPABILITY_PRE_TLS); @@ -59,13 +55,10 @@ cmd_capability(struct session *s, const char *tag, cha return (1); } - int cmd_noop(struct session *s, const char *tag, char *args) { - (void)args; - - /* RFC 9051 SS6.1.2: the poll; the reply waits for the refresh */ + /* RFC 9051 section 6.1.2: the poll; the reply waits for the refresh */ if (s->state != SESSION_SELECTED || strlcpy(s->pending_tag, tag, sizeof(s->pending_tag)) >= sizeof(s->pending_tag) || session_request_idle_refresh(s) == -1) { @@ -77,20 +70,16 @@ cmd_noop(struct session *s, const char *tag, char *arg return (1); } - int cmd_logout(struct session *s, const char *tag, char *args) { - (void)args; - - /* Exact example text from RFC 9051 SS6.1.3. */ + /* Exact example text from RFC 9051 section 6.1.3. */ session_untagged(s, "BYE IMAP4rev2 Server logging out"); session_reply(s, tag, "OK", "LOGOUT completed"); session_teardown(s, "logout"); return (0); } - int cmd_id(struct session *s, const char *tag, char *args) { @@ -99,7 +88,7 @@ cmd_id(struct session *s, const char *tag, char *args) const char *errmsg; int inquote = 0; - /* RFC 2971 SS3.1: only literals are read; the reply is NIL */ + /* RFC 2971 section 3.1: only literals are read; the reply is NIL */ while (p != NULL && *p != '\0') { if (!inquote && *p == '{') { if (read_literal(&p, &lit, &len, &errmsg) == -1) @@ -119,18 +108,14 @@ cmd_id(struct session *s, const char *tag, char *args) return (1); } - int cmd_login(struct session *s, const char *tag, char *args) { - (void)args; - session_reply(s, tag, "NO", "LOGIN not supported, use AUTHENTICATE PLAIN"); return (1); } - int cmd_starttls(struct session *s, const char *tag, char *args) { @@ -139,7 +124,7 @@ cmd_starttls(struct session *s, const char *tag, char return (1); } if (s->tls_active) { - /* RFC 9051 SS6.2.1: BAD if STARTTLS comes after negotiation */ + /* RFC 9051 section 6.2.1: STARTTLS after negotiation is BAD */ session_reply(s, tag, "BAD", "TLS already active"); return (1); } @@ -160,7 +145,7 @@ cmd_starttls(struct session *s, const char *tag, char return (1); } -/* RFC 4616 SS2: authzid/authcid/passwd up to 255 octets + 2 NULs = 767 */ +/* RFC 4616 section 2: authzid/authcid/passwd up to 255 octets + 2 NULs = 767 */ #define SASL_PLAIN_MAX 768 /* as auth.c's auth_safe_name(): '?' for anything unprintable */ @@ -223,7 +208,7 @@ sasl_plain_finish(struct session *s, const char *tag, passwd = nul + 1; passwdlen = (size_t)rawlen - off - authcidlen - 1; - /* RFC 4616 SS2: empty prep result fails verification, NO not BAD */ + /* RFC 4616 section 2: an empty prep result is NO, not BAD */ if (authcidlen == 0 || passwdlen == 0) { session_reply(s, tag, "NO", "[AUTHENTICATIONFAILED] authentication failed"); @@ -273,14 +258,14 @@ session_handle_auth_continuation(struct session *s, co { s->auth_cont = 0; - /* RFC 9051 SS6.2.2: lone "*" cancels exchange */ + /* RFC 9051 section 6.2.2: lone "*" cancels exchange */ if (strcmp(line, "*") == 0) { session_reply(s, s->pending_tag, "BAD", "AUTHENTICATE cancelled"); return (1); } - return sasl_plain_finish(s, s->pending_tag, line, 0); + return (sasl_plain_finish(s, s->pending_tag, line, 0)); } int @@ -295,7 +280,7 @@ session_handle_idle_continuation(struct session *s, co done = strcasecmp(line, "DONE") == 0; s->refresh_status = done ? "OK" : "BAD"; s->refresh_text = done ? "IDLE terminated" : "expected DONE"; - /* RFC 9051 SS7.5.1: an EXPUNGE in flight goes before the reply */ + /* RFC 9051 section 7.5.1: an EXPUNGE in flight goes before the reply */ if (!s->idle_refresh_pending) { session_reply(s, s->pending_tag, s->refresh_status, s->refresh_text); @@ -304,7 +289,6 @@ session_handle_idle_continuation(struct session *s, co return (1); } - int cmd_authenticate(struct session *s, const char *tag, char *args) { @@ -327,7 +311,7 @@ cmd_authenticate(struct session *s, const char *tag, c initial = (*p != '\0') ? p : NULL; } - /* RFC 9051 SS6.2.2: MUST NOT permit plaintext mechanisms pre-TLS */ + /* RFC 9051 section 6.2.2: no plaintext mechanisms before TLS */ if (!s->tls_active) { /* RFC 5530 PRIVACYREQUIRED: retry after STARTTLS */ session_reply(s, tag, "NO", @@ -343,7 +327,7 @@ cmd_authenticate(struct session *s, const char *tag, c if (initial != NULL) { s->scrub_inbuf = 1; - return sasl_plain_finish(s, tag, initial, 1); + return (sasl_plain_finish(s, tag, initial, 1)); } if (strlcpy(s->pending_tag, tag, sizeof(s->pending_tag)) >= @@ -359,13 +343,12 @@ cmd_authenticate(struct session *s, const char *tag, c int stub_not_implemented(struct session *s, const char *tag, const char *cmdname) { - log_debug("session %u: %s not implemented (store.c's IMSG_MBOX_* " - "wire protocol isn't designed yet)", s->id, cmdname); + log_debug("session %u: %s not implemented", s->id, cmdname); session_reply(s, tag, "NO", "not implemented"); return (1); } -/* RFC 9051 SS6.3.1 ENABLE */ +/* RFC 9051 section 6.3.1 ENABLE */ int cmd_enable(struct session *s, const char *tag, char *args) { blob - 8e8b857481d199891feabf48f917a2758c8c1a62 blob + a5e1441eb3b2b803cc7daebfc68f9faa3ddca484 --- src/envelope.c +++ src/envelope.c @@ -16,7 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - +#include #include #include #include @@ -58,7 +58,6 @@ envbuf_append(char *buf, size_t bufsize, size_t *outle return (0); } - int envbuf_append_str(char *buf, size_t bufsize, size_t *outlen, const char *s) { @@ -81,7 +80,7 @@ envbuf_append_nstring(char *buf, size_t bufsize, size_ for (i = 0; i < vallen; i++) { char c = val[i]; - /* RFC 9051 SS4.3: substituted, not dropped */ + /* RFC 9051 section 4.3: substituted, not dropped */ if (c == '\0' || c == '\r' || c == '\n') c = ' '; if ((c == '"' || c == '\\') && @@ -99,7 +98,7 @@ fail: return (-1); } -/* RFC 5322 SS3.2.4: the index just past the quoted string at i */ +/* RFC 5322 section 3.2.4: the index just past the quoted string at i */ static size_t quoted_end(const char *s, size_t len, size_t i) { @@ -126,7 +125,7 @@ unquoted_find(const char *s, size_t len, size_t i, con return (len); } -/* RFC 5322 SS3.2.2: each comment becomes one space; returns the length */ +/* RFC 5322 section 3.2.2: each comment becomes one space; returns the length */ size_t address_uncomment(char *s, size_t len) { @@ -157,7 +156,7 @@ address_uncomment(char *s, size_t len) return (n); } -/* unquoted in place (RFC 5322 SS3.2.4); a phrase keeps one space per run */ +/* RFC 5322 section 3.2.4, unquoted in place; one space per run in a phrase */ size_t address_cook(char *s, size_t len, int phrase) { @@ -187,7 +186,7 @@ address_cook(char *s, size_t len, int phrase) return (n); } -/* RFC 5322 SS3.4 mailbox, split and cooked in place; -1 if it is none */ +/* RFC 5322 section 3.4 mailbox, split and cooked in place; -1 if it is none */ int address_split(char *tok, size_t toklen, struct address *a) { @@ -210,7 +209,7 @@ address_split(char *tok, size_t toklen, struct address speclen--; } - /* RFC 5322 SS4.4 obs-route, for RFC 9051's at-domain-list */ + /* RFC 5322 section 4.4 obs-route, for RFC 9051's at-domain-list */ if (speclen > 0 && spec[0] == '@') { colon = unquoted_find(spec, speclen, 0, ":"); if (colon == speclen) @@ -237,7 +236,7 @@ address_split(char *tok, size_t toklen, struct address return (0); } -/* RFC 9051 SS7.5.2 address structure; a group marker has no host */ +/* RFC 9051 section 7.5.2 address structure; a group marker has no host */ int envbuf_append_one_address(char *buf, size_t bufsize, size_t *outlen, const struct address *a) @@ -258,7 +257,7 @@ envbuf_append_one_address(char *buf, size_t bufsize, s return (envbuf_append(buf, bufsize, outlen, ")", 1)); } -/* RFC 5322 SS3.4 address-list: the next mailbox, group start or end */ +/* RFC 5322 section 3.4 address-list: the next mailbox, group start or end */ int address_list_next(char *val, size_t vallen, size_t *pos, int *ingroup, char **tok_out, size_t *toklen_out) @@ -324,17 +323,18 @@ address_list_add(char *buf, size_t bufsize, size_t *ou return (0); } -/* RFC 5322 SS4.5.3: every occurrence of the field as one list, or NIL */ +/* RFC 5322 section 4.5.3: every occurrence of the field as one list, or NIL */ static int envbuf_append_address_fields(char *buf, size_t bufsize, size_t *outlen, const char *hdr, size_t hdrlen, const char *field, int *seen) { const char *name, *val; char *unf; - size_t flen = strlen(field), off = 0, namelen, vallen; + size_t flen, off = 0, namelen, vallen; size_t ulen, i; int any = 0, rc; + flen = strlen(field); *seen = 0; while (header_next_field(hdr, hdrlen, &off, &name, &namelen, &val, &vallen) == 1) { @@ -376,7 +376,7 @@ append_field_nstring(char *out, size_t outsize, size_t return (rc); } -/* RFC 9051 SS7.5.2 ENVELOPE; Sender and Reply-To default to From */ +/* RFC 9051 section 7.5.2 ENVELOPE; Sender and Reply-To default to From */ static int envelope_from_header(const char *hdr, size_t hdrlen, char *out, size_t outsize, size_t *outlen) @@ -411,7 +411,7 @@ envelope_from_header(const char *hdr, size_t hdrlen, c if (envbuf_append(out, outsize, outlen, " ", 1) == -1) return (-1); - for (fi = 0; fi < 2; fi++) { + for (fi = 0; fi < nitems(fallback_fields); fi++) { save = *outlen; if (envbuf_append_address_fields(out, outsize, outlen, hdr, hdrlen, fallback_fields[fi], &seen) == -1) @@ -426,7 +426,7 @@ envelope_from_header(const char *hdr, size_t hdrlen, c return (-1); } - for (fi = 0; fi < 3; fi++) { + for (fi = 0; fi < nitems(addr_fields); fi++) { if (envbuf_append_address_fields(out, outsize, outlen, hdr, hdrlen, addr_fields[fi], &seen) == -1) return (-1); @@ -462,8 +462,8 @@ build_envelope(int fd, const char *basename, char **bu return (-1); if (envelope_from_header(hdrbuf, hdrlen, out, sizeof(out), &outlen) == -1) { - log_warnx("session %u: message %s: formatted ENVELOPE exceeds " - "ENVELOPE_MAX, ENVELOPE skipped", session_id, basename); + log_warnx("session %u: message %s: ENVELOPE over " + "ENVELOPE_MAX, skipped", session_id, basename); free(hdrbuf); return (-1); } @@ -479,36 +479,36 @@ build_envelope(int fd, const char *basename, char **bu return (0); } -/* RFC 9051 SS7.5.2 BODYSTRUCTURE; no extension data */ +/* RFC 9051 section 7.5.2 BODYSTRUCTURE; no extension data */ int build_body_structure(int depth, int *nparts_used, const char *hdr, size_t hdrlen, const char *body, size_t bodylen, char *out, size_t outsize, size_t *outlen) { - char type[64], subtype[64]; - char params_fmt[600]; - /* RFC 2046 SS5.1.1 caps boundary at 70 chars, +1 NUL */ - char boundary[70 + 1]; - int has_boundary; + static const char *const cte_known[] = { "7BIT", "8BIT", "BINARY", + "BASE64", "QUOTED-PRINTABLE" }; + struct content_type ct; + char encstr[40], numbuf[32]; + char *idval = NULL, *descval = NULL, *encval = NULL; + const char *enc; + size_t idlen = 0, desclen = 0, enclen = 0, ihdr, ki; + int is_text, is_msg, rc; if (depth > MIME_MAX_DEPTH) return (-1); if (++*nparts_used > MIME_MAX_PARTS) return (-1); - params_fmt[0] = '\0'; - if (parse_content_type(hdr, hdrlen, type, sizeof(type), subtype, - sizeof(subtype), params_fmt, sizeof(params_fmt), boundary, - sizeof(boundary), &has_boundary) == -1) + if (parse_content_type(hdr, hdrlen, &ct) == -1) return (-1); - if (strcasecmp(type, "MULTIPART") == 0) { + if (strcasecmp(ct.type, "MULTIPART") == 0) { size_t part_starts[MIME_MAX_PARTS], part_ends[MIME_MAX_PARTS]; int n, i; - if (!has_boundary) + if (!ct.has_boundary) return (-1); - if (split_multipart(body, bodylen, boundary, part_starts, + if (split_multipart(body, bodylen, ct.boundary, part_starts, part_ends, &n, MIME_MAX_PARTS) == -1) return (-1); @@ -519,7 +519,7 @@ build_body_structure(int depth, int *nparts_used, cons size_t plen = part_ends[i] - part_starts[i]; size_t phdrend; - /* RFC 2046 SS5.1.1: an empty part is legal */ + /* RFC 2046 section 5.1.1: an empty part is legal */ if (plen == 0) phdrend = 0; else if (find_header_body_split(pbuf, plen, @@ -532,132 +532,104 @@ build_body_structure(int depth, int *nparts_used, cons } if (envbuf_append(out, outsize, outlen, " ", 1) == -1) return (-1); - if (envbuf_append_nstring(out, outsize, outlen, subtype, - strlen(subtype)) == -1) + if (envbuf_append_nstring(out, outsize, outlen, ct.subtype, + strlen(ct.subtype)) == -1) return (-1); return (envbuf_append(out, outsize, outlen, ")", 1)); } - { - char *idval = NULL, *descval = NULL, *encval = NULL; - size_t idlen = 0, desclen = 0, enclen = 0, ihdr; - char encstr[40]; - int is_text = (strcasecmp(type, "TEXT") == 0); - int is_msg = strcasecmp(type, "MESSAGE") == 0 && - (strcasecmp(subtype, "RFC822") == 0 || - strcasecmp(subtype, "GLOBAL") == 0); - int rc = 0; + is_text = strcasecmp(ct.type, "TEXT") == 0; + is_msg = strcasecmp(ct.type, "MESSAGE") == 0 && + (strcasecmp(ct.subtype, "RFC822") == 0 || + strcasecmp(ct.subtype, "GLOBAL") == 0); - if (envbuf_append(out, outsize, outlen, "(", 1) == -1) - return (-1); - if (envbuf_append_nstring(out, outsize, outlen, type, - strlen(type)) == -1) - return (-1); - if (envbuf_append(out, outsize, outlen, " ", 1) == -1) - return (-1); - if (envbuf_append_nstring(out, outsize, outlen, subtype, - strlen(subtype)) == -1) - return (-1); - if (envbuf_append(out, outsize, outlen, " ", 1) == -1) - return (-1); - if (envbuf_append(out, outsize, outlen, params_fmt, - strlen(params_fmt)) == -1) - return (-1); - if (envbuf_append(out, outsize, outlen, " ", 1) == -1) - return (-1); + if (envbuf_append(out, outsize, outlen, "(", 1) == -1) + return (-1); + if (envbuf_append_nstring(out, outsize, outlen, ct.type, + strlen(ct.type)) == -1) + return (-1); + if (envbuf_append(out, outsize, outlen, " ", 1) == -1) + return (-1); + if (envbuf_append_nstring(out, outsize, outlen, ct.subtype, + strlen(ct.subtype)) == -1) + return (-1); + if (envbuf_append(out, outsize, outlen, " ", 1) == -1) + return (-1); + if (envbuf_append(out, outsize, outlen, ct.params_fmt, + strlen(ct.params_fmt)) == -1) + return (-1); + if (envbuf_append(out, outsize, outlen, " ", 1) == -1) + return (-1); - if (extract_header_field(hdr, hdrlen, "Content-Id", &idval, - &idlen) == 0) - rc = envbuf_append_nstring(out, outsize, outlen, idval, - idlen); - else - rc = envbuf_append_nstring(out, outsize, outlen, - NULL, 0); - free(idval); - if (rc == -1) - return (-1); - if (envbuf_append(out, outsize, outlen, " ", 1) == -1) - return (-1); + if (extract_header_field(hdr, hdrlen, "Content-Id", &idval, + &idlen) == 0) + rc = envbuf_append_nstring(out, outsize, outlen, idval, idlen); + else + rc = envbuf_append_nstring(out, outsize, outlen, NULL, 0); + free(idval); + if (rc == -1) + return (-1); + if (envbuf_append(out, outsize, outlen, " ", 1) == -1) + return (-1); - if (extract_header_field(hdr, hdrlen, "Content-Description", - &descval, &desclen) == 0) - rc = envbuf_append_nstring(out, outsize, outlen, - descval, desclen); - else - rc = envbuf_append_nstring(out, outsize, outlen, - NULL, 0); - free(descval); - if (rc == -1) - return (-1); - if (envbuf_append(out, outsize, outlen, " ", 1) == -1) - return (-1); + if (extract_header_field(hdr, hdrlen, "Content-Description", + &descval, &desclen) == 0) + rc = envbuf_append_nstring(out, outsize, outlen, descval, + desclen); + else + rc = envbuf_append_nstring(out, outsize, outlen, NULL, 0); + free(descval); + if (rc == -1) + return (-1); + if (envbuf_append(out, outsize, outlen, " ", 1) == -1) + return (-1); - if (extract_header_field(hdr, hdrlen, - "Content-Transfer-Encoding", &encval, &enclen) == 0 && - enclen > 0 && enclen < sizeof(encstr)) { - memcpy(encstr, encval, enclen); - encstr[enclen] = '\0'; - } else if (strlcpy(encstr, "7BIT", sizeof(encstr)) >= - sizeof(encstr)) { - return (-1); + if (extract_header_field(hdr, hdrlen, "Content-Transfer-Encoding", + &encval, &enclen) == 0 && enclen > 0 && enclen < sizeof(encstr)) { + memcpy(encstr, encval, enclen); + encstr[enclen] = '\0'; + } else + strlcpy(encstr, "7BIT", sizeof(encstr)); + free(encval); + enc = encstr; + for (ki = 0; ki < nitems(cte_known); ki++) { + if (strcasecmp(encstr, cte_known[ki]) == 0) { + enc = cte_known[ki]; + break; } - free(encval); - { - static const char *const known[] = { "7BIT", "8BIT", - "BINARY", "BASE64", "QUOTED-PRINTABLE" }; - size_t ki; + } + if (envbuf_append_nstring(out, outsize, outlen, enc, strlen(enc)) == -1) + return (-1); + if (envbuf_append(out, outsize, outlen, " ", 1) == -1) + return (-1); - for (ki = 0; ki < 5; ki++) { - if (strcasecmp(encstr, known[ki]) == 0) { - if (strlcpy(encstr, known[ki], - sizeof(encstr)) >= sizeof(encstr)) - return (-1); - break; - } - } - } - if (envbuf_append_nstring(out, outsize, outlen, encstr, - strlen(encstr)) == -1) - return (-1); - if (envbuf_append(out, outsize, outlen, " ", 1) == -1) - return (-1); + snprintf(numbuf, sizeof(numbuf), "%zu", bodylen); + if (envbuf_append_str(out, outsize, outlen, numbuf) == -1) + return (-1); - { - char numbuf[32]; + /* RFC 9051 section 7.5.2 body-type-msg: envelope, body, lines */ + if (is_msg && (bodylen == 0 || + find_header_body_split(body, bodylen, &ihdr) == -1 || + envbuf_append(out, outsize, outlen, " ", 1) == -1 || + envelope_from_header(body, ihdr, out, outsize, outlen) == -1 || + envbuf_append(out, outsize, outlen, " ", 1) == -1 || + build_body_structure(depth + 1, nparts_used, body, ihdr, + body + ihdr, bodylen - ihdr, out, outsize, outlen) == -1)) + return (-1); - snprintf(numbuf, sizeof(numbuf), "%zu", bodylen); - if (envbuf_append_str(out, outsize, outlen, - numbuf) == -1) - return (-1); - } + if (is_text || is_msg) { + size_t lines = 0, li; - /* RFC 9051 SS7.5.2 body-type-msg: envelope, body, lines */ - if (is_msg && (bodylen == 0 || - find_header_body_split(body, bodylen, &ihdr) == -1 || - envbuf_append(out, outsize, outlen, " ", 1) == -1 || - envelope_from_header(body, ihdr, out, outsize, - outlen) == -1 || - envbuf_append(out, outsize, outlen, " ", 1) == -1 || - build_body_structure(depth + 1, nparts_used, body, ihdr, - body + ihdr, bodylen - ihdr, out, outsize, outlen) == -1)) - return (-1); - - if (is_text || is_msg) { - size_t lines = 0, li; - char numbuf2[32]; - - for (li = 0; li < bodylen; li++) { - if (body[li] == '\n') - lines++; - } - snprintf(numbuf2, sizeof(numbuf2), " %zu", lines); - if (envbuf_append_str(out, outsize, outlen, - numbuf2) == -1) - return (-1); + for (li = 0; li < bodylen; li++) { + if (body[li] == '\n') + lines++; } - - return (envbuf_append(out, outsize, outlen, ")", 1)); + snprintf(numbuf, sizeof(numbuf), " %zu", lines); + if (envbuf_append_str(out, outsize, outlen, numbuf) == -1) + return (-1); } + + return (envbuf_append(out, outsize, outlen, ")", 1)); } int blob - e04d21fe95064d78b9b7c40c63a0569221f683f7 blob + da4556a01b07ad01066be08de5e5a4b7ec0b1a8f --- src/fetch_cmd.c +++ src/fetch_cmd.c @@ -39,18 +39,18 @@ #include "log.h" #include "listener.h" +/* RFC 9051 section 9 nz-number: digit-nz first, so no sign or leading zero */ int parse_nz_number(const char *str, uint32_t *out) { - unsigned long v; - char *end; + const char *errstr; + long long v; - if (str == NULL || *str == '\0' || *str == '0') + if (str == NULL || *str < '1' || *str > '9') return (-1); - errno = 0; - v = strtoul(str, &end, 10); - if (*end != '\0' || errno == ERANGE || v == 0 || v > UINT32_MAX) + v = strtonum(str, 1, UINT32_MAX, &errstr); + if (errstr != NULL) return (-1); *out = (uint32_t)v; @@ -101,7 +101,7 @@ parse_one_seq_range(const char *tok, struct seq_range return (0); } -/* RFC 9051 SS9 sequence-set */ +/* RFC 9051 section 9 sequence-set */ int parse_sequence_set(const char *text, struct seq_range ranges[SEQSET_MAX_RANGES], uint32_t *nranges, const char **errmsg) @@ -146,7 +146,7 @@ parse_sequence_set(const char *text, struct seq_range if (*p == '\0') break; - p++; /* skip ',' */ + p++; } *nranges = n; @@ -193,7 +193,7 @@ fetch_att_tok(char *str, char **savep) return (start); } -/* RFC 9051 SS9 HEADER.FIELDS[.NOT]; -1 is BAD */ +/* RFC 9051 section 9 HEADER.FIELDS[.NOT]; -1 is BAD */ int parse_header_fields_att(const char *inner, int *not_out, char *fields_out, size_t fields_outsize) @@ -257,7 +257,7 @@ parse_header_fields_att(const char *inner, int *not_ou return (0); } -/* RFC 9051 SS6.4.5.1 section-part */ +/* RFC 9051 section 6.4.5.1 section-part */ int section_part_valid(const char *s) { @@ -280,12 +280,12 @@ section_part_valid(const char *s) return (0); s++; if (*s == '\0') - return (0); /* trailing dot */ + return (0); } return (1); } -/* RFC 9051 SS6.4.5 */ +/* RFC 9051 section 6.4.5 */ int parse_partial_suffix(const char *s, int *has_partial_out, uint32_t *start_out, uint32_t *count_out) @@ -326,11 +326,23 @@ parse_partial_suffix(const char *s, int *has_partial_o return (0); } +/* RFC 9051 section 6.4.5 fetch-att list, parsed */ +struct fetch_atts { + uint32_t attrs; + int degraded; + int bodystructure_full; + int header_fields_not; + int has_partial; + uint32_t partial_start; + uint32_t partial_count; + char header_fields[HEADER_FIELDS_MAX]; + char header_fields_label[HEADER_FIELDS_LABEL_MAX]; + char section_part[SECTION_PART_MAX]; +}; + static int -parse_body_peek_section_tok(const char *bracket_start, uint32_t *attrs_inout, - char *section_part_out, size_t section_part_outsize, - int *has_partial_out, uint32_t *partial_start_out, - uint32_t *partial_count_out, int *degraded_out, const char **errmsg) +parse_body_peek_section_tok(const char *bracket_start, struct fetch_atts *fa, + int *degraded_out, const char **errmsg) { char *close; char inner[SECTION_PART_MAX]; @@ -350,21 +362,21 @@ parse_body_peek_section_tok(const char *bracket_start, suffix = close + 1; if (suffix[0] != '\0' && - parse_partial_suffix(suffix, has_partial_out, partial_start_out, - partial_count_out) == -1) { + parse_partial_suffix(suffix, &fa->has_partial, &fa->partial_start, + &fa->partial_count) == -1) { *errmsg = "malformed range"; return (-1); } if (inner[0] == '\0') { - *attrs_inout |= MBOX_FETCH_BODY_WHOLE; + fa->attrs |= MBOX_FETCH_BODY_WHOLE; } else if (strcasecmp(inner, "TEXT") == 0) { - *attrs_inout |= MBOX_FETCH_BODY_TEXT; + fa->attrs |= MBOX_FETCH_BODY_TEXT; } else if (section_part_valid(inner)) { - *attrs_inout |= MBOX_FETCH_BODY_PART; - if (strlcpy(section_part_out, inner, section_part_outsize) >= - section_part_outsize) { - *attrs_inout &= ~MBOX_FETCH_BODY_PART; + fa->attrs |= MBOX_FETCH_BODY_PART; + if (strlcpy(fa->section_part, inner, + sizeof(fa->section_part)) >= sizeof(fa->section_part)) { + fa->attrs &= ~MBOX_FETCH_BODY_PART; *degraded_out = 1; return (0); } @@ -378,19 +390,18 @@ parse_body_peek_section_tok(const char *bracket_start, } static int -parse_body_peek_header_fields_tok(const char *sec, uint32_t *attrs_inout, - int *header_fields_not_out, char *header_fields_out, - size_t header_fields_outsize, char *header_fields_label_out, - size_t header_fields_label_outsize, const char **errmsg) +parse_body_peek_header_fields_tok(const char *sec, struct fetch_atts *fa, + const char **errmsg) { - size_t len = strlen(sec); + size_t len; char inner[HEADER_FIELDS_LABEL_MAX]; + len = strlen(sec); if (len < 1 || sec[len - 1] != ']') { *errmsg = "malformed HEADER.FIELDS section"; return (-1); } - if (*attrs_inout & MBOX_FETCH_HEADER_FIELDS) + if (fa->attrs & MBOX_FETCH_HEADER_FIELDS) /* already captured one, ignore any further duplicates */ return (0); @@ -401,17 +412,18 @@ parse_body_peek_header_fields_tok(const char *sec, uin memcpy(inner, sec, len - 1); inner[len - 1] = '\0'; - if (parse_header_fields_att(inner, header_fields_not_out, - header_fields_out, header_fields_outsize) == -1) { + if (parse_header_fields_att(inner, &fa->header_fields_not, + fa->header_fields, sizeof(fa->header_fields)) == -1) { *errmsg = "malformed HEADER.FIELDS section"; return (-1); } - if (strlcpy(header_fields_label_out, inner, - header_fields_label_outsize) >= header_fields_label_outsize) { + if (strlcpy(fa->header_fields_label, inner, + sizeof(fa->header_fields_label)) >= + sizeof(fa->header_fields_label)) { *errmsg = "HEADER.FIELDS section too long"; return (-1); } - *attrs_inout |= MBOX_FETCH_HEADER_FIELDS; + fa->attrs |= MBOX_FETCH_HEADER_FIELDS; return (1); } @@ -426,35 +438,17 @@ body_section(const char *tok, int *plain) return (NULL); } -/* RFC 9051 SS6.4.5 fetch-att; unsupported items are skipped */ +/* RFC 9051 section 6.4.5 fetch-att; unsupported items are skipped */ int -parse_fetch_atts(char *spec, uint32_t *attrs_out, int *degraded_out, - int *header_fields_not_out, char *header_fields_out, - size_t header_fields_outsize, char *header_fields_label_out, - size_t header_fields_label_outsize, int *bodystructure_full_out, - char *section_part_out, size_t section_part_outsize, - int *has_partial_out, uint32_t *partial_start_out, - uint32_t *partial_count_out, const char **errmsg) +parse_fetch_atts(char *spec, struct fetch_atts *fa, const char **errmsg) { char *p, *tok, *save; const char *sec; size_t len; - uint32_t attrs = 0; - int degraded = 0, plain, d; - int has_partial = 0; - uint32_t partial_start = 0, partial_count = 0; + int plain, d; *errmsg = NULL; - *attrs_out = 0; - *degraded_out = 0; - *header_fields_not_out = 0; - header_fields_out[0] = '\0'; - header_fields_label_out[0] = '\0'; - *bodystructure_full_out = 0; - section_part_out[0] = '\0'; - *has_partial_out = 0; - *partial_start_out = 0; - *partial_count_out = 0; + memset(fa, 0, sizeof(*fa)); if (spec == NULL || *spec == '\0') { *errmsg = "missing message data item(s)"; @@ -475,87 +469,75 @@ parse_fetch_atts(char *spec, uint32_t *attrs_out, int for (tok = fetch_att_tok(p, &save); tok != NULL; tok = fetch_att_tok(NULL, &save)) { if (strcasecmp(tok, "FAST") == 0) { - /* RFC 9051 SS6.4.5 FAST macro */ - attrs |= MBOX_FETCH_FLAGS | MBOX_FETCH_INTERNALDATE | - MBOX_FETCH_RFC822_SIZE; + fa->attrs |= MBOX_FETCH_FLAGS | + MBOX_FETCH_INTERNALDATE | MBOX_FETCH_RFC822_SIZE; } else if (strcasecmp(tok, "ALL") == 0) { /* FAST + ENVELOPE */ - attrs |= MBOX_FETCH_FLAGS | MBOX_FETCH_INTERNALDATE | - MBOX_FETCH_RFC822_SIZE | MBOX_FETCH_ENVELOPE; + fa->attrs |= MBOX_FETCH_FLAGS | + MBOX_FETCH_INTERNALDATE | MBOX_FETCH_RFC822_SIZE | + MBOX_FETCH_ENVELOPE; } else if (strcasecmp(tok, "FULL") == 0) { /* ALL + bare BODY */ - attrs |= MBOX_FETCH_FLAGS | MBOX_FETCH_INTERNALDATE | - MBOX_FETCH_RFC822_SIZE | MBOX_FETCH_ENVELOPE | - MBOX_FETCH_BODYSTRUCTURE; + fa->attrs |= MBOX_FETCH_FLAGS | + MBOX_FETCH_INTERNALDATE | MBOX_FETCH_RFC822_SIZE | + MBOX_FETCH_ENVELOPE | MBOX_FETCH_BODYSTRUCTURE; } else if (strcasecmp(tok, "FLAGS") == 0) { - attrs |= MBOX_FETCH_FLAGS; + fa->attrs |= MBOX_FETCH_FLAGS; } else if (strcasecmp(tok, "UID") == 0) { - attrs |= MBOX_FETCH_UID; + fa->attrs |= MBOX_FETCH_UID; } else if (strcasecmp(tok, "INTERNALDATE") == 0) { - attrs |= MBOX_FETCH_INTERNALDATE; + fa->attrs |= MBOX_FETCH_INTERNALDATE; } else if (strcasecmp(tok, "RFC822.SIZE") == 0) { - attrs |= MBOX_FETCH_RFC822_SIZE; + fa->attrs |= MBOX_FETCH_RFC822_SIZE; } else if (strcasecmp(tok, "MODSEQ") == 0) { - /* RFC 7162 SS3.1.4.2 */ - attrs |= MBOX_FETCH_MODSEQ; + fa->attrs |= MBOX_FETCH_MODSEQ; } else if ((sec = body_section(tok, &plain)) != NULL) { d = 0; if (strcasecmp(sec, "HEADER]") == 0) - attrs |= MBOX_FETCH_BODY_HEADER; + fa->attrs |= MBOX_FETCH_BODY_HEADER; else if (strncasecmp(sec, "HEADER.FIELDS", strlen("HEADER.FIELDS")) == 0) { - if (parse_body_peek_header_fields_tok(sec, - &attrs, header_fields_not_out, - header_fields_out, header_fields_outsize, - header_fields_label_out, - header_fields_label_outsize, + if (parse_body_peek_header_fields_tok(sec, fa, errmsg) == -1) return (-1); - } else if (parse_body_peek_section_tok(sec, &attrs, - section_part_out, section_part_outsize, - &has_partial, &partial_start, &partial_count, - &d, errmsg) == -1) + } else if (parse_body_peek_section_tok(sec, fa, &d, + errmsg) == -1) return (-1); if (plain && !d) - attrs |= MBOX_FETCH_SET_SEEN; - degraded |= d; + fa->attrs |= MBOX_FETCH_SET_SEEN; + fa->degraded |= d; } else if (strcasecmp(tok, "ENVELOPE") == 0) { - /* SS7.5.2; no .PEEK, no \Seen effect */ - attrs |= MBOX_FETCH_ENVELOPE; + /* section 7.5.2; no .PEEK, no \Seen effect */ + fa->attrs |= MBOX_FETCH_ENVELOPE; } else if (strcasecmp(tok, "BODY") == 0 || strcasecmp(tok, "BODYSTRUCTURE") == 0) { - attrs |= MBOX_FETCH_BODYSTRUCTURE; - *bodystructure_full_out = + fa->attrs |= MBOX_FETCH_BODYSTRUCTURE; + fa->bodystructure_full = (strcasecmp(tok, "BODYSTRUCTURE") == 0); } else if (strncasecmp(tok, "BODY", 4) == 0 || strcasecmp(tok, "RFC822") == 0 || strcasecmp(tok, "RFC822.HEADER") == 0 || strcasecmp(tok, "RFC822.TEXT") == 0) { /* other BODY forms and RFC822 items: unimplemented */ - degraded = 1; + fa->degraded = 1; } else { *errmsg = "unknown message data item"; return (-1); } } - if (attrs == 0) { + if (fa->attrs == 0) { *errmsg = "cannot fetch that message content yet, " "supported: FLAGS/UID/INTERNALDATE/RFC822.SIZE/MODSEQ/" "ENVELOPE/(BODY|BODYSTRUCTURE)/BODY[...]/BODY.PEEK[...]"; return (-2); } - /* RFC 9051 SS6.4.5: HEADER wins over HEADER.FIELDS */ - if ((attrs & MBOX_FETCH_BODY_HEADER) && - (attrs & MBOX_FETCH_HEADER_FIELDS)) - attrs &= ~MBOX_FETCH_HEADER_FIELDS; + /* RFC 9051 section 6.4.5: HEADER wins over HEADER.FIELDS */ + if ((fa->attrs & MBOX_FETCH_BODY_HEADER) && + (fa->attrs & MBOX_FETCH_HEADER_FIELDS)) + fa->attrs &= ~MBOX_FETCH_HEADER_FIELDS; - *attrs_out = attrs; - *degraded_out = degraded; - *has_partial_out = has_partial; - *partial_start_out = partial_start; - *partial_count_out = partial_count; return (0); } @@ -564,7 +546,7 @@ const char *fetch_month_names[12] = { "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; -/* RFC 9051 SS9 date-time, always "+0000": no tzdata in the chroot */ +/* RFC 9051 section 9 date-time, always "+0000": no tzdata in the chroot */ void format_internaldate(int64_t ts, char *out, size_t outsize) { @@ -572,10 +554,7 @@ format_internaldate(int64_t ts, char *out, size_t outs time_t t = (time_t)ts; if (gmtime_r(&t, &tm) == NULL) { - if (strlcpy(out, "01-Jan-1970 00:00:00 +0000", outsize) >= - outsize) - log_warnx("format_internaldate: fallback string " - "truncated, caller's buffer too small"); + strlcpy(out, "01-Jan-1970 00:00:00 +0000", outsize); return; } @@ -636,7 +615,6 @@ session_write_file_range(struct session *s, int fd, ui } } -/* RFC 9051 SS7.5.2 */ void session_send_fetch_response(struct session *s, struct imsg_mbox_fetch_meta *meta) @@ -659,7 +637,7 @@ session_send_fetch_response(struct session *s, fetch_append(buf, sizeof(buf), &len, "%u FETCH (", meta->seqno); - /* RFC 9051 SS6.4.5, RFC 7162 SS3.2.4: report a \Seen this set */ + /* RFC 9051 section 6.4.5, RFC 7162 section 3.2.4: FLAGS if \Seen set */ if ((s->fetch_attrs & MBOX_FETCH_FLAGS) || meta->seen_set) { fetch_append(buf, sizeof(buf), &len, "FLAGS (%s)", meta->flags); need_sp = 1; @@ -681,7 +659,7 @@ session_send_fetch_response(struct session *s, need_sp = 1; } if ((s->fetch_attrs & MBOX_FETCH_MODSEQ) || cs) { - /* RFC 7162 SS3.1.4.2 fetch-mod-resp */ + /* RFC 7162 section 3.1.4.2 fetch-mod-resp */ fetch_append(buf, sizeof(buf), &len, "%sMODSEQ (%llu)", need_sp ? " " : "", (unsigned long long)meta->modseq); need_sp = 1; @@ -801,7 +779,7 @@ session_send_fetch_response(struct session *s, } } -/* RFC 9051 SS6.4.6 STORE echo */ +/* RFC 9051 section 6.4.6 STORE echo */ void session_send_store_fetch_response(struct session *s, const struct imsg_mbox_fetch_meta *meta) @@ -809,7 +787,7 @@ session_send_store_fetch_response(struct session *s, char buf[MBOX_FLAGS_MAX + 96]; size_t len; - /* RFC 9051 SS6.4.9: UID STORE echoes UID after FLAGS */ + /* RFC 9051 section 6.4.9: UID STORE echoes UID after FLAGS */ len = (size_t)snprintf(buf, sizeof(buf), "%u FETCH (FLAGS (%s)", meta->seqno, meta->flags); if (s->cmd_by_uid && len < sizeof(buf)) @@ -824,7 +802,7 @@ session_send_store_fetch_response(struct session *s, session_untagged(s, buf); } -/* RFC 4466, RFC 7162 SS3.1.4.1 and SS3.2.6 modifiers */ +/* RFC 4466, RFC 7162 section 3.1.4.1 and section 3.2.6 modifiers */ int parse_fetch_modifiers(char *modspec, struct imsg_mbox_fetch *req, const struct session *s, int by_uid, int *want_vanished, @@ -846,24 +824,21 @@ parse_fetch_modifiers(char *modspec, struct imsg_mbox_ for (tok = strtok_r(p, " ", &save); tok != NULL; tok = strtok_r(NULL, " ", &save)) { if (strcasecmp(tok, "CHANGEDSINCE") == 0) { - const char *valtok = strtok_r(NULL, " ", &save); - char *ep; + const char *valtok, *errstr; + valtok = strtok_r(NULL, " ", &save); if (valtok == NULL) { *errmsg = "CHANGEDSINCE requires a " "mod-sequence value"; return (-1); } - /* strtoull(3) accepts a sign */ if (*valtok < '0' || *valtok > '9') { *errmsg = "invalid CHANGEDSINCE mod-sequence"; return (-1); } - errno = 0; - req->changedsince = strtoull(valtok, &ep, 10); - if (*ep != '\0' || errno != 0 || - req->changedsince == 0 || - req->changedsince > MODSEQ_MAX) { + req->changedsince = strtonum(valtok, 1, MODSEQ_MAX, + &errstr); + if (errstr != NULL) { *errmsg = "invalid CHANGEDSINCE mod-sequence"; return (-1); } @@ -871,14 +846,14 @@ parse_fetch_modifiers(char *modspec, struct imsg_mbox_ req->attrs |= MBOX_FETCH_MODSEQ; } else if (strcasecmp(tok, "VANISHED") == 0) { if (!by_uid) { - /* RFC 7162 SS3.2.6: BAD */ + /* RFC 7162 section 3.2.6: BAD */ *errmsg = "VANISHED is only valid as a UID " - "FETCH modifier (RFC 7162 SS3.2.6)"; + "FETCH modifier"; return (-1); } if (!s->qresync_enabled) { *errmsg = "VANISHED requires ENABLE QRESYNC " - "first (RFC 7162 SS3.2.6)"; + "first"; return (-1); } *want_vanished = 1; @@ -891,7 +866,7 @@ parse_fetch_modifiers(char *modspec, struct imsg_mbox_ return (0); } -/* RFC 9051 SS4.3: FETCH's literals, header-fld-names, as quoted strings */ +/* RFC 9051 section 4.3: FETCH's literals (header-fld-names) as quoted */ static int fetch_unliteral(char *spec, char *out, size_t outsize, const char **errmsg) { @@ -937,29 +912,22 @@ fetch_unliteral(char *spec, char *out, size_t outsize, int cmd_fetch(struct session *s, const char *tag, char *args) { - return fetch_dispatch(s, tag, args, 0); + return (fetch_dispatch(s, tag, args, 0)); } -/* RFC 9051 SS6.4.9: UID FETCH forces UID */ +/* RFC 9051 section 6.4.9: UID FETCH forces UID */ int fetch_dispatch(struct session *s, const char *tag, char *args, int by_uid) { struct imsg_mbox_fetch req; - const char *seqtok; - char *attspec, *modspec; + struct fetch_atts fa; struct seq_range ranges[SEQSET_MAX_RANGES]; - uint32_t nranges, attrs; - int rc, want_vanished = 0, degraded; - int header_fields_not = 0; - char header_fields[HEADER_FIELDS_MAX]; - char header_fields_label[HEADER_FIELDS_LABEL_MAX]; - char unlit[SESSION_INBUF_MAX]; - int bodystructure_full = 0; - char section_part[SECTION_PART_MAX]; - int has_partial = 0; - uint32_t partial_start = 0, partial_count = 0; - const char *errmsg; + const char *seqtok, *errmsg; const char *cmdname = by_uid ? "UID FETCH" : "FETCH"; + char *attspec, *modspec; + char unlit[SESSION_INBUF_MAX]; + uint32_t nranges; + int rc, want_vanished = 0; if (args == NULL) { session_reply(s, tag, "BAD", @@ -994,11 +962,7 @@ fetch_dispatch(struct session *s, const char *tag, cha if (*modspec == '\0') modspec = NULL; - rc = parse_fetch_atts(attspec, &attrs, °raded, &header_fields_not, - header_fields, sizeof(header_fields), header_fields_label, - sizeof(header_fields_label), &bodystructure_full, section_part, - sizeof(section_part), &has_partial, &partial_start, - &partial_count, &errmsg); + rc = parse_fetch_atts(attspec, &fa, &errmsg); if (rc == -1) { session_reply(s, tag, "BAD", errmsg); return (1); @@ -1007,81 +971,45 @@ fetch_dispatch(struct session *s, const char *tag, cha session_reply(s, tag, "NO", errmsg); return (1); } - /* RFC 9051 SS6.3.3: EXAMINE permits no change, \Seen included */ + /* RFC 9051 section 6.3.3: EXAMINE permits no change, \Seen included */ if (s->mbox_readonly) - attrs &= ~MBOX_FETCH_SET_SEEN; - if (degraded) - log_debug("session %u: %s: one or more unsupported message " - "data items not returned (a part number before " - "HEADER/TEXT/MIME, RFC822[.HEADER/.TEXT]), " - "answering the rest, then NO", s->id, cmdname); + fa.attrs &= ~MBOX_FETCH_SET_SEEN; + if (fa.degraded) + log_debug("session %u: %s: unsupported item skipped", s->id, + cmdname); memset(&req, 0, sizeof(req)); - req.attrs = attrs; + req.attrs = fa.attrs; req.by_uid = by_uid; - req.header_fields_not = header_fields_not; + req.header_fields_not = fa.header_fields_not; + req.has_partial = fa.has_partial; + req.partial_start = fa.partial_start; + req.partial_count = fa.partial_count; + strlcpy(req.header_fields, fa.header_fields, sizeof(req.header_fields)); + strlcpy(req.section_part, fa.section_part, sizeof(req.section_part)); - if (strlcpy(req.header_fields, header_fields, - sizeof(req.header_fields)) >= sizeof(req.header_fields) || - strlcpy(req.section_part, section_part, sizeof(req.section_part)) - >= sizeof(req.section_part)) { - session_reply(s, tag, "NO", "[SERVERBUG] internal error"); - return (1); - } - req.has_partial = has_partial; - req.partial_start = partial_start; - req.partial_count = partial_count; + if (fa.attrs & MBOX_FETCH_BODY_HEADER) + strlcpy(s->pending_header_label, "HEADER", + sizeof(s->pending_header_label)); + else if (fa.attrs & MBOX_FETCH_HEADER_FIELDS) + strlcpy(s->pending_header_label, fa.header_fields_label, + sizeof(s->pending_header_label)); - if (attrs & MBOX_FETCH_BODY_HEADER) { - if (strlcpy(s->pending_header_label, "HEADER", - sizeof(s->pending_header_label)) >= - sizeof(s->pending_header_label)) { - session_reply(s, tag, "NO", - "[SERVERBUG] internal error"); - return (1); - } - } else if (attrs & MBOX_FETCH_HEADER_FIELDS) { - if (strlcpy(s->pending_header_label, header_fields_label, - sizeof(s->pending_header_label)) >= - sizeof(s->pending_header_label)) { - session_reply(s, tag, "NO", - "[SERVERBUG] internal error"); - return (1); - } - } - - if (attrs & MBOX_FETCH_BODY_WHOLE) { + if (fa.attrs & MBOX_FETCH_BODY_WHOLE) s->pending_body_label[0] = '\0'; - } else if (attrs & MBOX_FETCH_BODY_TEXT) { - if (strlcpy(s->pending_body_label, "TEXT", - sizeof(s->pending_body_label)) >= - sizeof(s->pending_body_label)) { - session_reply(s, tag, "NO", - "[SERVERBUG] internal error"); - return (1); - } - } else if (attrs & MBOX_FETCH_BODY_PART) { - if (strlcpy(s->pending_body_label, section_part, - sizeof(s->pending_body_label)) >= - sizeof(s->pending_body_label)) { - session_reply(s, tag, "NO", - "[SERVERBUG] internal error"); - return (1); - } - } - s->pending_body_has_partial = has_partial; - s->pending_body_partial_origin = partial_start; + else if (fa.attrs & MBOX_FETCH_BODY_TEXT) + strlcpy(s->pending_body_label, "TEXT", + sizeof(s->pending_body_label)); + else if (fa.attrs & MBOX_FETCH_BODY_PART) + strlcpy(s->pending_body_label, fa.section_part, + sizeof(s->pending_body_label)); + s->pending_body_has_partial = fa.has_partial; + s->pending_body_partial_origin = fa.partial_start; - if (attrs & MBOX_FETCH_BODYSTRUCTURE) { - if (strlcpy(s->pending_bodystructure_label, - bodystructure_full ? "BODYSTRUCTURE" : "BODY", - sizeof(s->pending_bodystructure_label)) >= - sizeof(s->pending_bodystructure_label)) { - session_reply(s, tag, "NO", - "[SERVERBUG] internal error"); - return (1); - } - } + if (fa.attrs & MBOX_FETCH_BODYSTRUCTURE) + strlcpy(s->pending_bodystructure_label, + fa.bodystructure_full ? "BODYSTRUCTURE" : "BODY", + sizeof(s->pending_bodystructure_label)); if (modspec != NULL) { if (parse_fetch_modifiers(modspec, &req, s, by_uid, @@ -1092,10 +1020,9 @@ fetch_dispatch(struct session *s, const char *tag, cha } if (want_vanished && !req.has_changedsince) { - /* RFC 7162 SS3.2.6: VANISHED needs CHANGEDSINCE */ + /* RFC 7162 section 3.2.6: VANISHED needs CHANGEDSINCE */ session_reply(s, tag, "BAD", - "VANISHED requires CHANGEDSINCE also be specified " - "(RFC 7162 SS3.2.6)"); + "VANISHED requires CHANGEDSINCE"); return (1); } req.want_vanished = want_vanished; @@ -1112,7 +1039,7 @@ fetch_dispatch(struct session *s, const char *tag, cha req.nranges = nranges; - /* RFC 7162 SS3.1: both enable CONDSTORE */ + /* RFC 7162 section 3.1: both enable CONDSTORE */ if (req.attrs & MBOX_FETCH_MODSEQ) session_condstore_enable(s); @@ -1123,12 +1050,12 @@ fetch_dispatch(struct session *s, const char *tag, cha } s->fetch_attrs = req.attrs; s->cmd_by_uid = by_uid; - /* RFC 9051 SS6.4.5: "NO - fetch error: can't fetch that data" */ - s->fetch_incomplete = degraded; + /* RFC 9051 section 6.4.5: "NO - fetch error: can't fetch that data" */ + s->fetch_incomplete = fa.degraded; s->state = SESSION_FETCHING; - if (!send_mbox_request(s, IMSG_MBOX_FETCH, cmdname, "IMSG_MBOX_FETCH", - &req, sizeof(req), ranges, nranges, sizeof(struct seq_range))) { + if (send_mbox_request(s, IMSG_MBOX_FETCH, "IMSG_MBOX_FETCH", &req, + sizeof(req), ranges, nranges, sizeof(struct seq_range)) == -1) { session_reply(s, tag, "NO", "[SERVERBUG] internal error"); s->state = SESSION_SELECTED; return (1); blob - 4e44803fb3a69f2c0741294274aac08ab9941e6a blob + 778057317fadc0ccefd39083d8134d5be49d653c --- src/imapd.8 +++ src/imapd.8 @@ -3,7 +3,7 @@ .\" Written for the OpenIMAPD project. Public domain / no rights reserved, .\" matching the project's ports-oriented, OpenBSD-base-inclusion goal. .\" -.Dd $Mdocdate: October 3 2026 $ +.Dd $Mdocdate: October 5 2026 $ .Dt IMAPD 8 .Os .Sh NAME @@ -11,7 +11,7 @@ .Nd Internet Message Access Protocol (IMAP) daemon .Sh SYNOPSIS .Nm -.Op Fl dVv +.Op Fl dnVv .Op Fl D Ar macro Ns = Ns Ar value .Op Fl f Ar file .Sh DESCRIPTION @@ -27,6 +27,24 @@ as described in and rewrites each new message with the CRLF line endings RFC 5322 requires before any client sees it. .Pp +Message files that other software places in a mailbox's +.Pa cur +or +.Pa new +directory, such as a maildir brought from another server, are given +UIDs the next time the mailbox is selected or its status is read, +keeping the flags in their names. +A mailbox whose +.Pa imapd.index +is lost is rebuilt the same way, with a new +.Li UIDVALIDITY . +A file found in +.Pa cur +with the deleted flag +.Pq Sq T +is left out unless the mailbox has no index, since it may be a +message that was expunged but could not be removed. +.Pp By default, .Nm listens on port 143, where clients may upgrade to TLS with @@ -79,10 +97,16 @@ Use .Ar file as the configuration file, instead of the default .Pa /etc/imapd.conf . +.It Fl n +Configtest mode. +Only check the configuration file for validity. .It Fl V Print the version and exit. .It Fl v Produce more verbose logging. +Multiple +.Fl v +options increase the verbosity. .El .Sh FILES .Bl -tag -width "/etc/ssl/private/imapd.keyXX" -compact @@ -113,6 +137,8 @@ While it is absent, every mailbox is subscribed. Highest .Li UIDVALIDITY issued, at the root of each maildir. +If it cannot be read or written, a value already issued may be issued +again. .El .Sh SEE ALSO .Xr imapd.conf 5 , @@ -156,6 +182,11 @@ or polls the selected mailbox, so new mail is reported within one .Ic idle poll interval. +Files placed directly in +.Pa cur +are not noticed during +.Li IDLE +until something else changes the mailbox. .Pp One process serves all sessions of an account, so a .Li STORE , blob - a4b13bb83eaccee953142bf9ebf1432fe2958254 blob + 3ddc28bc90525e93fd6ccfd5d0d24f281d248de7 --- src/imapd.conf.5 +++ src/imapd.conf.5 @@ -3,7 +3,7 @@ .\" Written for the OpenIMAPD project. Public domain / no rights reserved, .\" matching the project's ports-oriented, OpenBSD-base-inclusion goal. .\" -.Dd $Mdocdate: October 4 2026 $ +.Dd $Mdocdate: October 6 2026 $ .Dt IMAPD.CONF 5 .Os .Sh NAME @@ -79,7 +79,9 @@ A login past the limit is answered .Li NO with the .Li LIMIT -response code and the connection is closed. +response code, after an untagged +.Li BYE +with the same code, and the connection is closed. A client that vanishes without closing its connection holds its session until TCP keepalive notices, which by default is 2 hours 10 minutes; see @@ -220,7 +222,11 @@ If no .Ic listen on is given, .Xr imapd 8 -listens on 0.0.0.0 port 143 and 0.0.0.0 tls port 993. +listens on +.Sq * +port 143 and +.Sq * +tls port 993. If any is given, only the listeners named are bound. .It Ic lock timeout Ar seconds How long a command waits for a mailbox lock held by another process @@ -362,6 +368,18 @@ or .Pa .. component. A line of 1023 or more characters causes every login to be refused. +A login is also refused if the account's mailbox process is not running +in the +.Ar maildir +within 10 seconds, as when the directory does not exist or its +filesystem is not mounted. +The client is sent an untagged +.Li BYE +and then +.Li NO , +both with the +.Li UNAVAILABLE +response code, and the cause is logged. .Xr imapduser 8 adds and removes entries. .Pp blob - ba320d62cfd97a54d1625488669effc1319157c3 blob + 5e7347935a2ef25064d6c7a8e50def90a4decbe3 --- src/imapd.conf.example +++ src/imapd.conf.example @@ -21,19 +21,18 @@ # && doas chmod 600 /etc/imapd.conf) # -# Listen on all IPv4 interfaces: cleartext/STARTTLS on port 143, implicit -# TLS (RFC 8314) on port 993. Both listeners must share the same address. -# These are also the defaults if "listen on" is omitted entirely. -listen on 0.0.0.0 port 143 -listen on 0.0.0.0 tls port 993 +# Listen on all IPv4 and IPv6 interfaces: cleartext/STARTTLS on port 143, +# implicit TLS (RFC 8314) on port 993. Both listeners must share the same +# address. These are also the defaults if "listen on" is omitted entirely. +listen on * port 143 +listen on * tls port 993 -# The address may also be "::" (all IPv6 interfaces) or "*" (both IPv4 and -# IPv6, binds two sockets per listener, one of each family, matching -# httpd.conf(5)'s own "*" convention), or a single literal IPv4/IPv6 -# address. Hostnames are not accepted. For example, to listen on both -# address families: -#listen on * port 143 -#listen on * tls port 993 +# The address may also be "0.0.0.0" (all IPv4 interfaces), "::" (all IPv6 +# interfaces), or a single literal IPv4/IPv6 address. Hostnames are not +# accepted. "*" binds two sockets per listener, one of each family, as +# httpd.conf(5)'s "*" does. For example, IPv4 only: +#listen on 0.0.0.0 port 143 +#listen on 0.0.0.0 tls port 993 # Mail spool root, chrooted into by the per-session store child. # Defaults to /var/mail/imapd. blob - ad6ba2b57e341a039b4ee83e8985f20671d4446f blob + 585519020ecf17b7299167f9eaaf557d89299509 --- src/imapd.h +++ src/imapd.h @@ -28,7 +28,7 @@ #include #include -#define IMAPD_VERSION "0.2.2" +#define IMAPD_VERSION "0.2.3" #define IMAPD_USER "_imapd" #define IMAPD_AUTH_USER "_imapauth" @@ -125,17 +125,17 @@ enum imsg_type { IMSG_MBOX_SELECT_VANISHED, IMSG_MBOX_STORE_MODIFIED, - /* RFC 9051 SS6.3.13 (IDLE); the VIEW ones go with any command */ + /* RFC 9051 section 6.3.13 (IDLE); the VIEW ones go with any command */ IMSG_MBOX_IDLE_REFRESH, IMSG_MBOX_VIEW_EXPUNGE, IMSG_MBOX_VIEW_FETCH, IMSG_MBOX_VIEW_EXISTS, IMSG_MBOX_IDLE_REFRESHED, - /* RFC 9051 SS6.3.9 (LIST) */ + /* RFC 9051 section 6.3.9 (LIST) */ IMSG_MBOX_LIST_ITEM, - /* RFC 9051 SS6.3.7/SS6.3.8 (SUBSCRIBE/UNSUBSCRIBE) */ + /* RFC 9051 section 6.3.7/section 6.3.8 (SUBSCRIBE/UNSUBSCRIBE) */ IMSG_MBOX_SUBSCRIBE, IMSG_MBOX_UNSUBSCRIBE, IMSG_MBOX_SAVED_CLEAR @@ -258,7 +258,7 @@ struct imsg_store_init { #define MBOX_NAME_MAX 256 -/* RFC 5530 SS3 response codes, and RFC 9051 NOTSAVED */ +/* RFC 5530 section 3 response codes, and RFC 9051 NOTSAVED */ enum mbox_op_error { MBOX_ERR_UNSET = 0, MBOX_OP_OK, @@ -272,7 +272,7 @@ enum mbox_op_error { MBOX_OP_ERR_OVERQUOTA, }; -/* QRESYNC select-param (RFC 7162 SS3.2.5). */ +/* QRESYNC select-param (RFC 7162 section 3.2.5). */ struct imsg_mbox_select { char mailbox[MBOX_NAME_MAX]; int readonly; /* 1 = EXAMINE, 0 = SELECT */ @@ -291,10 +291,10 @@ struct imsg_mbox_selected { uint32_t uidnext; uint64_t highestmodseq; - /* preceded by VANISHED, then FETCH_META (RFC 7162 SS3.2.6) */ + /* preceded by VANISHED, then FETCH_META (RFC 7162 section 3.2.6) */ }; -/* RFC 9051 SS6.3.11, plus RFC 7162 SS3.1.7 HIGHESTMODSEQ */ +/* RFC 9051 section 6.3.11, plus RFC 7162 section 3.1.7 HIGHESTMODSEQ */ #define STATUS_ATT_MESSAGES (1U << 0) #define STATUS_ATT_UIDNEXT (1U << 1) #define STATUS_ATT_UIDVALIDITY (1U << 2) @@ -304,7 +304,7 @@ struct imsg_mbox_selected { #define STATUS_ATT_HIGHESTMODSEQ (1U << 6) #define STATUS_ATT_RECENT (1U << 7) /* answered "0" */ -/* RFC 9051 SS6.3.11 STATUS; the selection is left alone */ +/* RFC 9051 section 6.3.11 STATUS; the selection is left alone */ struct imsg_mbox_status { char mailbox[MBOX_NAME_MAX]; uint32_t attrs; @@ -321,7 +321,7 @@ struct imsg_mbox_status_result { uint64_t size; }; -/* one gap [uid_lo, uid_hi]; RFC 7162 SS5.1 minimal state */ +/* one gap [uid_lo, uid_hi]; RFC 7162 section 5.1 minimal state */ struct imsg_mbox_select_vanished { uint32_t uid_lo; uint32_t uid_hi; @@ -333,7 +333,7 @@ struct imsg_mbox_select_vanished { #define MBOX_FETCH_UID (1U << 1) #define MBOX_FETCH_INTERNALDATE (1U << 2) #define MBOX_FETCH_RFC822_SIZE (1U << 3) -#define MBOX_FETCH_MODSEQ (1U << 4) /* RFC 7162 SS3.1.4.2 */ +#define MBOX_FETCH_MODSEQ (1U << 4) /* RFC 7162 section 3.1.4.2 */ #define MBOX_FETCH_BODY_HEADER (1U << 5) #define MBOX_FETCH_BODY_WHOLE (1U << 6) #define MBOX_FETCH_BODY_TEXT (1U << 7) @@ -384,11 +384,11 @@ struct imsg_mbox_select_vanished { #define IDLE_POLL_DEFAULT 5 #define IDLE_POLL_MAX 300 /* seconds; 0 disables polling */ -/* RFC 9051 SS5.4 permits a short pre-authentication timer */ +/* RFC 9051 section 5.4 permits a short pre-authentication timer */ #define LOGIN_GRACE_DEFAULT 60 #define LOGIN_GRACE_MAX 3600 /* seconds; 0 disables the timer */ -/* lock wait, then NO [INUSE] (RFC 9051 SS7.1) */ +/* lock wait, then NO [INUSE] (RFC 9051 section 7.1) */ #define LOCK_TIMEOUT_DEFAULT 120 #define LOCK_TIMEOUT_MAX 3600 /* seconds; 0 disables the bound */ @@ -416,7 +416,7 @@ struct imsg_parser_req { uint32_t poollen; }; -/* RFC 9051 SS6.4.4 content key; strings are offsets into the pool */ +/* RFC 9051 section 6.4.4 content key; strings are offsets into the pool */ struct imsg_parser_leaf { int32_t op; uint32_t str_off; @@ -434,7 +434,7 @@ struct imsg_parser_rep { uint32_t retiring; }; -/* RFC 9051 SS9 sequence-set range; the store resolves "*" */ +/* RFC 9051 section 9 sequence-set range; the store resolves "*" */ struct seq_range { uint32_t lo; uint32_t hi; @@ -442,7 +442,7 @@ struct seq_range { int hi_is_star; }; -/* RFC 9051 SS9 seq-last-command, "$": the all-zero range */ +/* RFC 9051 section 9 seq-last-command, "$": the all-zero range */ #define SEQ_RANGE_SAVED(r) ((r)->lo == 0 && !(r)->lo_is_star) /* keeps the trailing array under MAX_IMSGSIZE */ @@ -452,11 +452,11 @@ struct imsg_mbox_fetch { uint32_t nranges; uint32_t attrs; - /* RFC 7162 SS3.1.4.1 CHANGEDSINCE */ + /* RFC 7162 section 3.1.4.1 CHANGEDSINCE */ int has_changedsince; uint64_t changedsince; - /* RFC 9051 SS6.4.9 UID FETCH; RFC 7162 SS3.2.6 VANISHED */ + /* RFC 9051 section 6.4.9 UID FETCH; RFC 7162 section 3.2.6 VANISHED */ int by_uid; int want_vanished; @@ -517,7 +517,7 @@ struct imsg_mbox_result { uint32_t count; uint64_t highestmodseq; - /* RFC 9051 SS7.1 COPYUID; COPY and MOVE only */ + /* RFC 9051 section 7.1 COPYUID; COPY and MOVE only */ uint32_t uidvalidity; }; @@ -539,20 +539,20 @@ struct imsg_mbox_store { uint32_t sysflags; char keywords[MBOX_FLAGS_MAX]; - /* RFC 7162 SS3.1.3 UNCHANGEDSINCE */ + /* RFC 7162 section 3.1.3 UNCHANGEDSINCE */ int has_unchangedsince; uint64_t unchangedsince; int by_uid; }; -/* RFC 7162 SS3.1.3 MODIFIED */ +/* RFC 7162 section 3.1.3 MODIFIED */ struct imsg_mbox_store_modified { uint32_t seqno; uint32_t uid; }; -/* RFC 9051 SS6.4.3; lowest-numbered first (SS7.5.1) */ +/* RFC 9051 section 6.4.3; lowest-numbered first (section 7.5.1) */ struct imsg_mbox_expunge { int silent; /* 1 for CLOSE, 0 for a real EXPUNGE command */ @@ -562,18 +562,18 @@ struct imsg_mbox_expunge { }; struct imsg_mbox_expunged { - uint32_t seqno; /* after earlier EXPUNGEs (RFC 9051 SS7.5.1) */ - uint32_t uid; /* for VANISHED (RFC 7162 SS3.2.10.2) */ + uint32_t seqno; /* RFC 9051 section 7.5.1: after EXPUNGEs */ + uint32_t uid; /* for VANISHED (RFC 7162 section 3.2.10.2) */ }; -/* RFC 9051 SS6.4.7 COPY and SS6.4.8 MOVE */ +/* RFC 9051 section 6.4.7 COPY and section 6.4.8 MOVE */ struct imsg_mbox_copy { int by_uid; uint32_t nranges; char destname[MBOX_NAME_MAX]; }; -/* COPYUID pairs, sent before EXPUNGE for MOVE (RFC 9051 SS6.4.8) */ +/* COPYUID pairs, sent before EXPUNGE for MOVE (RFC 9051 section 6.4.8) */ struct imsg_mbox_copy_mapping { uint32_t src_uid; uint32_t dest_uid; @@ -595,10 +595,10 @@ struct imsg_mbox_appended { uint32_t uid; /* APPENDUID, with uidvalidity */ }; -/* RFC 9051 SS6.4.4 search-program, as a postfix array of nodes */ +/* RFC 9051 section 6.4.4 search-program, as a postfix array of nodes */ #define SEARCH_PROGRAM_MAX_NODES 100 #define SEARCH_KEYWORD_MAX 64 /* one keyword, not a list */ -/* operand pool; RFC 9051 SS4.3's non-synchronizing literal cap */ +/* operand pool; RFC 9051 section 4.3's non-synchronizing literal cap */ #define SEARCH_OPERANDS_MAX 4096 #define SEARCH_OP_ALL 0 @@ -624,8 +624,8 @@ struct imsg_mbox_appended { #define SEARCH_OP_AND 20 #define SEARCH_OP_OR 21 #define SEARCH_OP_NOT 22 -#define SEARCH_OP_MODSEQ 23 /* RFC 7162 SS3.1.5 */ -/* RFC 9051 SS6.4.4 content keys, answered by the parser-worker */ +#define SEARCH_OP_MODSEQ 23 /* RFC 7162 section 3.1.5 */ +/* RFC 9051 section 6.4.4 content keys, answered by the parser-worker */ #define SEARCH_OP_SUBJECT 24 #define SEARCH_OP_HEADER 25 #define SEARCH_OP_SENTBEFORE 26 @@ -653,7 +653,7 @@ struct search_node { uint32_t name_len; }; -/* RFC 9051 SS6.4.4 search-return-opt */ +/* RFC 9051 section 6.4.4 search-return-opt */ #define SEARCH_RETURN_MIN (1U << 0) #define SEARCH_RETURN_MAX (1U << 1) #define SEARCH_RETURN_ALL (1U << 2) @@ -683,10 +683,10 @@ struct search_parse_result { struct imsg_mbox_search_match { uint32_t seqno; uint32_t uid; - uint64_t modseq; /* RFC 7162 SS3.1.6 */ + uint64_t modseq; /* RFC 7162 section 3.1.6 */ }; -/* RFC 9051 SS6.3.13 IDLE, SS6.1.2 NOOP; EXPUNGE is imsg_mbox_expunged */ +/* RFC 9051 sections 6.3.13 IDLE, 6.1.2 NOOP; EXPUNGE is imsg_mbox_expunged */ struct imsg_mbox_idle_refreshed { int ok; int unchanged; /* the probe saw no change */ @@ -697,7 +697,7 @@ struct imsg_mbox_view_exists { uint32_t exists; }; -/* RFC 9051 SS6.3.4/SS6.3.5; store.c re-validates the name */ +/* RFC 9051 section 6.3.4/section 6.3.5; store.c re-validates the name */ struct imsg_mbox_create { char mailbox[MBOX_NAME_MAX]; }; @@ -706,24 +706,24 @@ struct imsg_mbox_delete { char mailbox[MBOX_NAME_MAX]; }; -/* RFC 9051 SS6.3.6 RENAME */ +/* RFC 9051 section 6.3.6 RENAME */ struct imsg_mbox_rename { char oldname[MBOX_NAME_MAX]; char newname[MBOX_NAME_MAX]; }; -/* RFC 9051 SS6.3.9 LIST; unordered, INBOX excluded */ +/* RFC 9051 section 6.3.9 LIST; unordered, INBOX excluded */ struct imsg_mbox_list { int subscribed_only; }; -/* exists 0: subscribed, no mailbox (RFC 9051 SS6.3.9.6) */ +/* exists 0: subscribed, no mailbox (RFC 9051 section 6.3.9.6) */ struct imsg_mbox_list_item { char mailbox[MBOX_NAME_MAX]; int exists; }; -/* RFC 9051 SS6.3.7 SUBSCRIBE and SS6.3.8 UNSUBSCRIBE */ +/* RFC 9051 section 6.3.7 SUBSCRIBE and section 6.3.8 UNSUBSCRIBE */ struct imsg_mbox_subscribe { char mailbox[MBOX_NAME_MAX]; }; @@ -756,6 +756,8 @@ void imsgev_add(struct imsgev *); /* a dispatch handler must call this before returning */ void imsgev_rearm_read(struct imsgev *); +int recv_fixed(struct imsg *, void *, size_t, const char *); +int recv_header(struct imsg *, void *, size_t, const char *); int setup_recv_one_peer(struct imsgbuf *); int setup_recv_one_peer_id(struct imsgbuf *, uint32_t *); blob - 4abc10cf7e54c09814b8651cf7a107c6d0ec0c43 blob + 2ef6ced46ea7c66ecc9add39ef9bc3cf9c0faff9 --- src/imsgev.c +++ src/imsgev.c @@ -7,23 +7,6 @@ * Copyright (c) 2004 Esben Norby * Copyright (c) 2003, 2004 Henning Brauer * - * This file's name and its "struct imsgev" wrapper-around-imsgbuf+ - * event(3) concept match Eric Faurot's imsgev.c in OpenBSD's ldapd - * (usr.sbin/ldapd/imsgev.c), not a generic/obvious name, so his - * copyright is carried forward here even though this file's actual - * function signatures and dispatch design (a caller-supplied raw - * libevent handler, vs. ldapd's callback+needfd model) were written - * independently and differ from his implementation. - * - * imsgev_add() below is the exception: it is a verbatim copy (up to - * whitespace and the choice of iev vs. iev->data as event_set()'s last - * argument) of the imsg_event_add() idiom shared across OpenBSD privsep - * daemons, traceable to usr.sbin/ospfd/ospfd.c:525-534 (Claudio Jeker - * 2005, Esben Norby 2004, Henning Brauer 2003-2004) and copied with - * only that one-argument variation into dvmrpd.c, npppd.c, and rad.c. - * Their copyright is carried forward for that function specifically, - * same rationale as log.c's and parse.y's shared-idiom copyright chains. - * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. @@ -61,8 +44,6 @@ imsgev_on_compose(struct imsgbuf *ibuf, void *arg) { struct imsgev *iev = arg; - (void)ibuf; - if (event_pending(&iev->ev, EV_WRITE, NULL)) return; imsgev_add(iev); @@ -119,6 +100,27 @@ imsgev_add(struct imsgev *iev) } /* EV_READ lacks EV_PERSIST: every handler must re-arm it */ +/* a fixed-size message, or 0 after a log line */ +int +recv_fixed(struct imsg *imsg, void *data, size_t len, const char *what) +{ + if (imsg_get_data(imsg, data, len) == -1) { + log_warnx("bad %s", what); + return (0); + } + return (1); +} + +int +recv_header(struct imsg *imsg, void *data, size_t len, const char *what) +{ + if (imsg_get_buf(imsg, data, len) == -1) { + log_warnx("bad %s (header)", what); + return (0); + } + return (1); +} + void imsgev_rearm_read(struct imsgev *iev) { blob - 68126e28fffe04e0f26c21b5e2882ed2b8e61d41 blob + 14c1f133989ed4d5fd815ac332d079572515d5f9 --- src/index.c +++ src/index.c @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - #include #include #include @@ -37,8 +36,7 @@ #include "log.h" #include "store_internal.h" -/* no ':', CR or LF in any index field */ -/* RFC 7162 SS7 */ +/* RFC 7162 section 7 */ #define INDEX_MODSEQ_MAX INT64_MAX #define CRLF_BLOCK 65536 @@ -91,7 +89,9 @@ int index_load(int fd, struct mbox_index *idx) { FILE *fp; - char line[STORE_INDEX_LINE_MAX]; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; int dupfd; int first = 1; @@ -111,26 +111,19 @@ index_load(int fd, struct mbox_index *idx) return (-1); } - while (fgets(line, sizeof(line), fp) != NULL) { - /* fgets(3) splits an over-long line */ - if (strchr(line, '\n') == NULL && - strlen(line) == sizeof(line) - 1) { - int c = fgetc(fp); - - if (c != EOF && c != '\n') { - log_warnx("session %u: over-long line in %s, " - "refusing to parse the index", session_id, - STORE_INDEX_NAME); - goto fail; - } + while ((linelen = getline(&line, &linesize, fp)) != -1) { + if (linelen >= STORE_INDEX_LINE_MAX) { + log_warnx("session %u: %s: line too long, index not " + "parsed", session_id, STORE_INDEX_NAME); + goto fail; } line[strcspn(line, "\n")] = '\0'; if (line[0] == '\0') continue; if (first) { - char *colon, *colon2, *ep; - unsigned long parsed; + char *colon, *colon2; + const char *errstr; first = 0; if ((colon = strchr(line, ':')) == NULL) { @@ -139,21 +132,18 @@ index_load(int fd, struct mbox_index *idx) goto fail; } *colon = '\0'; - /* strtoul(3) accepts leading space and a sign */ if (line[0] < '0' || line[0] > '9') { log_warnx("session %u: malformed " "UIDVALIDITY: %s", session_id, line); goto fail; } - errno = 0; - parsed = strtoul(line, &ep, 10); - if (*ep != '\0' || errno != 0 || - parsed > UINT32_MAX) { + idx->uidvalidity = strtonum(line, 0, UINT32_MAX, + &errstr); + if (errstr != NULL) { log_warnx("session %u: malformed " "UIDVALIDITY: %s", session_id, line); goto fail; } - idx->uidvalidity = (uint32_t)parsed; /* RFC 7162: optional HIGHESTMODSEQ; absent means 1 */ if ((colon2 = strchr(colon + 1, ':')) != NULL) @@ -164,15 +154,13 @@ index_load(int fd, struct mbox_index *idx) session_id, colon + 1); goto fail; } - errno = 0; - parsed = strtoul(colon + 1, &ep, 10); - if (*ep != '\0' || errno != 0 || - parsed > UINT32_MAX) { + idx->uidnext = strtonum(colon + 1, 0, UINT32_MAX, + &errstr); + if (errstr != NULL) { log_warnx("session %u: malformed UIDNEXT: %s", session_id, colon + 1); goto fail; } - idx->uidnext = (uint32_t)parsed; if (colon2 != NULL) { if (colon2[1] < '0' || colon2[1] > '9') { @@ -181,11 +169,9 @@ index_load(int fd, struct mbox_index *idx) colon2 + 1); goto fail; } - errno = 0; - idx->highestmodseq = strtoull(colon2 + 1, &ep, - 10); - if (*ep != '\0' || errno != 0 || - idx->highestmodseq > INDEX_MODSEQ_MAX) { + idx->highestmodseq = strtonum(colon2 + 1, 0, + INDEX_MODSEQ_MAX, &errstr); + if (errstr != NULL) { log_warnx("session %u: malformed " "HIGHESTMODSEQ: %s", session_id, colon2 + 1); @@ -205,9 +191,11 @@ index_load(int fd, struct mbox_index *idx) idx->nlines++; } if (ferror(fp)) { - log_warn("session %u: fgets %s", session_id, STORE_INDEX_NAME); + log_warn("session %u: getline %s", session_id, + STORE_INDEX_NAME); goto fail; } + free(line); fclose(fp); if (first) { @@ -220,6 +208,7 @@ index_load(int fd, struct mbox_index *idx) return (0); fail: + free(line); index_free(idx); fclose(fp); return (-1); @@ -228,7 +217,7 @@ fail: int index_parse_line(const char *line, struct index_rec *rec) { - const char *p, *q, *r; + const char *p, *q, *r, *errstr; char *ep; unsigned long parsed; @@ -261,8 +250,8 @@ index_parse_line(const char *line, struct index_rec *r memcpy(rec->basename, p, (size_t)(q - p)); rec->basename[q - p] = '\0'; if (!index_basename_valid(rec->basename)) { - log_warnx("session %u: refusing index line with unsafe " - "basename (UID %u)", session_id, rec->uid); + log_warnx("session %u: index line uid %u: unsafe basename", + session_id, rec->uid); return (-1); } @@ -282,10 +271,8 @@ index_parse_line(const char *line, struct index_rec *r "in index line: %s", session_id, line); return (-1); } - errno = 0; - rec->modseq = strtoull(r + 1, &ep, 10); - if (*ep != '\0' || errno != 0 || - rec->modseq > INDEX_MODSEQ_MAX) { + rec->modseq = strtonum(r + 1, 0, INDEX_MODSEQ_MAX, &errstr); + if (errstr != NULL) { log_warnx("session %u: malformed per-message MODSEQ " "in index line: %s", session_id, line); return (-1); @@ -370,7 +357,7 @@ seqset_max_hi(const struct seq_range *resolved, uint32 return (max); } -/* RFC 9051 SS6.4.9: a UID command's set is in UID space */ +/* RFC 9051 section 6.4.9: a UID command's set is in UID space */ enum seqset_pos seqset_position(const struct seq_range *resolved, uint32_t nresolved, uint32_t max_hi, int by_uid, uint32_t uid, uint32_t seqno) @@ -384,7 +371,7 @@ seqset_position(const struct seq_range *resolved, uint return (SEQSET_MATCH); } -/* RFC 7162 SS3.2.6 */ +/* RFC 7162 section 3.2.6 */ void send_vanished_range(const struct mbox_index *idx, uint32_t lo, uint32_t hi, struct imsgev *iev) @@ -465,16 +452,16 @@ index_append(struct mbox_index *idx, uint32_t uid, con char line[STORE_INDEX_LINE_MAX]; int len; - /* RFC 9051 SS9 forbids UID 0; refuse rather than wrap */ + /* RFC 9051 section 9 forbids UID 0; refuse rather than wrap */ if (uid == 0) { - log_warnx("session %u: refusing index entry with UID 0 " - "(uidnext exhausted or index header corrupt)", session_id); + log_warnx("session %u: index entry with UID 0, refused", + session_id); return (-1); } if (strpbrk(basename, ":\r\n") != NULL) { - log_warnx("session %u: refusing index entry with unsafe " - "basename: %s", session_id, basename); + log_warnx("session %u: index entry %s: unsafe basename", + session_id, basename); return (-1); } @@ -576,7 +563,6 @@ fail: return (-1); } - void index_free(struct mbox_index *idx) { @@ -588,7 +574,7 @@ index_free(struct mbox_index *idx) memset(idx, 0, sizeof(*idx)); } -/* RFC 7162 SS3.2.5.1 QRESYNC resync; "$" is the one before SELECT */ +/* RFC 7162 section 3.2.5.1 QRESYNC resync; "$" is the one before SELECT */ void qresync_send_resync(const struct imsg_mbox_select *req, const struct seq_range *ranges, uint32_t nranges, @@ -608,7 +594,7 @@ qresync_send_resync(const struct imsg_mbox_select *req nresolved += ss->nsaved; } } else { - /* RFC 7162 SS3.2.5.1: no known-uids means 1:uidnext-1 */ + /* RFC 7162 section 3.2.5.1: no known-uids means 1:uidnext-1 */ if (idx->uidnext <= 1) return; resolved[0].lo = 1; @@ -706,16 +692,14 @@ index_lock_release(struct index_lock *il) } } -/* RFC 9051 SS2.3.1.1: it must increase, so time is only a floor */ +/* RFC 9051 section 2.3.1.1: it must increase, so time is only a floor */ uint32_t uidvalidity_next(void) { - const char *path; + const char *path, *errstr; char buf[32]; struct stat st; time_t now; - char *ep; - unsigned long parsed; uint32_t floor = 0, val; ssize_t n; int fd, writeback = 1; @@ -727,13 +711,13 @@ uidvalidity_next(void) if ((fd = openat(maildir_root_fd, path, O_RDWR | O_CREAT, 0600)) == -1) { - log_warn("session %u: open %s (UIDVALIDITY floor); falling " - "back to a bare timestamp", session_id, path); + log_warn("session %u: open %s, using a bare timestamp", + session_id, path); return (val != 0 ? val : 1); } if (flock(fd, LOCK_EX) == -1) { - log_warn("session %u: flock %s (UIDVALIDITY floor); falling " - "back to a bare timestamp", session_id, path); + log_warn("session %u: flock %s, using a bare timestamp", + session_id, path); close(fd); return (val != 0 ? val : 1); } @@ -746,17 +730,16 @@ uidvalidity_next(void) } else { buf[n] = '\0'; buf[strcspn(buf, "\r\n")] = '\0'; - errno = 0; - parsed = strtoul(buf, &ep, 10); - if (buf[0] < '0' || buf[0] > '9' || *ep != '\0' || - errno != 0 || parsed > UINT32_MAX) { - log_warnx("session %u: %s holds no usable " - "UIDVALIDITY floor (%s); leaving it alone " - "and falling back to a bare timestamp", - session_id, path, buf); + errstr = "not a decimal number"; + if (buf[0] >= '0' && buf[0] <= '9') + floor = strtonum(buf, 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("session %u: %s: no usable " + "UIDVALIDITY floor (%s), using a bare " + "timestamp", session_id, path, buf); writeback = 0; - } else - floor = (uint32_t)parsed; + floor = 0; + } } } @@ -780,22 +763,20 @@ uidvalidity_next(void) lseek(fd, 0, SEEK_SET) == -1 || ftruncate(fd, 0) == -1 || write(fd, buf, (size_t)n) != n) - log_warn("session %u: could not record the " - "UIDVALIDITY floor in %s; the value issued now " - "may be issued again", session_id, path); + log_warn("session %u: %s: UIDVALIDITY floor not " + "recorded", session_id, path); } - log_debug("session %u: UIDVALIDITY: floor %u in %s -> issued %u%s", - session_id, floor, path, val, - writeback ? "" : " (floor NOT updated)"); + log_debug("session %u: UIDVALIDITY floor %u in %s, issued %u%s", + session_id, floor, path, val, writeback ? "" : " (floor kept)"); close(fd); /* releases the flock(2) */ return (val); } -/* RFC 5322 SS2.3: CR and LF only as CRLF; mail.maildir(8) writes LF alone */ +/* RFC 5322 section 2.3 wants CRLF only; mail.maildir(8) writes LF alone */ static int -new_to_crlf(int dfd, const char *name) +to_crlf(int dfd, const char *dir, const char *name) { struct stat st; char src[PATH_MAX], tmp[PATH_MAX]; @@ -805,11 +786,12 @@ new_to_crlf(int dfd, const char *name) int ifd = -1, ofd = -1, made = 0, cr = 0, changed = 0; int rc = -1; - if (snprintf(src, sizeof(src), "new/%s", name) >= (int)sizeof(src) || + if (snprintf(src, sizeof(src), "%s/%s", dir, name) >= + (int)sizeof(src) || snprintf(tmp, sizeof(tmp), "tmp/%s.crlf", name) >= (int)sizeof(tmp)) { - log_warnx("session %u: new/%s: name too long for CRLF, " - "indexed as it is", session_id, name); + log_warnx("session %u: %s/%s: name too long for CRLF, " + "indexed as it is", session_id, dir, name); return (-1); } ifd = openat(dfd, src, O_RDONLY | O_NOFOLLOW | O_NONBLOCK); @@ -881,22 +863,37 @@ done: return (rc); } +static void +new_to_cur(int dfd, const char *name) +{ + struct stat st; + char from[PATH_MAX], to[PATH_MAX]; + + if (snprintf(from, sizeof(from), "new/%s", name) >= + (int)sizeof(from) || + snprintf(to, sizeof(to), "cur/%s", name) >= (int)sizeof(to)) { + log_warnx("session %u: new/%s: name too long to move to cur/", + session_id, name); + return; + } + if (fstatat(dfd, to, &st, AT_SYMLINK_NOFOLLOW) == 0) { + log_warnx("session %u: %s: not moved, %s already exists", + session_id, from, to); + return; + } + if (errno != ENOENT || renameat(dfd, from, dfd, to) == -1) + log_warn("session %u: moving %s to cur/", session_id, from); +} + static int index_scan_new(int dfd, struct mbox_index *idx, int mutate) { DIR *dp; struct dirent *de; + const char *info; int added = 0, converted = 0; - { - int newfd; - - dp = NULL; - newfd = openat(dfd, "new", O_RDONLY | O_DIRECTORY); - if (newfd != -1 && (dp = fdopendir(newfd)) == NULL) - close(newfd); - } - if (dp == NULL) { + if ((dp = opendirat(dfd, "new")) == NULL) { if (errno == ENOENT) /* no new/ yet on a never-used mailbox, not an error */ return (0); @@ -909,12 +906,22 @@ index_scan_new(int dfd, struct mbox_index *idx, int mu if (de->d_name[0] == '.') /* ".", "..", and dotfiles */ continue; + /* maildir info ":2,": the cur/ scan indexes it there */ + if (strpbrk(de->d_name, "\r\n") == NULL && + (info = strchr(de->d_name, ':')) != NULL && + strncmp(info, ":2,", 3) == 0) { + if (!mutate) { + closedir(dp); + return (1); + } + new_to_cur(dfd, de->d_name); + continue; + } /* ':' or a newline would corrupt the index line */ if (strpbrk(de->d_name, ":\r\n") != NULL) { if (mutate) - log_warnx("session %u: skipping new/ file " - "with unsafe name (contains ':' or " - "newline): %s", session_id, de->d_name); + log_warnx("session %u: new/%s: unsafe name, " + "skipped", session_id, de->d_name); continue; } if (index_has_basename(idx, de->d_name)) @@ -923,8 +930,8 @@ index_scan_new(int dfd, struct mbox_index *idx, int mu closedir(dp); return (1); } - /* RFC 9051 SS2.3.1.1: convert before the UID is given */ - if (new_to_crlf(dfd, de->d_name) == 1) + /* RFC 9051 section 2.3.1.1: convert before the UID is given */ + if (to_crlf(dfd, "new", de->d_name) == 1) converted = 1; if (index_append(idx, idx->uidnext, de->d_name) == -1) { closedir(dp); @@ -940,6 +947,195 @@ index_scan_new(int dfd, struct mbox_index *idx, int mu return (added); } +struct base_set { + const char **name; + size_t *len; + size_t mask; +}; + +static size_t +base_slot(const struct base_set *bs, const char *s, size_t len) +{ + uint32_t h = 2166136261U; /* FNV-1a */ + size_t i, k; + + for (i = 0; i < len; i++) + h = (h ^ (unsigned char)s[i]) * 16777619U; + for (k = h & bs->mask; bs->name[k] != NULL; k = (k + 1) & bs->mask) + if (bs->len[k] == len && memcmp(bs->name[k], s, len) == 0) + break; + return (k); +} + +static int +name_cmp(const void *a, const void *b) +{ + return (strcmp(*(char * const *)a, *(char * const *)b)); +} + +static int +index_scan_cur(int dfd, struct mbox_index *idx, int mutate) +{ + struct base_set bs = { NULL, NULL, 0 }; + struct stat st; + DIR *dp = NULL; + struct dirent *de; + char **found = NULL, **grown, *info, *name; + char base[NAME_MAX + 1], from[PATH_MAX], to[PATH_MAX]; + const char *p, *prev = NULL; + size_t nfound = 0, cap = 0, size = 1, i, k, len; + size_t prevlen = 0, added = 0; + int synced = 0, rv = -1; + + while (size < 2 * idx->nlines + 1) + size <<= 1; + bs.mask = size - 1; + if ((bs.name = calloc(size, sizeof(*bs.name))) == NULL || + (bs.len = calloc(size, sizeof(*bs.len))) == NULL) { + log_warn("session %u: scanning cur", session_id); + goto done; + } + for (i = 0; i < idx->nlines; i++) { + if ((p = strchr(idx->lines[i], ':')) == NULL) + continue; + len = strcspn(++p, ":"); + k = base_slot(&bs, p, len); + bs.name[k] = p; + bs.len[k] = len; + } + + if ((dp = opendirat(dfd, "cur")) == NULL) { + if (errno == ENOENT) + rv = 0; + else + log_warn("session %u: opendir cur", session_id); + goto done; + } + while ((de = readdir(dp)) != NULL) { + if (de->d_name[0] == '.' || + strpbrk(de->d_name, "\r\n") != NULL) + continue; + len = strcspn(de->d_name, ":"); + if (bs.name[base_slot(&bs, de->d_name, len)] != NULL) + continue; + info = de->d_name[len] == ':' ? de->d_name + len : NULL; + if (len >= sizeof(base) || + (info != NULL && strncmp(info, ":2,", 3) != 0)) { + if (mutate) + log_warnx("session %u: cur/%s: not indexed, " + "not a maildir name", session_id, + de->d_name); + continue; + } + /* RFC 9051 section 6.4.3: likely a failed EXPUNGE unlink */ + if (info != NULL && !idx->fresh && + strchr(info + 3, 'T') != NULL) { + if (mutate) + log_warnx("session %u: cur/%s: not indexed, " + "flagged deleted", session_id, de->d_name); + continue; + } + memcpy(base, de->d_name, len); + base[len] = '\0'; + if (!index_basename_valid(base)) + continue; + if (!mutate) { + rv = 1; + goto done; + } + if (nfound == cap) { + cap = cap != 0 ? cap * 2 : 64; + if ((grown = reallocarray(found, cap, + sizeof(*found))) == NULL) { + log_warn("session %u: scanning cur", + session_id); + goto done; + } + found = grown; + } + if ((found[nfound] = strdup(de->d_name)) == NULL) { + log_warn("session %u: scanning cur", session_id); + goto done; + } + nfound++; + } + if (nfound > 1) + qsort(found, nfound, sizeof(*found), name_cmp); + + for (i = 0; i < nfound; i++) { + name = found[i]; + len = strcspn(name, ":"); + if (prev != NULL && len == prevlen && + memcmp(prev, name, len) == 0) { + log_warnx("session %u: cur/%s: not indexed, another " + "file has its base name", session_id, name); + continue; + } + if (name[len] == '\0') { + if (snprintf(from, sizeof(from), "cur/%s", name) >= + (int)sizeof(from) || + snprintf(to, sizeof(to), "cur/%s:2,", name) >= + (int)sizeof(to) || + fstatat(dfd, to, &st, AT_SYMLINK_NOFOLLOW) == 0) { + log_warnx("session %u: cur/%s: not indexed, " + "cannot add \":2,\"", session_id, name); + continue; + } + if (errno != ENOENT || + renameat(dfd, from, dfd, to) == -1) { + log_warn("session %u: cur/%s: not indexed", + session_id, name); + continue; + } + synced = 1; + name = to + strlen("cur/"); + } + prev = found[i]; + prevlen = len; + /* RFC 9051 section 2.3.1.1: convert before the UID is given */ + if (to_crlf(dfd, "cur", name) == 1) + synced = 1; + memcpy(base, name, len); + base[len] = '\0'; + if (index_append(idx, idx->uidnext, base) == -1) + goto done; + idx->uidnext++; + added++; + } + if (synced && fsync(dirfd(dp)) == -1) + log_warn("session %u: fsync cur", session_id); + if (added > 0) + log_info("session %u: indexed %zu file(s) found in cur/", + session_id, added); + rv = added > 0; + +done: + if (dp != NULL) + closedir(dp); + for (i = 0; i < nfound; i++) + free(found[i]); + free(found); + free(bs.name); + free(bs.len); + if (rv == -1 && mutate) + index_free(idx); + return (rv); +} + +static int +index_scan(int dfd, struct mbox_index *idx, int mutate) +{ + int in_new, in_cur; + + if ((in_new = index_scan_new(dfd, idx, mutate)) == -1) + return (-1); + if (in_new && !mutate) + return (1); + if ((in_cur = index_scan_cur(dfd, idx, mutate)) == -1) + return (-1); + return (in_new || in_cur); +} + int refresh_index(int dfd, struct mbox_index *idx, int fd) { @@ -948,8 +1144,8 @@ refresh_index(int dfd, struct mbox_index *idx, int fd) if (index_load(fd, idx) == -1) return (-1); - if ((added = index_scan_new(dfd, idx, 1)) == -1) - return (-1); /* index_scan_new() has already freed idx */ + if ((added = index_scan(dfd, idx, 1)) == -1) + return (-1); /* index_scan() has already freed idx */ /* a fresh UIDVALIDITY must still be written down */ if (!added && !idx->fresh) @@ -998,7 +1194,7 @@ uid_find(const uint32_t *list, size_t n, uint32_t uid) return ((lo < n && list[lo] == uid) ? lo : n); } -/* RFC 9051 SS7.5.1: once told, never again, so drop it from the baseline */ +/* RFC 9051 section 7.5.1: told once, never again; drop it from the baseline */ int send_expunged(struct store_session *ss, uint32_t seqno, uint32_t uid) { @@ -1097,7 +1293,7 @@ saved_resolve(const struct store_session *ss, const st return (0); } -/* RFC 2180 SS4.1.2: does the set name a message expunged, not yet told? */ +/* RFC 2180 section 4.1.2: does the set name an expunge not yet told? */ int view_names_ghost(const struct store_session *ss, const struct mbox_index *idx, const struct seq_range *uids, uint32_t nuids) @@ -1122,7 +1318,7 @@ view_names_ghost(const struct store_session *ss, const return (0); } -/* RFC 9051 SS7.5.1: each EXPUNGE renumbers those above it */ +/* RFC 9051 section 7.5.1: each EXPUNGE renumbers those above it */ static size_t view_merge(const uint32_t *old, size_t oldn, const uint32_t *cur, size_t curn, int expunge_ok, uint32_t *view, size_t *viewn, @@ -1158,7 +1354,7 @@ view_merge(const uint32_t *old, size_t oldn, const uin return (gone); } -/* RFC 9051 SS6.3.13: flag changes, as FETCH with UID */ +/* RFC 9051 section 6.3.13: flag changes, as FETCH with UID */ static size_t view_send_flag_fetches(const struct mbox_index *idx, const uint32_t *old, size_t oldn, uint64_t since, const uint32_t *view, size_t viewn, @@ -1183,7 +1379,7 @@ view_send_flag_fetches(const struct mbox_index *idx, c if (locate_message_file(&ss->cur_snap, ss->mailbox_dir_fd, rec.basename, &size, suffix, sizeof(suffix)) == -1) { log_warnx("session %u: message %s (uid %u) indexed " - "but missing on disk, no IDLE flag push", + "but missing on disk, not pushed", session_id, rec.basename, rec.uid); continue; } @@ -1286,7 +1482,7 @@ idle_baseline_seed(struct store_session *ss, const str base->valid = 1; } -/* RFC 9051 SS5.2, SS7.5.1: what changed; EXPUNGE only if expunge_ok */ +/* RFC 9051 sections 5.2, 7.5.1: what changed; EXPUNGE only if expunge_ok */ int view_sync(struct store_session *ss, const struct mbox_index *idx, int expunge_ok) @@ -1316,7 +1512,7 @@ view_sync(struct store_session *ss, const struct mbox_ &viewn, &ss->iev); (void)view_send_flag_fetches(idx, base->uids, base->n, base->modseq, view, viewn, ss); - /* after EXPUNGEs too, as RFC 9051 SS6.3.13's example does */ + /* after EXPUNGEs too, as RFC 9051 section 6.3.13's example does */ if (gone > 0 || viewn != base->n) { memset(&ve, 0, sizeof(ve)); ve.exists = (uint32_t)viewn; @@ -1379,7 +1575,7 @@ handle_mbox_idle_refresh(struct store_session *ss) struct store_idle_baseline *base = &ss->idle_baseline; struct imsgev *iev = &ss->iev; struct mbox_index idx; - struct imsg_mbox_idle_refreshed reply; + struct imsg_mbox_idle_refreshed reply; struct index_lock il = INDEX_LOCK_INIT; int pending = 0, seeded, locked; @@ -1406,7 +1602,7 @@ handle_mbox_idle_refresh(struct store_session *ss) index_lock_release(&il); goto send; } - if ((pending = index_scan_new(ss->mailbox_dir_fd, &idx, 0)) == -1) { + if ((pending = index_scan(ss->mailbox_dir_fd, &idx, 0)) == -1) { index_free(&idx); index_lock_release(&il); goto send; @@ -1441,7 +1637,7 @@ handle_mbox_idle_refresh(struct store_session *ss) goto send; busy: - /* RFC 9051 SS7.5.1: a seed cannot skip, or EXPUNGEs repeat */ + /* RFC 9051 section 7.5.1: a seed cannot skip, or EXPUNGEs repeat */ reply.busy = 1; if (seeded && idle_seed_unlocked(ss) == 0) { reply.ok = 1; blob - d1f56a65c1e211d40c9520058aff64700ffd3ce8 blob + ccbf8986e244971ab893be8b94ac99c6eaa7fcc9 --- src/keymgr.c +++ src/keymgr.c @@ -7,28 +7,6 @@ * Copyright (c) 2008 Pierre-Yves Ritschard * Copyright (c) 2008 Reyk Floeter * - * This file's overall architecture -- a dedicated process holding the - * real TLS private key, a "fake private key" installed in the - * TLS-terminating process instead, and a process-wide OpenSSL - * RSA_METHOD/EC_KEY_METHOD engine override that forwards every - * private-key operation here as a synchronous imsg round-trip -- is - * smtpd's ca.c (Reyk Floeter, Gilles Chehade), ported to imapd's own - * imsg/privsep conventions rather than copied verbatim. The RSA_METHOD/ - * EC_KEY_METHOD engine-override code itself (the code this file's - * request/reply pair answers) lives in listener.c, not here -- see that - * file's header comment for the matching attribution. - * - * keymgr_pubkey_hash() below additionally replicates, byte-for-byte, - * smtpd's ssl.c hash_x509()/ssl_pubkey_hash() algorithm (Pierre-Yves - * Ritschard, Reyk Floeter, Gilles Chehade): SHA256 of the certificate's - * DER-encoded SubjectPublicKeyInfo, formatted "SHA256:" plus lowercase - * hex. That exact format is what libtls's own (unexported) - * tls_cert_pubkey_hash() tags onto the fake key's OpenSSL ex_data slot 0 - * at TLS-config time (lib/libtls/tls.c, confirmed by direct reading, not - * assumed) -- this process's key lookup only works if it derives the - * identical string from the same certificate, so the exact byte shape - * of hash_x509() is load-bearing here, not just a convenient precedent. - * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. @@ -168,15 +146,12 @@ keymgr_main(void) imsg_free(&imsg); } - /* a bad key is not fatal: its operations fail instead */ if (cert_len == 0 || key_len == 0) - log_warnx("no usable TLS cert/key at boot, TLS " - "private-key operations will fail until the next " - "SIGHUP reload supplies valid material"); + log_warnx("no usable TLS cert/key, handshakes fail until a " + "reload"); else if (keymgr_load(cert_buf, cert_len, key_buf, key_len) == -1) - log_warnx("failed to load TLS cert/key at boot " - "(see above), TLS private-key operations will fail " - "until the next SIGHUP reload supplies valid material"); + log_warnx("TLS cert/key not loaded, handshakes fail until a " + "reload"); explicit_bzero(key_buf, sizeof(key_buf)); keymgr_got_init = 1; @@ -203,17 +178,15 @@ keymgr_main(void) imsgev_init_from_ibuf(&iev_parent, &ibuf3, keymgr_dispatch_parent, NULL); -#ifdef __OpenBSD__ /* recvfd only: peers arrive, nothing is sent */ if (pledge("stdio recvfd", NULL) == -1) fatal("pledge"); -#endif event_dispatch(); fatalx("exited event loop"); } -/* as smtpd's ssl.c hash_x509() */ +/* as smtpd's ssl.c hash_x509(); libtls tags the key with this exact string */ static int keymgr_pubkey_hash(X509 *cert, char *hash, size_t hashlen) { @@ -254,6 +227,7 @@ keymgr_load(const char *cert_buf, size_t cert_len, con X509 *cert = NULL; EVP_PKEY *pkey = NULL; char hash[KEYMGR_HASH_MAX]; + int rc = -1; if (cert_len == 0 || key_len == 0) { log_warnx("empty cert or key, not (re)loading"); @@ -298,22 +272,14 @@ keymgr_load(const char *cert_buf, size_t cert_len, con (void)strlcpy(keymgr_hash, hash, sizeof(keymgr_hash)); log_info("TLS key loaded (%s)", keymgr_hash); + rc = 0; +fail: BIO_free(cert_bio); BIO_free(key_bio); X509_free(cert); - return (0); - -fail: - if (cert_bio != NULL) - BIO_free(cert_bio); - if (key_bio != NULL) - BIO_free(key_bio); - if (cert != NULL) - X509_free(cert); - if (pkey != NULL) - EVP_PKEY_free(pkey); - return (-1); + EVP_PKEY_free(pkey); + return (rc); } static void @@ -324,7 +290,7 @@ keymgr_try_reload(void) if (keymgr_load(reload_cert_buf, reload_cert_len, reload_key_buf, reload_key_len) == -1) - log_warnx("SIGHUP reload: keeping previous key, see above"); + log_warnx("SIGHUP: keeping the previous key"); explicit_bzero(reload_key_buf, sizeof(reload_key_buf)); reload_got_cert = reload_got_key = 0; } @@ -364,11 +330,13 @@ keymgr_dispatch_parent(int fd, short event, void *arg) keymgr_key_free(); exit(0); case IMSG_SETUP_PEER: { - uint32_t sess_id = imsg_get_id(&imsg); - int peer_fd = imsg_get_fd(&imsg); + uint32_t sess_id; + int peer_fd; struct keymgr_peer *kp; int flags; + sess_id = imsg_get_id(&imsg); + peer_fd = imsg_get_fd(&imsg); if (peer_fd == -1) { log_warnx("IMSG_SETUP_PEER carried no fd"); break; @@ -394,8 +362,9 @@ keymgr_dispatch_parent(int fd, short event, void *arg) break; } case IMSG_TLS_CERT: { - size_t len = imsg_get_len(&imsg); + size_t len; + len = imsg_get_len(&imsg); if (len > sizeof(reload_cert_buf)) { log_warnx("SIGHUP reload: TLS cert " "too large (%zu > %zu)", len, @@ -413,8 +382,9 @@ keymgr_dispatch_parent(int fd, short event, void *arg) break; } case IMSG_KEYMGR_INIT: { - size_t len = imsg_get_len(&imsg); + size_t len; + len = imsg_get_len(&imsg); if (len > sizeof(reload_key_buf)) { log_warnx("SIGHUP reload: TLS key " "too large (%zu > %zu)", len, @@ -439,7 +409,6 @@ keymgr_dispatch_parent(int fd, short event, void *arg) imsg_free(&imsg); } imsgev_rearm_read(iev); - (void)fd; } static void @@ -497,7 +466,6 @@ keymgr_dispatch_listener(int fd, short event, void *ar imsg_free(&imsg); } imsgev_rearm_read(iev); - (void)fd; } static void @@ -514,21 +482,21 @@ static int keymgr_recv_trailing(struct imsg *imsg, uint32_t len, unsigned char *buf, size_t bufsize) { - size_t bodylen = imsg_get_len(imsg); + size_t bodylen; + bodylen = imsg_get_len(imsg); if (len > bufsize || bodylen != (size_t)len) { - log_warnx("trailing data length mismatch (header " - "says %u, imsg has %zu bytes, buffer holds %zu)", len, - bodylen, bufsize); - return (0); + log_warnx("trailing data length mismatch (header %u, imsg " + "%zu, buffer %zu)", len, bodylen, bufsize); + return (-1); } if (bodylen == 0) - return (1); + return (0); if (imsg_get_buf(imsg, buf, bodylen) == -1) { log_warnx("bad trailing data"); - return (0); + return (-1); } - return (1); + return (0); } /* the reply reuses the request's type, correlated by id */ @@ -541,9 +509,8 @@ keymgr_reply(struct imsgev *iev, uint32_t type, uint32 KEYMGR_DATA_MAX]; if (ok && tolen > KEYMGR_DATA_MAX) { - log_warnx("keymgr_reply: %zu-byte result exceeds " - "KEYMGR_DATA_MAX (%d), refusing", tolen, - KEYMGR_DATA_MAX); + log_warnx("keymgr_reply: %zu bytes over KEYMGR_DATA_MAX %d", + tolen, KEYMGR_DATA_MAX); ok = 0; } @@ -584,20 +551,18 @@ keymgr_handle_rsa(struct imsgev *iev, struct imsg *ims /* imsg_get_buf() does not NUL-terminate */ req.hash[sizeof(req.hash) - 1] = '\0'; - if (!keymgr_recv_trailing(imsg, req.fromlen, from, sizeof(from))) { + if (keymgr_recv_trailing(imsg, req.fromlen, from, sizeof(from)) == -1) { keymgr_reply(iev, type, id, 0, NULL, 0); return; } if (!keymgr_got_init) { - log_warnx("%s request before IMSG_KEYMGR_INIT " - "completed, refusing", opname); + log_warnx("%s before IMSG_KEYMGR_INIT", opname); keymgr_reply(iev, type, id, 0, NULL, 0); return; } if (keymgr_pkey == NULL || strcmp(req.hash, keymgr_hash) != 0) { - log_warnx("%s request for unrecognized key hash, " - "refusing", opname); + log_warnx("%s: unknown key hash", opname); keymgr_reply(iev, type, id, 0, NULL, 0); return; } @@ -634,7 +599,6 @@ keymgr_handle_rsa(struct imsgev *iev, struct imsg *ims explicit_bzero(to, sizeof(to)); } -/* as smtpd's ca.c ca_imsg() */ static void keymgr_handle_ecdsa(struct imsgev *iev, struct imsg *imsg, uint32_t id) { @@ -652,20 +616,18 @@ keymgr_handle_ecdsa(struct imsgev *iev, struct imsg *i } req.hash[sizeof(req.hash) - 1] = '\0'; - if (!keymgr_recv_trailing(imsg, req.fromlen, dgst, sizeof(dgst))) { + if (keymgr_recv_trailing(imsg, req.fromlen, dgst, sizeof(dgst)) == -1) { keymgr_reply(iev, IMSG_KEYMGR_ECDSA_SIGN, id, 0, NULL, 0); return; } if (!keymgr_got_init) { - log_warnx("ECDSA_SIGN request before " - "IMSG_KEYMGR_INIT completed, refusing"); + log_warnx("ECDSA_SIGN before IMSG_KEYMGR_INIT"); keymgr_reply(iev, IMSG_KEYMGR_ECDSA_SIGN, id, 0, NULL, 0); return; } if (keymgr_pkey == NULL || strcmp(req.hash, keymgr_hash) != 0) { - log_warnx("ECDSA_SIGN request for unrecognized key " - "hash, refusing"); + log_warnx("ECDSA_SIGN: unknown key hash"); keymgr_reply(iev, IMSG_KEYMGR_ECDSA_SIGN, id, 0, NULL, 0); return; } blob - 72cd56b418ec77a0a000346f1b2af0a4aa3ff452 blob + 5a6b4b20eaf510e140a2221587e287c2228b1633 --- src/listener.c +++ src/listener.c @@ -5,19 +5,6 @@ * Copyright (c) 2014 Reyk Floeter * Copyright (c) 2012 Gilles Chehade * - * The RSA_METHOD/EC_KEY_METHOD engine override below (keymgr_engine_ - * init() and everything it installs) adapts smtpd's ca.c - * (rsa_engine_init()/ecdsa_engine_init()/rsae_priv_enc()/rsae_priv_ - * dec()/ecdsae_do_sign(), ca.c:289-558) to imapd's own imsg - * conventions: the OpenSSL API shape leaves little room for - * independent structure, and this project's own licensing - * precedent (log.c, imsgev.c) already treats a borrow this close as - * needing the original author's copyright even where the - * implementation differs. keymgr_use_fake_private_key()'s two-line call - * shape is lifted from smtpd's smtp.c:187-193 (Gilles Chehade, - * Pierre-Yves Ritschard, Jacek Masiulaniec); see that function's own - * comment. - * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. @@ -31,10 +18,10 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - +#include #include -#include #include +#include #include @@ -45,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -67,7 +55,7 @@ #include "log.h" #include "listener.h" -struct session_list sessions = TAILQ_HEAD_INITIALIZER(sessions); +static struct session sess; /* one process, one session */ struct imsgev iev_auth; struct imsgev iev_parent; @@ -108,7 +96,7 @@ struct imap_cmd_entry { (ST_NOTAUTH | (1U << SESSION_AUTHENTICATING) | \ (1U << SESSION_STORE_PENDING)) -/* RFC 9051 SS9; excludes transient states, which have their own pending_tag. */ +/* RFC 9051 section 9; the transient states have their own pending_tag */ #define ST_AUTH \ ((1U << SESSION_AUTHENTICATED) | (1U << SESSION_SELECTED)) #define ST_SELECTED (1U << SESSION_SELECTED) @@ -122,7 +110,7 @@ static const struct imap_cmd_entry imap_cmds[] = { { "STARTTLS", ST_NOTAUTH, cmd_starttls }, { "AUTHENTICATE", ST_NOTAUTH, cmd_authenticate }, - /* command-auth, RFC 9051 SS6.3 order */ + /* command-auth, RFC 9051 section 6.3 order */ { "ENABLE", ST_AUTH, cmd_enable }, { "SELECT", ST_AUTH, cmd_select }, { "EXAMINE", ST_AUTH, cmd_examine }, @@ -138,7 +126,7 @@ static const struct imap_cmd_entry imap_cmds[] = { { "APPEND", ST_AUTH, cmd_append }, { "IDLE", ST_AUTH, cmd_idle }, - /* command-select, RFC 9051 SS6.4 order */ + /* command-select, RFC 9051 section 6.4 order */ { "CLOSE", ST_SELECTED, cmd_close }, { "UNSELECT", ST_SELECTED, cmd_unselect }, { "EXPUNGE", ST_SELECTED, cmd_expunge }, @@ -149,7 +137,6 @@ static const struct imap_cmd_entry imap_cmds[] = { { "MOVE", ST_SELECTED, cmd_move }, { "UID", ST_SELECTED, cmd_uid }, }; -#define NUM_IMAP_CMDS (sizeof(imap_cmds) / sizeof(imap_cmds[0])) /* internal to libtls, declared as smtpd/smtp.c does */ void tls_config_use_fake_private_key(struct tls_config *); @@ -159,8 +146,8 @@ keymgr_set_fake_keypair(struct tls_config *config, con size_t cert_len) { tls_config_use_fake_private_key(config); - return tls_config_set_keypair_mem(config, (const uint8_t *)cert_buf, - cert_len, NULL, 0); + return (tls_config_set_keypair_mem(config, + (const uint8_t *)cert_buf, cert_len, NULL, 0)); } /* private-key operations go to keymgr, after smtpd's ca.c */ @@ -348,29 +335,27 @@ keymgr_assert_fake_key(const char *hash, const BIGNUM /* check A: the placeholder key has no private part */ if (priv != NULL) - fatalx("%s: key object carries a private component -- " - "libtls is no longer using a placeholder key, and this " - "process is not separated from the TLS private key", op); + fatalx("%s: key object carries a private component", op); /* check B: one keypair, so a missing tag is a regression */ if (hash == NULL) - fatalx("%s: no pubkey-hash tag on the key object -- libtls's " - "ex_data slot 0 tagging has changed", op); + fatalx("%s: no pubkey-hash tag on the key object", op); /* check C1: NUL-terminated within KEYMGR_HASH_MAX */ for (i = 0; i < KEYMGR_HASH_MAX; i++) if (hash[i] == '\0') return; - fatalx("%s: pubkey-hash tag is not a NUL-terminated string within " - "%d bytes -- refusing to read it", op, KEYMGR_HASH_MAX); + fatalx("%s: pubkey-hash tag not NUL-terminated within %d bytes", + op, KEYMGR_HASH_MAX); } static int keymgr_rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) { - const char *hash = RSA_get_ex_data(rsa, 0); + const char *hash; + hash = RSA_get_ex_data(rsa, 0); keymgr_assert_fake_key(hash, RSA_get0_d(rsa), "RSA_PRIVENC"); return (keymgr_forward_rsa(IMSG_KEYMGR_RSA_PRIVENC, hash, from, flen, to, (size_t)RSA_size(rsa), padding)); @@ -380,8 +365,9 @@ static int keymgr_rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) { - const char *hash = RSA_get_ex_data(rsa, 0); + const char *hash; + hash = RSA_get_ex_data(rsa, 0); keymgr_assert_fake_key(hash, RSA_get0_d(rsa), "RSA_PRIVDEC"); return (keymgr_forward_rsa(IMSG_KEYMGR_RSA_PRIVDEC, hash, from, flen, to, (size_t)RSA_size(rsa), padding)); @@ -391,11 +377,9 @@ static ECDSA_SIG * keymgr_ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) { - const char *hash = EC_KEY_get_ex_data(eckey, 0); + const char *hash; - (void)inv; - (void)rp; - + hash = EC_KEY_get_ex_data(eckey, 0); keymgr_assert_fake_key(hash, EC_KEY_get0_private_key(eckey), "ECDSA_SIGN"); return (keymgr_forward_ecdsa(hash, dgst, dgst_len)); @@ -486,9 +470,11 @@ listener_main(void) } switch (imsg_get_type(&imsg)) { case IMSG_SETUP_PEER: { - uint32_t id = imsg_get_id(&imsg); - int peer_fd = imsg_get_fd(&imsg); + uint32_t id; + int peer_fd; + id = imsg_get_id(&imsg); + peer_fd = imsg_get_fd(&imsg); if (peer_fd == -1) { log_warnx("listener: IMSG_SETUP_PEER " "carried no fd"); @@ -617,9 +603,8 @@ listener_main(void) NULL); else { iev_auth.ibuf.fd = -1; - log_warnx("session %u: no auth-worker was spawned for this " - "connection, AUTHENTICATE/LOGIN will fail until a new " - "connection gets one", sinit.session_id); + log_warnx("session %u: no auth-worker for this connection", + sinit.session_id); } if (imsgbuf_init(&keymgr_ibuf, keymgr_peer_fd) == -1) @@ -638,10 +623,8 @@ listener_main(void) sinit.implicit_tls, &sinit.remote_ss, sinit.remote_sslen); /* no "inet": getnameinfo(3) only formats numeric bytes */ -#ifdef __OpenBSD__ if (pledge("stdio recvfd", NULL) == -1) fatal("pledge"); -#endif event_dispatch(); fatalx("listener: exited event loop"); @@ -652,9 +635,6 @@ session_login_grace_expired(int fd, short event, void { struct session *s = arg; - (void)fd; - (void)event; - log_info("session %u: closing peer=%.200s, no authentication within " "%u seconds", s->id, s->remote_addr, listener_login_grace_secs); session_teardown(s, "login-grace"); @@ -672,9 +652,7 @@ session_login_grace_init(struct session *s) tv.tv_sec = (time_t)listener_login_grace_secs; tv.tv_usec = 0; if (evtimer_add(&s->grace_ev, &tv) == -1) - log_warnx("session %u: evtimer_add (login grace); this " - "session will not be closed if it never authenticates", - s->id); + log_warnx("session %u: evtimer_add (login grace)", s->id); } void @@ -687,15 +665,10 @@ static void listener_start_session(uint32_t session_id, int client_fd, int implicit_tls, const struct sockaddr_storage *ss, socklen_t sslen) { - struct session *s; + struct session *s = &sess; + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; - s = calloc(1, sizeof(*s)); - if (s == NULL) { - log_warn("calloc"); - close(client_fd); - exit(1); - } - s->pending_body_fd = -1; /* calloc(3)'s 0 is a real descriptor */ + s->pending_body_fd = -1; /* 0 is a real descriptor */ session_idle_poll_init(s); /* before anything can tear s down */ s->id = session_id; s->client_fd = client_fd; @@ -704,22 +677,16 @@ listener_start_session(uint32_t session_id, int client session_login_grace_init(s); if (clock_gettime(CLOCK_MONOTONIC, &s->connected_at) == -1) log_warn("session %u: clock_gettime", s->id); - TAILQ_INSERT_TAIL(&sessions, s, entry); - { - char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + /* numeric: no resolver or network I/O */ + if (getnameinfo((const struct sockaddr *)ss, sslen, hbuf, sizeof(hbuf), + sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == 0) + snprintf(s->remote_addr, sizeof(s->remote_addr), + ss->ss_family == AF_INET6 ? "[%s]:%s" : "%s:%s", hbuf, + sbuf); + else + strlcpy(s->remote_addr, "?", sizeof(s->remote_addr)); - /* numeric: no resolver or network I/O */ - if (getnameinfo((const struct sockaddr *)ss, sslen, hbuf, - sizeof(hbuf), sbuf, sizeof(sbuf), - NI_NUMERICHOST | NI_NUMERICSERV) == 0) - snprintf(s->remote_addr, sizeof(s->remote_addr), - ss->ss_family == AF_INET6 ? "[%s]:%s" : "%s:%s", - hbuf, sbuf); - else - strlcpy(s->remote_addr, "?", sizeof(s->remote_addr)); - } - /* %.200s bounds untrusted text, the way sshd's auth.c does */ /* tls= is how it was accepted; the close line has the final state */ log_info("session %u: connected peer=%.200s tls=%s", s->id, @@ -781,9 +748,6 @@ session_tls_handshake(int fd, short event, void *arg) struct session *s = arg; int ret; - (void)fd; - (void)event; - ret = tls_handshake(s->tls_ctx); if (ret == 0) { s->tls_active = 1; @@ -817,7 +781,7 @@ session_tls_handshake(int fd, short event, void *arg) session_teardown(s, "tls-error"); } -/* RFC 9051 SS7.1.1's example OK-response text, used verbatim. */ +/* RFC 9051 section 7.1.1's example OK-response text, used verbatim. */ void session_send_greeting(struct session *s) { @@ -829,16 +793,17 @@ session_send_greeting(struct session *s) static int session_is_busy(const struct session *); static int session_enqueue_cmd(struct session *, const char *); -/* RFC 9051 SS4.3 hard cap on a non-synchronizing literal. */ +/* RFC 9051 section 4.3 hard cap on a non-synchronizing literal. */ #define IMAP_NONSYNC_LITERAL_MAX 4096 -/* RFC 9051 SS4.3 "{n}" or "{n+}" at p; *endp is past its "}" */ +/* RFC 9051 section 4.3 "{n}" or "{n+}" at p; *endp is past its "}" */ static int literal_count(const char *p, uint64_t *lenp, int *nonsyncp, const char **endp) { - char digits[24]; - size_t dlen; - unsigned long long v; + char digits[24]; + const char *errstr; + size_t dlen; + long long v; if (*p++ != '{') return (0); @@ -847,9 +812,8 @@ literal_count(const char *p, uint64_t *lenp, int *nons return (0); memcpy(digits, p, dlen); digits[dlen] = '\0'; - errno = 0; - v = strtoull(digits, NULL, 10); - if (errno == ERANGE) + v = strtonum(digits, 0, LLONG_MAX, &errstr); + if (errstr != NULL) return (0); p += dlen; *nonsyncp = (*p == '+'); @@ -962,7 +926,7 @@ session_gather(struct session *s, uint64_t n) return (1); } -/* RFC 9051 SS4.3: run a command, re-parsed as each literal arrives */ +/* RFC 9051 section 4.3: run a command, re-parsed as each literal arrives */ static int session_run_cmd(struct session *s, char *cmd) { @@ -1001,7 +965,7 @@ session_queue(struct session *s, const char *cmd) return (0); } -/* RFC 9051 SS9: a tag may not contain "+" */ +/* RFC 9051 section 9: a tag may not contain "+" */ static int tag_is_valid(const char *tag) { @@ -1018,24 +982,16 @@ tag_is_valid(const char *tag) return (1); } -void -session_dispatch_client(int fd, short event, void *arg) +/* octets read into inbuf, or -1: retry later, or the session is gone */ +static ssize_t +session_read_client(struct session *s, int fd, size_t *tls_wantp) { - struct session *s = arg; - ssize_t n; - char *crlf; - size_t tls_want = 0; + ssize_t n; - (void)event; - - if (s->write_failed) { - session_teardown(s, "io-error"); - return; - } - + *tls_wantp = 0; if (s->tls_active) { - tls_want = sizeof(s->inbuf) - s->inbuflen; - n = tls_read(s->tls_ctx, s->inbuf + s->inbuflen, tls_want); + *tls_wantp = sizeof(s->inbuf) - s->inbuflen; + n = tls_read(s->tls_ctx, s->inbuf + s->inbuflen, *tls_wantp); if (n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT) { /* tls_read() may want to write */ event_del(&s->client_ev); @@ -1043,13 +999,13 @@ session_dispatch_client(int fd, short event, void *arg (n == TLS_WANT_POLLIN) ? EV_READ : EV_WRITE, session_dispatch_client, s); event_add(&s->client_ev, NULL); - return; + return (-1); } if (n == -1) { log_warnx("session %u: tls_read: %s", s->id, tls_error(s->tls_ctx)); session_teardown(s, "tls-error"); - return; + return (-1); } session_arm_client_read(s); } else { @@ -1058,219 +1014,255 @@ session_dispatch_client(int fd, short event, void *arg if (n == -1) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) - return; + return (-1); log_warn("session %u: read", s->id); session_teardown(s, "io-error"); - return; + return (-1); } } - if (n == 0) { log_debug("session %u: client closed connection", s->id); session_teardown(s, "client-closed"); - return; + return (-1); } - s->inbuflen += (size_t)n; + return (n); +} - for (;;) { - uint64_t nonsync_len; - size_t consumed, linelen; - int alive, resume; +/* consumption steps: 1 consumed, 0 wants more input, -1 session gone */ +static int +session_discard_literal(struct session *s) +{ + uint64_t take; - if (s->literal_discard > 0) { - uint64_t take; + take = (uint64_t)s->inbuflen < s->literal_discard ? + (uint64_t)s->inbuflen : s->literal_discard; + if (take > 0) { + memmove(s->inbuf, s->inbuf + take, s->inbuflen - (size_t)take); + s->inbuflen -= (size_t)take; + s->literal_discard -= take; + } + if (s->literal_discard > 0) + return (0); + s->literal_skipline = 1; + return (1); +} - take = (uint64_t)s->inbuflen < s->literal_discard ? - (uint64_t)s->inbuflen : s->literal_discard; - if (take > 0) { - memmove(s->inbuf, s->inbuf + take, - s->inbuflen - (size_t)take); - s->inbuflen -= (size_t)take; - s->literal_discard -= take; - } - if (s->literal_discard > 0) - break; - s->literal_skipline = 1; - continue; - } +/* RFC 9051 section 4.3 literal; may contain a CRLF */ +static int +session_stream_append(struct session *s) +{ + uint64_t want, take; - /* RFC 9051 SS4.3 literal; may contain a CRLF */ - if (s->literal_pending) { - uint64_t want, take; - - want = s->literal_remaining; - take = (uint64_t)s->inbuflen < want ? - (uint64_t)s->inbuflen : want; - - if (take > 0) { - if (imsg_compose(&s->store_iev->ibuf, - IMSG_MBOX_APPEND_DATA, 0, 0, -1, s->inbuf, - (size_t)take) == -1) { - log_warn("session %u: imsg_compose " - "IMSG_MBOX_APPEND_DATA", s->id); - session_teardown(s, "io-error"); - return; - } - s->literal_remaining -= take; - memmove(s->inbuf, s->inbuf + take, - s->inbuflen - (size_t)take); - s->inbuflen -= (size_t)take; - } - - if (s->literal_remaining > 0) - break; - - if (s->inbuflen < 2) - break; - if (s->inbuf[0] != '\r' || s->inbuf[1] != '\n') { - /* no reliable resync point, give up */ - log_warnx("session %u: expected CRLF after " - "literal data, closing", s->id); - session_teardown(s, "protocol-error"); - return; - } - memmove(s->inbuf, s->inbuf + 2, s->inbuflen - 2); - s->inbuflen -= 2; - - s->literal_pending = 0; - alive = session_finish_append(s); - if (!alive) - return; - continue; + want = s->literal_remaining; + take = (uint64_t)s->inbuflen < want ? (uint64_t)s->inbuflen : want; + if (take > 0) { + if (imsg_compose(&s->store_iev->ibuf, IMSG_MBOX_APPEND_DATA, + 0, 0, -1, s->inbuf, (size_t)take) == -1) { + log_warn("session %u: imsg_compose " + "IMSG_MBOX_APPEND_DATA", s->id); + session_teardown(s, "io-error"); + return (-1); } + s->literal_remaining -= take; + memmove(s->inbuf, s->inbuf + take, s->inbuflen - (size_t)take); + s->inbuflen -= (size_t)take; + } + if (s->literal_remaining > 0) + return (0); - if (s->cmd_gather && s->cmd_octets > 0) { - size_t take; + if (s->inbuflen < 2) + return (0); + if (s->inbuf[0] != '\r' || s->inbuf[1] != '\n') { + /* no reliable resync point, give up */ + log_warnx("session %u: expected CRLF after literal data, " + "closing", s->id); + session_teardown(s, "protocol-error"); + return (-1); + } + memmove(s->inbuf, s->inbuf + 2, s->inbuflen - 2); + s->inbuflen -= 2; - take = s->inbuflen < s->cmd_octets ? - s->inbuflen : s->cmd_octets; - if (take == 0) - break; - if (memchr(s->inbuf, '\0', take) != NULL) { - /* RFC 9051 SS9: CHAR8 excludes NUL */ - session_cmd_reply(s, "BAD", "NUL in a literal"); - s->literal_discard = s->cmd_octets; - s->cmd_gather = 0; - s->cmd_queued = 0; - s->cmd_octets = 0; - continue; - } - memcpy(s->cmdbuf + s->cmdlen, s->inbuf, take); - s->cmdlen += take; - s->cmd_octets -= take; - memmove(s->inbuf, s->inbuf + take, s->inbuflen - take); - s->inbuflen -= take; - continue; - } + s->literal_pending = 0; + if (!session_finish_append(s)) + return (-1); + return (1); +} - crlf = memmem(s->inbuf, s->inbuflen, "\r\n", 2); - if (crlf == NULL) - break; +static int +session_gather_octets(struct session *s) +{ + size_t take; - linelen = (size_t)(crlf - s->inbuf); - consumed = linelen + 2; + take = s->inbuflen < s->cmd_octets ? s->inbuflen : s->cmd_octets; + if (take == 0) + return (0); + if (memchr(s->inbuf, '\0', take) != NULL) { + /* RFC 9051 section 9: CHAR8 excludes NUL */ + session_cmd_reply(s, "BAD", "NUL in a literal"); + s->literal_discard = s->cmd_octets; + s->cmd_gather = 0; + s->cmd_queued = 0; + s->cmd_octets = 0; + return (1); + } + memcpy(s->cmdbuf + s->cmdlen, s->inbuf, take); + s->cmdlen += take; + s->cmd_octets -= take; + memmove(s->inbuf, s->inbuf + take, s->inbuflen - take); + s->inbuflen -= take; + return (1); +} - /* RFC 9051 SS2.2/SS9: no CR, LF or NUL inside a line */ - if (memchr(s->inbuf, '\r', linelen) != NULL || - memchr(s->inbuf, '\n', linelen) != NULL || - memchr(s->inbuf, '\0', linelen) != NULL) { - static const char bad[] = "* BAD bare CR, LF or NUL " - "in command line, closing connection\r\n"; +static int +session_route_line(struct session *s, size_t linelen, uint64_t nonsync_len) +{ + int alive, resume; - log_warnx("session %u: bare CR/LF/NUL in command " - "line, closing", s->id); - session_write(s, bad, sizeof(bad) - 1); - session_teardown(s, "protocol-error"); - return; - } - - *crlf = '\0'; - - nonsync_len = 0; - if (line_nonsync_literal(s->inbuf, &nonsync_len) && - nonsync_len > IMAP_NONSYNC_LITERAL_MAX) { - /* RFC 7888 SS4, SS5: BYE, with RFC 4469's TOOBIG */ - static const char bye[] = "* BYE [TOOBIG] " - "non-synchronizing literal exceeds 4096 octets\r\n"; - - log_warnx("session %u: oversized non-synchronizing " - "literal (%llu), closing", s->id, - (unsigned long long)nonsync_len); - session_write(s, bye, sizeof(bye) - 1); - session_teardown(s, "limit-exceeded"); - return; - } - - resume = 0; - if (s->literal_skipline) { - s->literal_skipline = 0; - alive = 1; - resume = 1; - } else if (s->cmd_gather) { - s->cmd_gather = 0; - alive = 1; - if (linelen >= sizeof(s->cmdbuf) - s->cmdlen) { - session_cmd_reply(s, "NO", cmd_too_long); - s->cmd_queued = 0; - } else { - memcpy(s->cmdbuf + s->cmdlen, s->inbuf, - linelen + 1); - if (!s->cmd_queued) - alive = session_run_cmd(s, s->cmdbuf); - else if (nonsync_len > 0) { - s->cmdlen += linelen; - (void)session_gather(s, nonsync_len); - } else { - s->cmd_queued = 0; - alive = session_queue(s, s->cmdbuf); - } - } - resume = 1; - } else if (s->auth_cont) { - /* the line is the user's base64 password, see below */ - s->scrub_inbuf = 1; - alive = session_handle_auth_continuation(s, s->inbuf); - } else if (s->idling) { - alive = session_handle_idle_continuation(s, s->inbuf); - } else if (session_is_busy(s)) { - /* RFC 9051 SS5.5: queue while one is in flight */ - alive = 1; - if (nonsync_len > 0) { - (void)strlcpy(s->cmdbuf, s->inbuf, - sizeof(s->cmdbuf)); - s->cmdlen = strlen(s->cmdbuf); - s->cmd_queued = 1; - (void)session_gather(s, nonsync_len); - } else - alive = session_queue(s, s->inbuf); + resume = 0; + if (s->literal_skipline) { + s->literal_skipline = 0; + alive = 1; + resume = 1; + } else if (s->cmd_gather) { + s->cmd_gather = 0; + alive = 1; + if (linelen >= sizeof(s->cmdbuf) - s->cmdlen) { + session_cmd_reply(s, "NO", cmd_too_long); + s->cmd_queued = 0; } else { - alive = session_run_cmd(s, s->inbuf); + memcpy(s->cmdbuf + s->cmdlen, s->inbuf, linelen + 1); + if (!s->cmd_queued) + alive = session_run_cmd(s, s->cmdbuf); + else if (nonsync_len > 0) { + s->cmdlen += linelen; + (void)session_gather(s, nonsync_len); + } else { + s->cmd_queued = 0; + alive = session_queue(s, s->cmdbuf); + } } - if (alive && resume && nonsync_len == 0) - alive = session_dequeue_next(s); - if (alive == 0) - return; /* s was torn down (LOGOUT), do not touch */ + resume = 1; + } else if (s->auth_cont) { + /* the line is the user's base64 password, scrubbed after use */ + s->scrub_inbuf = 1; + alive = session_handle_auth_continuation(s, s->inbuf); + } else if (s->idling) { + alive = session_handle_idle_continuation(s, s->inbuf); + } else if (session_is_busy(s)) { + /* RFC 9051 section 5.5: queue while one is in flight */ + alive = 1; + if (nonsync_len > 0) { + (void)strlcpy(s->cmdbuf, s->inbuf, sizeof(s->cmdbuf)); + s->cmdlen = strlen(s->cmdbuf); + s->cmd_queued = 1; + (void)session_gather(s, nonsync_len); + } else + alive = session_queue(s, s->inbuf); + } else { + alive = session_run_cmd(s, s->inbuf); + } + if (alive && resume && nonsync_len == 0) + alive = session_dequeue_next(s); + return (alive); +} - if (nonsync_len > 0 && !s->literal_pending && - !s->cmd_gather) - s->literal_discard = nonsync_len; +static int +session_consume_line(struct session *s) +{ + uint64_t nonsync_len; + size_t consumed, linelen; + char *crlf; - /* cmd_starttls() zeroes inbuflen */ - if (consumed > s->inbuflen) - consumed = s->inbuflen; + crlf = memmem(s->inbuf, s->inbuflen, "\r\n", 2); + if (crlf == NULL) + return (0); + linelen = (size_t)(crlf - s->inbuf); + consumed = linelen + 2; - /* don't leave a base64 password in inbuf */ - if (s->scrub_inbuf) { - explicit_bzero(s->inbuf, consumed); - s->scrub_inbuf = 0; - } + /* RFC 9051 section 2.2/section 9: no CR, LF or NUL inside a line */ + if (memchr(s->inbuf, '\r', linelen) != NULL || + memchr(s->inbuf, '\n', linelen) != NULL || + memchr(s->inbuf, '\0', linelen) != NULL) { + static const char bad[] = "* BAD bare CR, LF or NUL " + "in command line, closing connection\r\n"; - memmove(s->inbuf, s->inbuf + consumed, s->inbuflen - consumed); - s->inbuflen -= consumed; + log_warnx("session %u: bare CR/LF/NUL in command " + "line, closing", s->id); + session_write(s, bad, sizeof(bad) - 1); + session_teardown(s, "protocol-error"); + return (-1); } + *crlf = '\0'; + nonsync_len = 0; + if (line_nonsync_literal(s->inbuf, &nonsync_len) && + nonsync_len > IMAP_NONSYNC_LITERAL_MAX) { + /* RFC 7888 section 4, section 5: BYE, with RFC 4469's TOOBIG */ + static const char bye[] = "* BYE [TOOBIG] " + "non-synchronizing literal exceeds 4096 octets\r\n"; + + log_warnx("session %u: oversized non-synchronizing " + "literal (%llu), closing", s->id, + (unsigned long long)nonsync_len); + session_write(s, bye, sizeof(bye) - 1); + session_teardown(s, "limit-exceeded"); + return (-1); + } + + if (!session_route_line(s, linelen, nonsync_len)) + return (-1); /* s was torn down (LOGOUT), do not touch */ + + if (nonsync_len > 0 && !s->literal_pending && !s->cmd_gather) + s->literal_discard = nonsync_len; + + /* cmd_starttls() zeroes inbuflen */ + if (consumed > s->inbuflen) + consumed = s->inbuflen; + + /* don't leave a base64 password in inbuf */ + if (s->scrub_inbuf) { + explicit_bzero(s->inbuf, consumed); + s->scrub_inbuf = 0; + } + + memmove(s->inbuf, s->inbuf + consumed, s->inbuflen - consumed); + s->inbuflen -= consumed; + return (1); +} + +void +session_dispatch_client(int fd, short event, void *arg) +{ + struct session *s = arg; + ssize_t n; + size_t tls_want; + int rc; + + if (s->write_failed) { + session_teardown(s, "io-error"); + return; + } + if ((n = session_read_client(s, fd, &tls_want)) == -1) + return; + s->inbuflen += (size_t)n; + + for (;;) { + if (s->literal_discard > 0) + rc = session_discard_literal(s); + else if (s->literal_pending) + rc = session_stream_append(s); + else if (s->cmd_gather && s->cmd_octets > 0) + rc = session_gather_octets(s); + else + rc = session_consume_line(s); + if (rc == -1) + return; + if (rc == 0) + break; + } + if (s->inbuflen == sizeof(s->inbuf)) { - /* RFC 9051 SS7.1.3's example text */ + /* RFC 9051 section 7.1.3's example text */ static const char bad[] = "* BAD command line too long\r\n"; log_warnx("session %u: command line too long, closing", @@ -1281,12 +1273,12 @@ session_dispatch_client(int fd, short event, void *arg } /* libtls may hold bytes that EV_READ will not report */ - if (s->tls_active && n > 0 && (size_t)n == tls_want && + if (s->tls_active && (size_t)n == tls_want && s->inbuflen < sizeof(s->inbuf)) event_active(&s->client_ev, EV_READ, 1); } -/* RFC 9051 SS5.4: a short timer before login, 30 minutes after */ +/* RFC 9051 section 5.4: a short timer before login, 30 minutes after */ #define SESSION_WRITE_PREAUTH_MS 5000 #define SESSION_WRITE_AUTH_MS (1800 * 1000) @@ -1380,7 +1372,6 @@ fail: event_active(&s->client_ev, EV_READ, 1); } - /* on overflow the line still ends in CRLF */ static void session_writef(struct session *, const char *, ...) __attribute__((__format__ (printf, 2, 3))); @@ -1413,7 +1404,6 @@ session_reply(struct session *s, const char *tag, cons session_writef(s, "%s %s %s\r\n", tag, status, text); } - void session_untagged(struct session *s, const char *text) { @@ -1421,38 +1411,22 @@ session_untagged(struct session *s, const char *text) } int -send_mbox_request(struct session *s, int imsg_type, const char *what, - const char *imsgname, const void *req, size_t reqlen, const void *elems, - uint32_t nelems, size_t elemsize) +send_mbox_request(struct session *s, int imsg_type, const char *imsgname, + const void *req, size_t reqlen, const void *elems, uint32_t nelems, + size_t elemsize) { - size_t bodylen = (size_t)nelems * elemsize; - char *combined; + struct iovec iov[2]; - if (bodylen == 0) { - if (imsg_compose(&s->store_iev->ibuf, imsg_type, 0, 0, -1, - req, reqlen) == -1) { - log_warn("session %u: imsg_compose %s", s->id, - imsgname); - return (0); - } - return (1); + iov[0].iov_base = (void *)req; + iov[0].iov_len = reqlen; + iov[1].iov_base = (void *)elems; + iov[1].iov_len = (size_t)nelems * elemsize; + if (imsg_composev(&s->store_iev->ibuf, imsg_type, 0, 0, -1, iov, + iov[1].iov_len > 0 ? 2 : 1) == -1) { + log_warn("session %u: imsg_composev %s", s->id, imsgname); + return (-1); } - - if ((combined = malloc(reqlen + bodylen)) == NULL) { - log_warn("session %u: malloc %s imsg buffer", s->id, what); - return (0); - } - memcpy(combined, req, reqlen); - memcpy(combined + reqlen, elems, bodylen); - - if (imsg_compose(&s->store_iev->ibuf, imsg_type, 0, 0, -1, combined, - reqlen + bodylen) == -1) { - log_warn("session %u: imsg_compose %s", s->id, imsgname); - free(combined); - return (0); - } - free(combined); - return (1); + return (0); } void @@ -1589,7 +1563,7 @@ session_handle_line(struct session *s, char *line) return (1); } - /* RFC 4616 SS2: the initial response holds the password */ + /* RFC 4616 section 2: the initial response holds the password */ if (name != NULL && (strcasecmp(name, "AUTHENTICATE") == 0 || strcasecmp(name, "LOGIN") == 0)) log_debug("session %u: <<< %s %s ", s->id, tag, @@ -1613,16 +1587,16 @@ session_handle_line(struct session *s, char *line) return (1); } - for (i = 0; i < NUM_IMAP_CMDS; i++) { + for (i = 0; i < nitems(imap_cmds); i++) { if (strcasecmp(name, imap_cmds[i].name) == 0) break; } - if (i == NUM_IMAP_CMDS) { + if (i == nitems(imap_cmds)) { session_reply(s, tag, "BAD", "Unknown command"); return (1); } if (!(imap_cmds[i].states & (1U << s->state))) { - /* RFC 9051 SS3 allows BAD or NO */ + /* RFC 9051 section 3 allows BAD or NO */ session_reply(s, tag, "BAD", "Command not permitted in this state"); return (1); @@ -1630,7 +1604,6 @@ session_handle_line(struct session *s, char *line) return (imap_cmds[i].handler(s, tag, args)); } - void listener_dispatch_auth(int fd, short event, void *arg) { @@ -1669,15 +1642,15 @@ listener_dispatch_auth(int fd, short event, void *arg) switch (imsg_get_type(&imsg)) { case IMSG_AUTH_RESULT: { struct imsg_auth_result res; - struct session *s; + struct session *s = &sess; if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) { log_warnx("bad IMSG_AUTH_RESULT"); break; } - if ((s = session_find(res.session_id)) == NULL) { - log_debug("IMSG_AUTH_RESULT for unknown " - "session %u", res.session_id); + if (res.session_id != s->id) { + log_debug("IMSG_AUTH_RESULT for session %u, " + "not ours", res.session_id); break; } if (!res.ok) { @@ -1708,7 +1681,6 @@ listener_dispatch_auth(int fd, short event, void *arg) imsg_free(&imsg); } imsgev_rearm_read(iev); - (void)fd; } void @@ -1727,14 +1699,10 @@ listener_dispatch_parent(int fd, short event, void *ar if ((n = imsgbuf_read(&iev->ibuf)) == -1) fatal("imsgbuf_read"); if (n == 0) { - struct session *s = TAILQ_FIRST(&sessions); - /* the parent is stopping us: end the session */ log_debug("listener-worker: parent closed " "channel, shutting down"); - if (s != NULL) - session_teardown(s, "shutdown"); - exit(0); + session_teardown(&sess, "shutdown"); } } @@ -1747,40 +1715,47 @@ listener_dispatch_parent(int fd, short event, void *ar switch (imsg_get_type(&imsg)) { case IMSG_STORE_FORK: { struct imsg_store_fork fail; - struct session *s; + struct session *s = &sess; if (imsg_get_data(&imsg, &fail, sizeof(fail)) == -1) { log_warnx("bad IMSG_STORE_FORK reply"); break; } - if ((s = session_find(fail.session_id)) == NULL) + if (fail.session_id != s->id) break; + /* RFC 9051 section 3.4: a BYE before the close */ if (fail.limit) { + session_untagged(s, "BYE [LIMIT] too many " + "sessions for this account"); session_reply(s, s->pending_tag, "NO", "[LIMIT] too many sessions for this " "account"); session_teardown(s, "limit-exceeded"); } else { + session_untagged(s, "BYE [UNAVAILABLE] " + "mailbox store unavailable"); session_reply(s, s->pending_tag, "NO", - "authentication succeeded but mailbox " - "store unavailable"); + "[UNAVAILABLE] authentication " + "succeeded but mailbox store unavailable"); session_teardown(s, "io-error"); } break; } case IMSG_SETUP_PEER: { - uint32_t sess_id = imsg_get_id(&imsg); - int store_fd = imsg_get_fd(&imsg); - struct session *s; + struct session *s = &sess; + uint32_t sess_id; + int store_fd; + sess_id = imsg_get_id(&imsg); + store_fd = imsg_get_fd(&imsg); if (store_fd == -1) { log_warnx("IMSG_SETUP_PEER (store) carried " "no fd"); break; } - if ((s = session_find(sess_id)) == NULL) { + if (sess_id != s->id) { log_warnx("IMSG_SETUP_PEER (store) for " - "unknown session %u", sess_id); + "session %u, not ours", sess_id); close(store_fd); break; } @@ -1795,7 +1770,7 @@ listener_dispatch_parent(int fd, short event, void *ar s->state = SESSION_AUTHENTICATED; session_login_grace_disarm(s); log_debug("session %u: store peer wired", sess_id); - /* RFC 9051 SS6.2.2's PLAIN example text, verbatim. */ + /* RFC 9051 section 6.2.2's PLAIN example, verbatim */ session_reply(s, s->pending_tag, "OK", "Success (tls protection)"); break; @@ -1808,25 +1783,17 @@ listener_dispatch_parent(int fd, short event, void *ar imsg_free(&imsg); } imsgev_rearm_read(iev); - (void)fd; } -struct session * -session_find(uint32_t id) -{ - struct session *s; - TAILQ_FOREACH(s, &sessions, entry) { - if (s->id == id) - return (s); - } - return (NULL); -} - /* reason: one of eight fixed tokens, never NULL, never attacker text */ void session_teardown(struct session *s, const char *reason) { + struct timespec now; + const char *who = "-"; + long long dur = -1; + if (s->store_iev != NULL) { if (imsg_compose(&s->store_iev->ibuf, IMSG_STORE_SHUTDOWN, 0, 0, -1, NULL, 0) == -1) @@ -1847,8 +1814,9 @@ session_teardown(struct session *s, const char *reason event_del(&s->client_ev); if (s->tls_ctx != NULL) { - int ret = tls_close(s->tls_ctx); + int ret; + ret = tls_close(s->tls_ctx); if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) close(s->client_fd); /* debug: a missing close_notify is routine; httpd ignores it */ @@ -1873,29 +1841,19 @@ session_teardown(struct session *s, const char *reason while (s->cmd_queue_n > 0) free(s->cmd_queue[--s->cmd_queue_n]); - TAILQ_REMOVE(&sessions, s, entry); - { - struct timespec now; - const char *who = "-"; - long long dur = -1; + if (clock_gettime(CLOCK_MONOTONIC, &now) != -1 && + s->connected_at.tv_sec != 0) + dur = (long long)(now.tv_sec - s->connected_at.tv_sec); + /* user= stays "-" until past authenticating; see listener.h */ + if (s->user[0] != '\0' && s->state != SESSION_AUTHENTICATING) + who = s->user; + /* tls= is the final state, unlike the connect line's */ + log_info("session %u: closed peer=%.200s user=%.64s reason=%s tls=%s " + "duration=%lld", s->id, s->remote_addr, who, reason, + s->tls_active ? "yes" : "no", dur); - if (clock_gettime(CLOCK_MONOTONIC, &now) != -1 && - s->connected_at.tv_sec != 0) - dur = (long long)(now.tv_sec - - s->connected_at.tv_sec); - /* user= stays "-" until past authenticating; see listener.h */ - if (s->user[0] != '\0' && s->state != SESSION_AUTHENTICATING) - who = s->user; - /* tls= is the final state, unlike the connect line's */ - log_info("session %u: closed peer=%.200s user=%.64s " - "reason=%s tls=%s duration=%lld", s->id, - s->remote_addr, who, reason, - s->tls_active ? "yes" : "no", dur); - } - /* inbuf may hold a base64 SASL response */ explicit_bzero(s, sizeof(*s)); - free(s); log_debug("listener-worker: session closed, exiting"); exit(0); blob - 14c2f93c66bdbc2b25143b61ec596f8d7db838fd blob + 6b3f0d914c33d18b53a07299df430b5a8a5a5cac --- src/listener.h +++ src/listener.h @@ -20,13 +20,17 @@ #define LISTENER_H #include -#include #include #include #include #include +/* + * Returns: command handlers 1 while the session lives, 0 once torn down; + * parsers 0, -1 (BAD) or -2 (NO) with *errmsg set; the rest 0/-1 or 1/0. + */ + enum session_state { SESSION_NOT_AUTH, SESSION_AUTHENTICATING, @@ -42,7 +46,7 @@ enum session_state { SESSION_STATUSING, SESSION_COPYING, - /* RFC 9051 SS6.3.4-SS6.3.9; mbox_op_prev_state is restored after */ + /* RFC 9051 sections 6.3.4-6.3.9; mbox_op_prev_state restored after */ SESSION_CREATING, SESSION_DELETING, SESSION_RENAMING, @@ -54,10 +58,10 @@ enum session_state { /* RFC 9051 sets no limit; this bounds a client that never sends CRLF */ #define SESSION_INBUF_MAX 8192 -/* RFC 9051 SS9 sets no limit; a longer tag is truncated */ +/* RFC 9051 section 9 sets no limit; a longer tag is truncated */ #define IMAP_TAG_MAX 64 -/* pipelined lines (RFC 9051 SS5.5); past this the session is dropped */ +/* pipelined lines (RFC 9051 section 5.5); past this the session is dropped */ #define SESSION_CMD_QUEUE_MAX 8 #define HEADER_FIELDS_LABEL_MAX 288 @@ -65,7 +69,7 @@ enum session_state { /* two DQUOTEs, a NUL, and a backslash per byte */ #define MBOX_QUOTED_MAX ((2 * MBOX_NAME_MAX) + 3) -/* RFC 7162 SS7: a mod-sequence is a positive 63-bit integer */ +/* RFC 7162 section 7: a mod-sequence is a positive 63-bit integer */ #define MODSEQ_MAX INT64_MAX struct vanished_range { @@ -96,9 +100,9 @@ struct session { /* inbuf held SASL credentials; explicit_bzero(3) it */ int scrub_inbuf; int auth_cont; - /* RFC 9051 SS6.3.13: the next line is DONE */ + /* RFC 9051 section 6.3.13: the next line is DONE */ int idling; - /* pipelined lines (RFC 9051 SS5.5), malloc(3)'d */ + /* pipelined lines (RFC 9051 section 5.5), malloc(3)'d */ char *cmd_queue[SESSION_CMD_QUEUE_MAX]; uint32_t cmd_queue_n; /* the tag points into inbuf, which the next read overwrites */ @@ -114,7 +118,7 @@ struct session { uint64_t pending_body_len; int pending_body_found; char pending_body_label[SECTION_PART_MAX]; - /* only the origin is echoed back (RFC 9051 SS6.4.5) */ + /* only the origin is echoed back (RFC 9051 section 6.4.5) */ int pending_body_has_partial; uint32_t pending_body_partial_origin; char *pending_envelope_buf; @@ -125,19 +129,19 @@ struct session { int pending_bodystructure_found; char pending_bodystructure_label[16]; - /* tagged NO rather than OK (RFC 9051 SS6.4.5) */ + /* tagged NO rather than OK (RFC 9051 section 6.4.5) */ int fetch_incomplete; int close_after_expunge; - /* RFC 9051 SS4.3 literal octets, checked before the CRLF search */ + /* RFC 9051 section 4.3 literal octets, read before the CRLF search */ int literal_pending; uint64_t literal_len; /* announced size, "{n}" */ uint64_t literal_remaining; - /* RFC 7888 SS3: a refused literal's octets, then its command's line */ + /* RFC 7888 section 3: a refused literal's octets, then its line */ uint64_t literal_discard; int literal_skipline; - /* RFC 9051 SS4.3: a command gathered with its literals */ + /* RFC 9051 section 4.3: a command gathered with its literals */ char cmdbuf[SESSION_INBUF_MAX]; size_t cmdlen; size_t cmd_octets; @@ -169,15 +173,15 @@ struct session { int search_used_modseq; uint64_t search_max_modseq; - /* RFC 7162 SS3.1/SS3.2.3: sticky; QRESYNC implies CONDSTORE */ + /* RFC 7162 sections 3.1, 3.2.3: sticky; QRESYNC implies CONDSTORE */ int condstore_enabled; int qresync_enabled; uint64_t mbox_highestmodseq; - /* RFC 9051 SS6.3.3 EXAMINE */ + /* RFC 9051 section 6.3.3 EXAMINE */ int mbox_readonly; char selected_mailbox[MBOX_NAME_MAX]; - /* RFC 9051 SS6.3.13 IDLE; the store child keeps the UID list */ + /* RFC 9051 section 6.3.13 IDLE; the store child keeps the UID list */ int idle_refresh_pending; int idle_refresh_again; /* the tagged reply IDLE or NOOP owes once the refresh is in */ @@ -186,7 +190,7 @@ struct session { struct event idle_ev; struct event grace_ev; - /* held until IMSG_MBOX_SELECTED (RFC 7162 SS3.2.6 ordering) */ + /* held until IMSG_MBOX_SELECTED (RFC 7162 section 3.2.6 ordering) */ struct vanished_range *vanished_ranges; uint32_t vanished_nranges; uint32_t vanished_cap; @@ -196,7 +200,7 @@ struct session { /* a dropped entry fails the SELECT: HIGHESTMODSEQ would hide it */ int qresync_alloc_failed; - /* COPYUID precedes EXPUNGED (RFC 9051 SS6.4.8) */ + /* COPYUID precedes EXPUNGED (RFC 9051 section 6.4.8) */ uint32_t *copy_src_uids; uint32_t *copy_dest_uids; uint32_t copy_n; @@ -207,28 +211,22 @@ struct session { uint32_t move_expunged_n; uint32_t move_expunged_cap; - /* RFC 7162 SS3.1.3 MODIFIED */ + /* RFC 7162 section 3.1.3 MODIFIED */ uint32_t *store_modified; uint32_t store_modified_n; uint32_t store_modified_cap; /* MODIFIED must list every failure, so an incomplete set is refused */ int store_modified_alloc_failed; - /* RFC 9051 SS6.4.9 UID */ + /* RFC 9051 section 6.4.9 UID */ int cmd_by_uid; - - TAILQ_ENTRY(session) entry; }; -/* named: declared and defined in different files */ -TAILQ_HEAD(session_list, session); - -extern struct session_list sessions; extern struct imsgev iev_auth; extern struct imsgev iev_parent; extern struct tls *listener_tls_ctx; -/* RFC 9051 SS7.1 INUSE, OVERQUOTA */ +/* RFC 9051 section 7.1 INUSE, OVERQUOTA */ #define IMAP_BUSY_TEXT "[INUSE] mailbox busy, try again" #define IMAP_OVERQUOTA_TEXT "[OVERQUOTA] not enough free space" @@ -265,9 +263,8 @@ int parse_nz_number(const char *, uint32_t *); int parse_sequence_set(const char *, struct seq_range[SEQSET_MAX_RANGES], uint32_t *, const char **); -int parse_fetch_atts(char *, uint32_t *, int *, int *, char *, - size_t, char *, size_t, int *, char *, size_t, int *, - uint32_t *, uint32_t *, const char **); +struct fetch_atts; +int parse_fetch_atts(char *, struct fetch_atts *, const char **); int parse_header_fields_att(const char *, int *, char *, size_t); int section_part_valid(const char *); int parse_partial_suffix(const char *, int *, uint32_t *, @@ -293,7 +290,6 @@ void session_finish_search(struct session *, struct imsg_mbox_result *); void session_send_greeting(struct session *); void session_teardown(struct session *, const char *); -struct session *session_find(uint32_t); /* RFC 7162 (CONDSTORE/QRESYNC) */ void session_condstore_enable(struct session *); @@ -331,7 +327,7 @@ int parse_fetch_modifiers(char *, struct imsg_mbox_fe int parse_store_modifiers(char *, struct imsg_mbox_store *, const char **); -/* RFC 9051 SS6.4.9 (UID) */ +/* RFC 9051 section 6.4.9 (UID) */ int fetch_dispatch(struct session *, const char *, char *, int); int store_do(struct session *, const char *, char *, int); int search_dispatch(struct session *, const char *, char *, int); @@ -339,7 +335,7 @@ int uid_expunge_dispatch(struct session *, const char void session_handle_fetch_vanished(struct session *, const struct imsg_mbox_select_vanished *); -/* RFC 9051 SS6.4.7/SS6.4.8 (COPY/MOVE) */ +/* RFC 9051 section 6.4.7/section 6.4.8 (COPY/MOVE) */ int copy_move_dispatch(struct session *, const char *, char *, int, int); int listener_mailbox_name_valid(const char *); @@ -369,8 +365,8 @@ void session_write(struct session *, const char *, si void session_reply(struct session *, const char *, const char *, const char *); void session_untagged(struct session *, const char *); -int send_mbox_request(struct session *, int, const char *, const char *, - const void *, size_t, const void *, uint32_t, size_t); +int send_mbox_request(struct session *, int, const char *, const void *, + size_t, const void *, uint32_t, size_t); int parse_command_line(char *, char **, char **, char **); int session_handle_line(struct session *, char *); int session_dequeue_next(struct session *); @@ -386,7 +382,7 @@ int cmd_login(struct session *, const char *, char *) int cmd_starttls(struct session *, const char *, char *); int cmd_authenticate(struct session *, const char *, char *); -/* command-auth (RFC 9051 SS6.3) */ +/* command-auth (RFC 9051 section 6.3) */ int stub_not_implemented(struct session *, const char *, const char *); int cmd_enable(struct session *, const char *, char *); @@ -404,7 +400,7 @@ int cmd_status(struct session *, const char *, char * int cmd_append(struct session *, const char *, char *); int cmd_idle(struct session *, const char *, char *); -/* command-select (RFC 9051 SS6.4, valid only in Selected state). */ +/* command-select (RFC 9051 section 6.4, valid only in Selected state). */ int cmd_close(struct session *, const char *, char *); int cmd_unselect(struct session *, const char *, char *); int cmd_expunge(struct session *, const char *, char *); blob - b2dad4bfd9b2765481ed2214adabfe90e4b5da87 blob + efaf76ace0f31de26d972dafffccbaabf3f683a4 --- src/log.c +++ src/log.c @@ -4,17 +4,6 @@ * Copyright (c) 2026 David Williams * Copyright (c) 2003, 2004 Henning Brauer * - * This file's log_init()/log_procinit()/log_setverbose()/log_getverbose()/ - * vlog()/logit()/log_warn()/log_warnx()/log_info()/log_debug()/fatal()/ - * fatalx() API is the same daemon-logging idiom Henning Brauer wrote and - * that is carried, nearly verbatim, across essentially every privsep - * daemon in the OpenBSD base system (smtpd, bgpd, and many others all - * credit him in their own log.c). This file's implementation details - * (log_procname storage, log_init()'s parameters) are this project's own, - * but the API shape and name are that same shared idiom, so his copyright - * is carried forward here too, same rationale as parse.y's copyright - * chain. - * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. blob - f315b680566be25f414789461f75694af4c4c95e blob + 5a9fbc87de9ea457a108331001578181b316af06 --- src/log.h +++ src/log.h @@ -4,13 +4,6 @@ * Copyright (c) 2026 David Williams * Copyright (c) 2003, 2004 Henning Brauer * - * This header's call signatures were deliberately matched to smtpd's own - * log.h (see the comment below), that log_init()/fatal()/fatalx()/ - * log_warn()/log_debug() API is the same shared daemon-logging idiom - * Henning Brauer wrote and that smtpd, bgpd, and most other privsep - * daemons in OpenBSD base carry his copyright for. Same rationale as - * log.c's copyright chain. - * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. blob - 7e8374d788e2868027027f32d27f31f14949c84f blob + 8046d4740bbff21de11286d3b7566844c593d26d --- src/mailbox_cmd.c +++ src/mailbox_cmd.c @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - #include #include #include @@ -47,11 +46,11 @@ parse_qresync_group(char *inner, struct imsg_mbox_sele struct seq_range ranges[SEQSET_MAX_RANGES], uint32_t *nranges, const char **errmsg) { - char *p; - const char *tok; - uint32_t uidvalidity; + char *p, *end; + const char *tok, *errstr; + uint32_t uidvalidity, i; uint64_t modseq; - char *ep; + int depth; p = inner; while (*p == ' ') @@ -81,15 +80,12 @@ parse_qresync_group(char *inner, struct imsg_mbox_sele *errmsg = "QRESYNC requires uidvalidity and mod-sequence"; return (-1); } - /* RFC 7162: 1..2^63-1; strtoull(3) accepts a sign */ if (*tok < '0' || *tok > '9') { *errmsg = "invalid QRESYNC mod-sequence"; return (-1); } - errno = 0; - modseq = strtoull(tok, &ep, 10); - if (*ep != '\0' || errno != 0 || modseq == 0 || - modseq > MODSEQ_MAX) { + modseq = strtonum(tok, 1, MODSEQ_MAX, &errstr); + if (errstr != NULL) { *errmsg = "invalid QRESYNC mod-sequence"; return (-1); } @@ -115,22 +111,18 @@ parse_qresync_group(char *inner, struct imsg_mbox_sele *p = '\0'; p++; } - { - uint32_t i; - - /* RFC 7162 SS3.2.5.1: "*" is not allowed */ - if (parse_sequence_set(tok, ranges, nranges, errmsg) == -1) + /* RFC 7162 section 3.2.5.1: "*" is not allowed */ + if (parse_sequence_set(tok, ranges, nranges, errmsg) == -1) + return (-1); + for (i = 0; i < *nranges; i++) { + if (ranges[i].lo_is_star || ranges[i].hi_is_star) { + *errmsg = "invalid known-uids, '*' is not " + "allowed here"; return (-1); - for (i = 0; i < *nranges; i++) { - if (ranges[i].lo_is_star || ranges[i].hi_is_star) { - *errmsg = "invalid known-uids, '*' is not " - "allowed here (RFC 7162 SS3.2.5.1)"; - return (-1); - } } - req->qresync_has_uids = 1; - req->qresync_nranges = *nranges; } + req->qresync_has_uids = 1; + req->qresync_nranges = *nranges; while (*p == ' ') p++; @@ -141,25 +133,19 @@ parse_qresync_group(char *inner, struct imsg_mbox_sele *errmsg = "expected seq-match-data"; return (-1); } - { - char *end = p; - int depth = 0; - - for (;;) { - if (*end == '(') - depth++; - else if (*end == ')') { - depth--; - if (depth == 0) - break; - } else if (*end == '\0') { - *errmsg = "unterminated seq-match-data"; - return (-1); - } - end++; + depth = 0; + for (end = p;; end++) { + if (*end == '(') + depth++; + else if (*end == ')') { + if (--depth == 0) + break; + } else if (*end == '\0') { + *errmsg = "unterminated seq-match-data"; + return (-1); } - p = end + 1; } + p = end + 1; while (*p == ' ') p++; @@ -206,8 +192,8 @@ parse_select_params(char *p, struct imsg_mbox_select * return (-1); } if (!s->qresync_enabled) { - *errmsg = "QRESYNC select-param requires " - "ENABLE QRESYNC first (RFC 7162 SS3.2.5)"; + *errmsg = "QRESYNC requires ENABLE QRESYNC " + "first"; return (-1); } @@ -234,7 +220,7 @@ parse_select_params(char *p, struct imsg_mbox_select * return (-1); req->qresync = 1; - /* RFC 7162 SS3.2.3: QRESYNC implies CONDSTORE */ + /* RFC 7162 section 3.2.3: QRESYNC implies CONDSTORE */ *want_condstore = 1; p = end + 1; continue; @@ -247,7 +233,7 @@ parse_select_params(char *p, struct imsg_mbox_select * return (0); } -/* RFC 9051 SS6.3.2/SS6.3.3 */ +/* RFC 9051 section 6.3.2/section 6.3.3 */ static int select_or_examine(struct session *s, const char *tag, char *args, int readonly) { @@ -277,7 +263,7 @@ select_or_examine(struct session *s, const char *tag, p++; params = (*p != '\0') ? p : NULL; - /* "" parses as RFC 9051 SS9 quoted, so this is NO, not BAD */ + /* "" parses as RFC 9051 section 9 quoted, so this is NO, not BAD */ if (mailbox[0] == '\0') { session_reply(s, tag, "NO", "[NONEXISTENT] no such mailbox"); return (1); @@ -299,8 +285,9 @@ select_or_examine(struct session *s, const char *tag, req.readonly = readonly; if (params != NULL) { - size_t plen = strlen(params); + size_t plen; + plen = strlen(params); if (plen < 2 || params[0] != '(' || params[plen - 1] != ')') { session_reply(s, tag, "BAD", "malformed select-param list"); @@ -315,11 +302,11 @@ select_or_examine(struct session *s, const char *tag, } } - /* RFC 7162 SS3.1.8/SS3.2.3 */ + /* RFC 7162 section 3.1.8/section 3.2.3 */ if (want_condstore) s->condstore_enabled = 1; - /* RFC 9051 SS6.3.2: SELECT auto-deselects the current mailbox. */ + /* RFC 9051 section 6.3.2: SELECT auto-deselects the current mailbox. */ if (s->state == SESSION_SELECTED) session_untagged(s, "OK [CLOSED] Previous mailbox is now closed"); @@ -334,8 +321,8 @@ select_or_examine(struct session *s, const char *tag, s->state = SESSION_SELECTING; s->mbox_readonly = readonly; - if (!send_mbox_request(s, IMSG_MBOX_SELECT, cmdname, "IMSG_MBOX_SELECT", - &req, sizeof(req), ranges, nranges, sizeof(struct seq_range))) { + if (send_mbox_request(s, IMSG_MBOX_SELECT, "IMSG_MBOX_SELECT", &req, + sizeof(req), ranges, nranges, sizeof(struct seq_range)) == -1) { session_reply(s, tag, "NO", "[SERVERBUG] internal error"); s->state = SESSION_AUTHENTICATED; return (1); @@ -344,18 +331,16 @@ select_or_examine(struct session *s, const char *tag, return (1); } - int cmd_select(struct session *s, const char *tag, char *args) { - return select_or_examine(s, tag, args, 0); + return (select_or_examine(s, tag, args, 0)); } - int cmd_examine(struct session *s, const char *tag, char *args) { - return select_or_examine(s, tag, args, 1); + return (select_or_examine(s, tag, args, 1)); } /* checked here to save a round trip; the store checks again */ @@ -371,14 +356,13 @@ listener_reject_bad_utf8(struct session *s, const char if (utf8_mailbox_ok(name)) return (0); - log_debug("session %u: mailbox name is not valid UTF-8, refusing " - "(RFC 9051 SS5.1)", s->id); + log_debug("session %u: mailbox name not valid UTF-8", s->id); session_reply(s, tag, "NO", "[CANNOT] mailbox name is not valid UTF-8"); return (1); } -/* RFC 9051 SS6.3.4 CREATE */ +/* RFC 9051 section 6.3.4 CREATE */ int cmd_create(struct session *s, const char *tag, char *args) { @@ -402,7 +386,7 @@ cmd_create(struct session *s, const char *tag, char *a } if (listener_reject_bad_utf8(s, tag, mailbox)) return (1); - /* a name the server refuses is RFC 5530 SS3 NO [CANNOT], not BAD */ + /* a refused name is RFC 5530 section 3 NO [CANNOT], not BAD */ if (!listener_mailbox_name_valid(mailbox)) { session_reply(s, tag, "NO", "[CANNOT] invalid mailbox name"); return (1); @@ -438,8 +422,8 @@ cmd_create(struct session *s, const char *tag, char *a s->state = SESSION_CREATING; /* a failed compose must not leave the session busy */ - if (!send_mbox_request(s, IMSG_MBOX_CREATE, "CREATE", - "IMSG_MBOX_CREATE", &req, sizeof(req), NULL, 0, 0)) { + if (send_mbox_request(s, IMSG_MBOX_CREATE, + "IMSG_MBOX_CREATE", &req, sizeof(req), NULL, 0, 0) == -1) { s->state = s->mbox_op_prev_state; session_reply(s, tag, "NO", "[SERVERBUG] internal error"); return (1); @@ -448,7 +432,7 @@ cmd_create(struct session *s, const char *tag, char *a return (1); } -/* RFC 9051 SS6.3.5 DELETE */ +/* RFC 9051 section 6.3.5 DELETE */ int cmd_delete(struct session *s, const char *tag, char *args) { @@ -476,7 +460,6 @@ cmd_delete(struct session *s, const char *tag, char *a return (1); } - /* no arguments may follow the name */ while (*p == ' ') p++; if (*p != '\0') { @@ -505,8 +488,8 @@ cmd_delete(struct session *s, const char *tag, char *a s->mbox_op_prev_state = s->state; s->state = SESSION_DELETING; - if (!send_mbox_request(s, IMSG_MBOX_DELETE, "DELETE", - "IMSG_MBOX_DELETE", &req, sizeof(req), NULL, 0, 0)) { + if (send_mbox_request(s, IMSG_MBOX_DELETE, + "IMSG_MBOX_DELETE", &req, sizeof(req), NULL, 0, 0) == -1) { s->state = s->mbox_op_prev_state; session_reply(s, tag, "NO", "[SERVERBUG] internal error"); return (1); @@ -515,7 +498,7 @@ cmd_delete(struct session *s, const char *tag, char *a return (1); } -/* RFC 9051 SS6.3.6 RENAME */ +/* RFC 9051 section 6.3.6 RENAME */ int cmd_rename(struct session *s, const char *tag, char *args) { @@ -538,7 +521,7 @@ cmd_rename(struct session *s, const char *tag, char *a return (session_arg_error(s, tag, errmsg)); if (mailbox_name_is_inbox(oldname)) { - /* RFC 9051 SS6.3.6 allows refusing this */ + /* RFC 9051 section 6.3.6 allows refusing this */ session_reply(s, tag, "NO", "[CANNOT] cannot rename INBOX"); return (1); } @@ -548,14 +531,12 @@ cmd_rename(struct session *s, const char *tag, char *a } if (listener_reject_bad_utf8(s, tag, newname)) return (1); - /* a name the server refuses is RFC 5530 SS3 NO [CANNOT], not BAD */ if (mailbox_name_is_inbox(newname) || !listener_mailbox_name_valid(newname)) { session_reply(s, tag, "NO", "[CANNOT] invalid mailbox name"); return (1); } - /* no arguments may follow the name */ while (*p == ' ') p++; if (*p != '\0') { @@ -588,8 +569,8 @@ cmd_rename(struct session *s, const char *tag, char *a s->mbox_op_prev_state = s->state; s->state = SESSION_RENAMING; - if (!send_mbox_request(s, IMSG_MBOX_RENAME, "RENAME", - "IMSG_MBOX_RENAME", &req, sizeof(req), NULL, 0, 0)) { + if (send_mbox_request(s, IMSG_MBOX_RENAME, + "IMSG_MBOX_RENAME", &req, sizeof(req), NULL, 0, 0) == -1) { s->state = s->mbox_op_prev_state; session_reply(s, tag, "NO", "[SERVERBUG] internal error"); return (1); @@ -598,8 +579,7 @@ cmd_rename(struct session *s, const char *tag, char *a return (1); } - -/* RFC 9051 SS6.3.7 SUBSCRIBE and SS6.3.8 UNSUBSCRIBE */ +/* RFC 9051 section 6.3.7 SUBSCRIBE and section 6.3.8 UNSUBSCRIBE */ static int subscribe_dispatch(struct session *s, const char *tag, char *args, int is_unsub) @@ -628,7 +608,7 @@ subscribe_dispatch(struct session *s, const char *tag, if (listener_reject_bad_utf8(s, tag, mailbox)) return (1); - /* RFC 9051 SS5.1: INBOX is always subscribed */ + /* RFC 9051 section 5.1: INBOX is always subscribed */ if (mailbox_name_is_inbox(mailbox)) { if (is_unsub) { session_reply(s, tag, "NO", @@ -638,7 +618,6 @@ subscribe_dispatch(struct session *s, const char *tag, session_reply(s, tag, "OK", "SUBSCRIBE completed"); return (1); } - /* a name the server refuses is RFC 5530 SS3 NO [CANNOT], not BAD */ if (!listener_mailbox_name_valid(mailbox)) { session_reply(s, tag, "NO", "[CANNOT] invalid mailbox name"); return (1); @@ -673,9 +652,9 @@ subscribe_dispatch(struct session *s, const char *tag, s->mbox_op_prev_state = s->state; s->state = is_unsub ? SESSION_UNSUBSCRIBING : SESSION_SUBSCRIBING; - if (!send_mbox_request(s, is_unsub ? IMSG_MBOX_UNSUBSCRIBE : - IMSG_MBOX_SUBSCRIBE, cmdname, imsgname, &req, sizeof(req), NULL, - 0, 0)) { + if (send_mbox_request(s, is_unsub ? IMSG_MBOX_UNSUBSCRIBE : + IMSG_MBOX_SUBSCRIBE, imsgname, &req, sizeof(req), NULL, + 0, 0) == -1) { s->state = s->mbox_op_prev_state; session_reply(s, tag, "NO", "[SERVERBUG] internal error"); return (1); @@ -684,21 +663,19 @@ subscribe_dispatch(struct session *s, const char *tag, return (1); } - int cmd_subscribe(struct session *s, const char *tag, char *args) { - return subscribe_dispatch(s, tag, args, 0); + return (subscribe_dispatch(s, tag, args, 0)); } - int cmd_unsubscribe(struct session *s, const char *tag, char *args) { - return subscribe_dispatch(s, tag, args, 1); + return (subscribe_dispatch(s, tag, args, 1)); } -/* RFC 9051 SS6.3.9 wildcards; a flat namespace */ +/* RFC 9051 section 6.3.9 wildcards; a flat namespace */ int list_pattern_match(const char *pat, const char *name, int ci) { @@ -733,7 +710,7 @@ list_pattern_match(const char *pat, const char *name, return (*p == '\0'); } -/* RFC 9051 SS4.3 quoted string */ +/* RFC 9051 section 4.3 quoted string */ int quote_mailbox(char *out, size_t outsize, const char *name) { @@ -750,7 +727,7 @@ quote_mailbox(char *out, size_t outsize, const char *n char c = name[i]; size_t need; - /* RFC 9051 SS4.3: no NUL, CR or LF */ + /* RFC 9051 section 4.3: no NUL, CR or LF */ if (c == '\r' || c == '\n') c = ' '; @@ -782,7 +759,7 @@ atom_char_ok(unsigned char c, int wildcards) return (1); } -/* RFC 9051 SS9 astring, or list-mailbox with wildcards */ +/* RFC 9051 section 9 astring, or list-mailbox with wildcards */ static int parse_mailbox_arg(char **pp, char *out, size_t outsize, int wildcards, const char **errmsg) @@ -827,7 +804,7 @@ parse_mailbox_arg(char **pp, char *out, size_t outsize break; } if (*p == '\\') { - /* RFC 9051 SS9 QUOTED-CHAR */ + /* RFC 9051 section 9 QUOTED-CHAR */ p++; if (*p == '\0') { /* a trailing backslash */ @@ -881,20 +858,20 @@ parse_list_pattern(char **pp, char *out, size_t outsiz return (parse_mailbox_arg(pp, out, outsize, 1, errmsg)); } -/* RFC 9051 SS6.3.9 basic syntax; LSUB varies only the output */ +/* RFC 9051 section 6.3.9 basic syntax; LSUB varies only the output */ static int list_dispatch(struct session *s, const char *tag, char *args, int is_lsub) { struct imsg_mbox_list req; - char reference[MBOX_NAME_MAX]; - char pattern[MBOX_NAME_MAX]; - char canon[2 * MBOX_NAME_MAX]; - char *p; - const char *errmsg; - const char *cmdname = is_lsub ? "LSUB" : "LIST"; - const char *kw = is_lsub ? "LSUB" : "LIST"; - char text[64]; - int subscribed_only = 0; + char reference[MBOX_NAME_MAX]; + char pattern[MBOX_NAME_MAX]; + char canon[2 * MBOX_NAME_MAX]; + char text[64]; + char *p; + const char *errmsg; + const char *cmdname = is_lsub ? "LSUB" : "LIST"; + size_t n; + int subscribed_only = 0; if (args == NULL) { snprintf(text, sizeof(text), "%s requires two arguments", @@ -908,7 +885,7 @@ list_dispatch(struct session *s, const char *tag, char p++; if (*p == '(') { - /* RFC 9051 SS6.3.9 condition 1: list-select-opts */ + /* RFC 9051 section 6.3.9 condition 1: list-select-opts */ if (is_lsub) { session_reply(s, tag, "BAD", "LSUB takes no selection options"); @@ -944,7 +921,7 @@ list_dispatch(struct session *s, const char *tag, char p++; if (*p == '(') { - /* RFC 9051 SS6.3.9 condition 2 */ + /* RFC 9051 section 6.3.9 condition 2 */ snprintf(text, sizeof(text), "extended %s mailbox-pattern lists not supported", cmdname); @@ -958,39 +935,36 @@ list_dispatch(struct session *s, const char *tag, char while (*p == ' ') p++; if (*p != '\0') { - /* RFC 9051 SS6.3.9 condition 3 */ + /* RFC 9051 section 6.3.9 condition 3 */ snprintf(text, sizeof(text), "extended %s return options not supported", cmdname); session_reply(s, tag, "NO", text); return (1); } - /* RFC 9051 SS6.3.9: an empty pattern asks for the delimiter */ + /* RFC 9051 section 6.3.9: an empty pattern asks for the delimiter */ if (pattern[0] == '\0') { - snprintf(text, sizeof(text), "%s (\\Noselect) \"/\" \"\"", kw); + snprintf(text, sizeof(text), "%s (\\Noselect) \"/\" \"\"", + cmdname); session_untagged(s, text); snprintf(text, sizeof(text), "%s completed", cmdname); session_reply(s, tag, "OK", text); return (1); } - { - size_t n; - - n = strlcpy(canon, reference, sizeof(canon)); - if (n < sizeof(canon)) - n = strlcat(canon, pattern, sizeof(canon)); - if (n >= sizeof(canon)) { - session_reply(s, tag, "BAD", - "combined reference and pattern too long"); - return (1); - } + n = strlcpy(canon, reference, sizeof(canon)); + if (n < sizeof(canon)) + n = strlcat(canon, pattern, sizeof(canon)); + if (n >= sizeof(canon)) { + session_reply(s, tag, "BAD", + "combined reference and pattern too long"); + return (1); } - /* RFC 9051 SS6.3.9: INBOX is answered here */ + /* RFC 9051 section 6.3.9: INBOX is answered here */ if (list_pattern_match(canon, "INBOX", 1)) { - /* RFC 9051 SS7.3.1: attributes are optional */ - snprintf(text, sizeof(text), "%s (%s) \"/\" \"INBOX\"", kw, + /* RFC 9051 section 7.3.1: attributes are optional */ + snprintf(text, sizeof(text), "%s (%s) \"/\" \"INBOX\"", cmdname, subscribed_only ? "\\Subscribed" : ""); session_untagged(s, text); } @@ -1010,7 +984,7 @@ list_dispatch(struct session *s, const char *tag, char return (1); } s->list_is_lsub = is_lsub; - /* LSUB lists subscribed names (RFC 3501 SS6.3.9) */ + /* LSUB lists subscribed names (RFC 3501 section 6.3.9) */ s->list_subscribed_only = subscribed_only || is_lsub; s->mbox_op_prev_state = s->state; s->state = SESSION_LISTING; @@ -1018,8 +992,8 @@ list_dispatch(struct session *s, const char *tag, char memset(&req, 0, sizeof(req)); req.subscribed_only = s->list_subscribed_only; - if (!send_mbox_request(s, IMSG_MBOX_LIST, cmdname, "IMSG_MBOX_LIST", - &req, sizeof(req), NULL, 0, 0)) { + if (send_mbox_request(s, IMSG_MBOX_LIST, "IMSG_MBOX_LIST", + &req, sizeof(req), NULL, 0, 0) == -1) { s->state = s->mbox_op_prev_state; session_reply(s, tag, "NO", "[SERVERBUG] internal error"); return (1); @@ -1028,31 +1002,28 @@ list_dispatch(struct session *s, const char *tag, char return (1); } - int cmd_list(struct session *s, const char *tag, char *args) { - return list_dispatch(s, tag, args, 0); + return (list_dispatch(s, tag, args, 0)); } - int cmd_lsub(struct session *s, const char *tag, char *args) { - return list_dispatch(s, tag, args, 1); + return (list_dispatch(s, tag, args, 1)); } -/* RFC 9051 SS6.3.10 NAMESPACE */ +/* RFC 9051 section 6.3.10 NAMESPACE */ int cmd_namespace(struct session *s, const char *tag, char *args) { - (void)args; session_untagged(s, "NAMESPACE ((\"\" \"/\")) NIL NIL"); session_reply(s, tag, "OK", "NAMESPACE command completed"); return (1); } -/* RFC 9051 SS6.3.11 STATUS */ +/* RFC 9051 section 6.3.11 STATUS */ int cmd_status(struct session *s, const char *tag, char *args) { @@ -1107,7 +1078,7 @@ cmd_status(struct session *s, const char *tag, char *a else if (strcasecmp(tok, "HIGHESTMODSEQ") == 0) attrs |= STATUS_ATT_HIGHESTMODSEQ; else if (strcasecmp(tok, "RECENT") == 0) - /* RFC 9051 SS2.3.2 dropped it; clients ask */ + /* RFC 9051 section 2.3.2 dropped it; clients ask */ attrs |= STATUS_ATT_RECENT; else { session_reply(s, tag, "BAD", "unknown status-att"); @@ -1115,7 +1086,7 @@ cmd_status(struct session *s, const char *tag, char *a } } - /* RFC 9051 SS9: at least one status-att */ + /* RFC 9051 section 9: at least one status-att */ if (attrs == 0) { session_reply(s, tag, "BAD", "STATUS requires at least one status-att"); @@ -1136,7 +1107,7 @@ cmd_status(struct session *s, const char *tag, char *a return (1); } - /* RFC 7162 SS3.1: STATUS HIGHESTMODSEQ enables CONDSTORE */ + /* RFC 7162 section 3.1: STATUS HIGHESTMODSEQ enables CONDSTORE */ if (attrs & STATUS_ATT_HIGHESTMODSEQ) session_condstore_enable(s); @@ -1155,8 +1126,8 @@ cmd_status(struct session *s, const char *tag, char *a s->status_prev_state = s->state; s->state = SESSION_STATUSING; - if (!send_mbox_request(s, IMSG_MBOX_STATUS, "STATUS", - "IMSG_MBOX_STATUS", &req, sizeof(req), NULL, 0, 0)) { + if (send_mbox_request(s, IMSG_MBOX_STATUS, + "IMSG_MBOX_STATUS", &req, sizeof(req), NULL, 0, 0) == -1) { s->state = s->status_prev_state; session_reply(s, tag, "NO", "[SERVERBUG] internal error"); return (1); blob - 328b47f988740d0a5fe9f0f6576e7df5ddcd1803 blob + 5b09cc8f6e152a8199588a67814172051a4eb00c --- src/main.c +++ src/main.c @@ -16,6 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include #include @@ -29,7 +30,7 @@ #include "imapd.h" #include "log.h" -__dead static void usage(void); +static __dead void usage(void); static const struct { const char *name; @@ -63,11 +64,11 @@ log_procname(enum openimap_proc_type type) } } -__dead static void +static __dead void usage(void) { fprintf(stderr, - "usage: %s [-dVv] [-D macro=value] [-f file]\n", + "usage: %s [-dnVv] [-D macro=value] [-f file]\n", getprogname()); exit(1); } @@ -76,7 +77,7 @@ int main(int argc, char *argv[]) { int ch; - int debug = 0, verbose = 0; + int debug = 0, noaction = 0, verbose = 0; const char *conffile = "/etc/imapd.conf"; const char *rolearg = NULL; enum openimap_proc_type role = PROC_PARENT; @@ -88,7 +89,7 @@ main(int argc, char *argv[]) /* before any fork(2), so every role inherits SIG_IGN */ signal(SIGPIPE, SIG_IGN); - while ((ch = getopt(argc, argv, "D:df:Vvx:")) != -1) { + while ((ch = getopt(argc, argv, "D:df:nVvx:")) != -1) { switch (ch) { case 'V': printf("%s %s\n", getprogname(), IMAPD_VERSION); @@ -104,8 +105,11 @@ main(int argc, char *argv[]) case 'f': conffile = optarg; break; + case 'n': + noaction = 1; + break; case 'v': - verbose = 1; + verbose++; break; case 'x': rolearg = optarg; @@ -116,14 +120,13 @@ main(int argc, char *argv[]) } if (rolearg != NULL) { - for (i = 0; i < sizeof(proc_table) / sizeof(proc_table[0]); - i++) { + for (i = 0; i < nitems(proc_table); i++) { if (strcmp(rolearg, proc_table[i].name) == 0) { role = proc_table[i].type; break; } } - if (i == sizeof(proc_table) / sizeof(proc_table[0])) + if (i == nitems(proc_table)) usage(); } @@ -133,7 +136,12 @@ main(int argc, char *argv[]) switch (role) { case PROC_PARENT: - /* before config_load(): imapd.conf is root-only */ + if (config_load(conffile, &conf) == -1) + fatalx("config_load: %s", conffile); + if (noaction) { + fprintf(stderr, "configuration OK\n"); + return (0); + } if (geteuid() != 0) fatalx("parent must start as root (running as " "uid %u)", (unsigned int)geteuid()); @@ -143,8 +151,6 @@ main(int argc, char *argv[]) fatalx("unknown user %s", IMAPD_AUTH_USER); if (getpwnam(IMAPD_KEY_USER) == NULL) fatalx("unknown user %s", IMAPD_KEY_USER); - if (config_load(conffile, &conf) == -1) - fatalx("config_load: %s", conffile); parent_main(conffile, argc, argv, &conf); case PROC_LISTENER: listener_main(); blob - 00ffff1b63663f3599aa5c68bef8d0360370412d blob + 3097d348370917895540dacc8eb070368430406e --- src/mbox_copy.c +++ src/mbox_copy.c @@ -88,8 +88,7 @@ stage_copy_messages(struct store_session *ss, struct m if (locate_message_file(&ss->cur_snap, ss->mailbox_dir_fd, rec.basename, &size, suffix, sizeof(suffix)) == -1) { log_warnx("session %u: COPY: message %s indexed but " - "missing on disk, failing whole COPY (partial " - "copy not permitted, RFC 9051 SS6.4.7)", + "missing on disk, COPY failed", session_id, rec.basename); goto fail; } @@ -121,8 +120,8 @@ stage_copy_messages(struct store_session *ss, struct m goto fail; } if (!index_basename_valid(cs.basename)) { - log_warnx("session %u: COPY: generated basename is " - "unsafe for the index, failing COPY", session_id); + log_warnx("session %u: COPY: unsafe generated basename", + session_id); goto fail; } @@ -143,9 +142,10 @@ stage_copy_messages(struct store_session *ss, struct m if (nstaged == stagedcap) { size_t newcap = (stagedcap == 0) ? 8 : stagedcap * 2; - struct copy_staged *newstaged = reallocarray(staged, - newcap, sizeof(*staged)); + struct copy_staged *newstaged; + newstaged = reallocarray(staged, newcap, + sizeof(*staged)); if (newstaged == NULL) { log_warn("session %u: COPY: reallocarray " "staged", session_id); @@ -298,8 +298,7 @@ commit_copy_messages(struct store_session *ss, int dfd if (!index_basename_valid(staged[i].basename) || !index_field_valid(staged[i].keywords)) { log_warnx("session %u: COPY: unsafe field in staged " - "message, refusing to update the index", - session_id); + "message", session_id); goto rollback; } @@ -349,7 +348,7 @@ commit_copy_messages(struct store_session *ss, int dfd return (1); rollback: - /* RFC 9051 SS6.4.7: no partial copy */ + /* RFC 9051 section 6.4.7: no partial copy */ while (ncommitted > 0) { char curpath[320]; char letters[8]; @@ -368,181 +367,173 @@ rollback: return (0); } +/* a COPY or MOVE's two mailboxes, the selected one and the destination */ +struct copy_move { + struct mbox_index idx_a, idx_b; + struct mbox_index *srcidx, *destidx; + struct index_lock il_a, il_b; + struct imsg_mbox_result result; + char desttarget[MBOX_NAME_MAX]; + int destfd; + int cross_mailbox; +}; + +/* 1 with both indexes locked and loaded; 2 lock busy; 0 failed, result set */ static int -lock_copy_move_mailboxes(struct store_session *ss, const char *what, - const char *destname, int *destfd_out, struct mbox_index *idx_a, - struct mbox_index *idx_b, struct mbox_index **srcidx_out, - struct mbox_index **destidx_out, char *desttarget, size_t desttargetlen, - int *cross_mailbox_out, struct index_lock *il_a, struct index_lock *il_b, - struct imsg_mbox_result *result) +copy_move_lock(struct store_session *ss, const char *what, + const char *destname, struct copy_move *cm) { - int firstfd, secondfd, first_is_dest, locked; + static const struct index_lock il_init = INDEX_LOCK_INIT; + int firstfd, secondfd, first_is_dest; + int locked; - if (resolve_mailbox_target(destname, desttarget, desttargetlen) == - -1) { + memset(cm, 0, sizeof(*cm)); + cm->il_a = il_init; + cm->il_b = il_init; + cm->destfd = -1; + + if (resolve_mailbox_target(destname, cm->desttarget, + sizeof(cm->desttarget)) == -1) { log_debug("session %u: %s %s: invalid destination mailbox " "name", session_id, what, destname); - result->error = MBOX_OP_ERR_NO_SUCH_MAILBOX; + cm->result.error = MBOX_OP_ERR_NO_SUCH_MAILBOX; return (0); } - *cross_mailbox_out = (strcmp(ss->selected_mailbox, desttarget) != 0); + cm->cross_mailbox = (strcmp(ss->selected_mailbox, cm->desttarget) != 0); - if (!*cross_mailbox_out) { - locked = index_lock_acquire(ss->mailbox_dir_fd, il_a, + if (!cm->cross_mailbox) { + locked = index_lock_acquire(ss->mailbox_dir_fd, &cm->il_a, LOCK_EX | LOCK_NB); if (locked == 1) return (2); if (locked == -1) return (0); - if (index_load(il_a->fd, idx_a) == -1) + if (index_load(cm->il_a.fd, &cm->idx_a) == -1) return (0); - *srcidx_out = idx_a; - *destidx_out = idx_a; + cm->srcidx = &cm->idx_a; + cm->destidx = &cm->idx_a; return (1); } - if ((*destfd_out = mailbox_open_dir(desttarget)) == -1) { + if ((cm->destfd = mailbox_open_dir(cm->desttarget)) == -1) { log_debug("session %u: %s: no such mailbox %s", session_id, - what, desttarget); - result->error = MBOX_OP_ERR_NO_SUCH_MAILBOX; + what, cm->desttarget); + cm->result.error = MBOX_OP_ERR_NO_SUCH_MAILBOX; return (0); } /* both locked at once: take them in name order, against AB-BA */ - first_is_dest = strcmp(ss->selected_mailbox, desttarget) > 0; - firstfd = first_is_dest ? *destfd_out : ss->mailbox_dir_fd; - secondfd = first_is_dest ? ss->mailbox_dir_fd : *destfd_out; + first_is_dest = strcmp(ss->selected_mailbox, cm->desttarget) > 0; + firstfd = first_is_dest ? cm->destfd : ss->mailbox_dir_fd; + secondfd = first_is_dest ? ss->mailbox_dir_fd : cm->destfd; - locked = index_lock_acquire(firstfd, il_a, LOCK_EX | LOCK_NB); + locked = index_lock_acquire(firstfd, &cm->il_a, LOCK_EX | LOCK_NB); if (locked != 0) goto fail; - if (index_load(il_a->fd, idx_a) == -1) + if (index_load(cm->il_a.fd, &cm->idx_a) == -1) goto fail; - locked = index_lock_acquire(secondfd, il_b, LOCK_EX | LOCK_NB); + locked = index_lock_acquire(secondfd, &cm->il_b, LOCK_EX | LOCK_NB); if (locked != 0) goto fail; - if (index_load(il_b->fd, idx_b) == -1) + if (index_load(cm->il_b.fd, &cm->idx_b) == -1) goto fail; - *srcidx_out = first_is_dest ? idx_b : idx_a; - *destidx_out = first_is_dest ? idx_a : idx_b; + cm->srcidx = first_is_dest ? &cm->idx_b : &cm->idx_a; + cm->destidx = first_is_dest ? &cm->idx_a : &cm->idx_b; return (1); fail: - close(*destfd_out); - *destfd_out = -1; + close(cm->destfd); + cm->destfd = -1; if (locked == 1) { /* busy: the first lock goes too, so a re-run starts clean */ - index_lock_release(il_a); - index_free(idx_a); - memset(idx_a, 0, sizeof(*idx_a)); + index_lock_release(&cm->il_a); + index_free(&cm->idx_a); + memset(&cm->idx_a, 0, sizeof(cm->idx_a)); return (2); } return (0); } static void -finish_copy_move(struct mbox_index *idx_a, struct mbox_index *idx_b, - struct index_lock *il_a, struct index_lock *il_b, int ok, - struct imsg_mbox_result *result, struct imsgev *iev) +copy_move_finish(struct copy_move *cm, int ok, struct imsgev *iev) { - index_lock_release(il_a); - index_lock_release(il_b); + if (cm->destfd != -1) + close(cm->destfd); + index_lock_release(&cm->il_a); + index_lock_release(&cm->il_b); - if (result->error != MBOX_OP_ERR_NO_SUCH_MAILBOX && - result->error != MBOX_OP_ERR_LIMIT && - result->error != MBOX_OP_ERR_OVERQUOTA) - result->error = ok ? MBOX_OP_OK : MBOX_OP_ERR_GENERIC; - index_free(idx_a); - index_free(idx_b); + if (cm->result.error != MBOX_OP_ERR_NO_SUCH_MAILBOX && + cm->result.error != MBOX_OP_ERR_LIMIT && + cm->result.error != MBOX_OP_ERR_OVERQUOTA) + cm->result.error = ok ? MBOX_OP_OK : MBOX_OP_ERR_GENERIC; + index_free(&cm->idx_a); + index_free(&cm->idx_b); - if (imsg_compose(&iev->ibuf, IMSG_MBOX_RESULT, 0, 0, -1, result, - sizeof(*result)) == -1) + if (imsg_compose(&iev->ibuf, IMSG_MBOX_RESULT, 0, 0, -1, &cm->result, + sizeof(cm->result)) == -1) log_warn("session %u: imsg_compose IMSG_MBOX_RESULT", session_id); } -/* RFC 9051 SS6.4.7 COPY */ - +/* RFC 9051 section 6.4.7 COPY */ int handle_mbox_copy(struct imsg_mbox_copy *req, const struct seq_range *ranges, uint32_t nranges, struct store_session *ss) { - struct imsgev *iev = &ss->iev; - struct mbox_index idx_a, idx_b; - struct mbox_index *srcidx = NULL, *destidx = NULL; - struct imsg_mbox_result result; - struct copy_staged *staged = NULL; - size_t nstaged = 0, i; - struct index_lock il_a = INDEX_LOCK_INIT; - struct index_lock il_b = INDEX_LOCK_INIT; - struct seq_range vr[SEQSET_MAX_RANGES]; - uint32_t nvr; - int ok = 1; - char desttarget[MBOX_NAME_MAX]; - int cross_mailbox; - int destfd = -1, dfd, got, used; + struct copy_move cm; + struct copy_staged *staged = NULL; + struct seq_range vr[SEQSET_MAX_RANGES]; + size_t nstaged = 0, i; + uint32_t nvr; + int ok = 1, dfd, got, used; - memset(&idx_a, 0, sizeof(idx_a)); - memset(&idx_b, 0, sizeof(idx_b)); - memset(&result, 0, sizeof(result)); - - got = lock_copy_move_mailboxes(ss, "COPY", req->destname, &destfd, - &idx_a, &idx_b, &srcidx, &destidx, desttarget, - sizeof(desttarget), &cross_mailbox, &il_a, &il_b, &result); + got = copy_move_lock(ss, "COPY", req->destname, &cm); if (got == 2) return (1); if (got == 0) { ok = 0; goto done; } - dfd = cross_mailbox ? destfd : ss->mailbox_dir_fd; + dfd = cm.cross_mailbox ? cm.destfd : ss->mailbox_dir_fd; if (!store_space_ok(dfd)) { - result.error = MBOX_OP_ERR_OVERQUOTA; + cm.result.error = MBOX_OP_ERR_OVERQUOTA; ok = 0; - goto close_dest; + goto done; } - /* RFC 2180 SS4.4.2: the numbers are those before the EXPUNGEs */ + /* RFC 2180 section 4.4.2: the numbers are those before the EXPUNGEs */ if ((got = saved_resolve(ss, ranges, nranges, vr, &nvr, &req->by_uid, &used)) != 0) { if (got == -1) - result.error = MBOX_OP_ERR_LIMIT; + cm.result.error = MBOX_OP_ERR_LIMIT; ok = 0; - goto close_dest; + goto done; } - (void)view_sync(ss, srcidx, 1); + (void)view_sync(ss, cm.srcidx, 1); - if (!stage_copy_messages(ss, srcidx, req, vr, nvr, &staged, + if (!stage_copy_messages(ss, cm.srcidx, req, vr, nvr, &staged, &nstaged)) { ok = 0; - goto close_dest; + goto done; } - if (nstaged > 0) { - if (ensure_maildir_dirs(dfd, "") == -1) { + if (ensure_maildir_dirs(dfd, "") == -1) ok = 0; - goto close_dest; - } - if (!commit_copy_messages(ss, dfd, destidx, staged, nstaged)) + else if (!commit_copy_messages(ss, dfd, cm.destidx, staged, + nstaged)) ok = 0; } - if (ok) { - result.count = (uint32_t)nstaged; - result.uidvalidity = destidx->uidvalidity; - result.highestmodseq = destidx->highestmodseq; + cm.result.count = (uint32_t)nstaged; + cm.result.uidvalidity = cm.destidx->uidvalidity; + cm.result.highestmodseq = cm.destidx->highestmodseq; } -close_dest: - if (destfd != -1) - close(destfd); - done: for (i = 0; i < nstaged; i++) free(staged[i].srcpath); free(staged); - - finish_copy_move(&idx_a, &idx_b, &il_a, &il_b, ok, &result, iev); + copy_move_finish(&cm, ok, &ss->iev); return (0); } @@ -557,8 +548,6 @@ compaction_bail(struct mbox_index *idx, size_t dropped return (out); } -/* test the read position, not the write index */ - static int move_same_mailbox(struct imsg_mbox_copy *req, const struct seq_range *ranges, uint32_t nranges, struct mbox_index *idx, uint32_t *nmoved_out, @@ -610,9 +599,9 @@ move_same_mailbox(struct imsg_mbox_copy *req, const st if (nmoved == movedcap) { size_t newcap = (movedcap == 0) ? 8 : movedcap * 2; - struct moved *newmoved = reallocarray(moved, newcap, - sizeof(*moved)); + struct moved *newmoved; + newmoved = reallocarray(moved, newcap, sizeof(*moved)); if (newmoved == NULL) { log_warn("session %u: MOVE: reallocarray " "moved", session_id); @@ -652,8 +641,7 @@ move_same_mailbox(struct imsg_mbox_copy *req, const st if (!index_basename_valid(moved[i].basename) || !index_field_valid(moved[i].keywords)) { log_warnx("session %u: MOVE: unsafe field in moved " - "message, refusing to update the index", - session_id); + "message", session_id); ok = 0; goto done; } @@ -694,7 +682,6 @@ move_same_mailbox(struct imsg_mbox_copy *req, const st goto done; } - /* COPYUID mapping first */ for (i = 0; i < nmoved; i++) { struct imsg_mbox_copy_mapping mapping; @@ -706,7 +693,7 @@ move_same_mailbox(struct imsg_mbox_copy *req, const st log_warn("session %u: imsg_compose " "IMSG_MBOX_COPY_MAPPING", session_id); } - /* then EXPUNGE (RFC 9051 SS6.4.8) */ + /* RFC 9051 section 6.4.8: COPYUID before EXPUNGE */ for (i = 0; i < nmoved; i++) { pcache_drop_uid(ss->selected_mailbox, moved[i].old_uid); (void)send_expunged(ss, moved[i].old_seqno, moved[i].old_uid); @@ -718,13 +705,13 @@ done: return (ok); } -/* RFC 9051 SS6.4.8: dest commits before removal */ - +/* RFC 9051 section 6.4.8: dest commits before removal */ static int move_cross_mailbox(struct imsg_mbox_copy *req, const struct seq_range *ranges, - uint32_t nranges, struct mbox_index *srcidx, struct mbox_index *destidx, - int destfd, uint32_t *nmoved_out, struct store_session *ss) + uint32_t nranges, struct copy_move *cm, uint32_t *nmoved_out, + struct store_session *ss) { + struct mbox_index *srcidx = cm->srcidx; struct copy_staged *staged = NULL; size_t nstaged = 0, i; int ok = 1, any_removed = 0; @@ -738,11 +725,12 @@ move_cross_mailbox(struct imsg_mbox_copy *req, const s if (nstaged == 0) return (1); - if (ensure_maildir_dirs(destfd, "") == -1) { + if (ensure_maildir_dirs(cm->destfd, "") == -1) { ok = 0; goto cleanup; } - if (!commit_copy_messages(ss, destfd, destidx, staged, nstaged)) { + if (!commit_copy_messages(ss, cm->destfd, cm->destidx, staged, + nstaged)) { ok = 0; goto cleanup; } @@ -785,14 +773,13 @@ move_cross_mailbox(struct imsg_mbox_copy *req, const s (void)send_expunged(ss, old_seqno, staged[i].src_uid); } - /* RFC 7162 SS3.1 */ + /* RFC 7162 section 3.1 */ if (any_removed) srcidx->highestmodseq++; if (index_save(ss->mailbox_dir_fd, srcidx) == -1) { - log_warnx("session %u: MOVE: destination commit succeeded " - "but saving the source's compacted index failed, " - "message(s) may remain duplicated", session_id); + log_warnx("session %u: MOVE: source index not saved after " + "commit, duplicates possible", session_id); ok = 0; goto cleanup; } @@ -806,75 +793,49 @@ cleanup: return (ok); } -/* RFC 9051 SS6.4.8 MOVE */ - +/* RFC 9051 section 6.4.8 MOVE */ int handle_mbox_move(struct imsg_mbox_copy *req, const struct seq_range *ranges, uint32_t nranges, struct store_session *ss) { - struct imsgev *iev = &ss->iev; - struct mbox_index idx_a, idx_b; - struct mbox_index *srcidx = NULL, *destidx = NULL; - struct imsg_mbox_result result; - uint32_t nmoved = 0; - struct index_lock il_a = INDEX_LOCK_INIT; - struct index_lock il_b = INDEX_LOCK_INIT; - int ok = 1; - char desttarget[MBOX_NAME_MAX]; - int cross_mailbox; - int destfd = -1, got, used; - struct seq_range vr[SEQSET_MAX_RANGES]; - uint32_t nvr; + struct copy_move cm; + struct seq_range vr[SEQSET_MAX_RANGES]; + uint32_t nmoved = 0, nvr; + int ok = 1, got, used; - memset(&idx_a, 0, sizeof(idx_a)); - memset(&idx_b, 0, sizeof(idx_b)); - memset(&result, 0, sizeof(result)); - - got = lock_copy_move_mailboxes(ss, "MOVE", req->destname, &destfd, - &idx_a, &idx_b, &srcidx, &destidx, desttarget, - sizeof(desttarget), &cross_mailbox, &il_a, &il_b, &result); + got = copy_move_lock(ss, "MOVE", req->destname, &cm); if (got == 2) return (1); if (got == 0) { ok = 0; goto done; } - if (cross_mailbox && !store_space_ok(destfd)) { - result.error = MBOX_OP_ERR_OVERQUOTA; + if (cm.cross_mailbox && !store_space_ok(cm.destfd)) { + cm.result.error = MBOX_OP_ERR_OVERQUOTA; ok = 0; - close(destfd); goto done; } - if ((got = saved_resolve(ss, ranges, nranges, vr, &nvr, &req->by_uid, &used)) != 0) { if (got == -1) - result.error = MBOX_OP_ERR_LIMIT; + cm.result.error = MBOX_OP_ERR_LIMIT; ok = 0; - if (destfd != -1) - close(destfd); goto done; } - (void)view_sync(ss, srcidx, 1); - if (!cross_mailbox) { - if (!move_same_mailbox(req, vr, nvr, srcidx, &nmoved, ss)) + (void)view_sync(ss, cm.srcidx, 1); + if (!cm.cross_mailbox) { + if (!move_same_mailbox(req, vr, nvr, cm.srcidx, &nmoved, ss)) ok = 0; - } else { - if (!move_cross_mailbox(req, vr, nvr, srcidx, destidx, - destfd, &nmoved, ss)) - ok = 0; - } + } else if (!move_cross_mailbox(req, vr, nvr, &cm, &nmoved, ss)) + ok = 0; if (ok) { - result.count = nmoved; - result.uidvalidity = destidx->uidvalidity; - result.highestmodseq = destidx->highestmodseq; + cm.result.count = nmoved; + cm.result.uidvalidity = cm.destidx->uidvalidity; + cm.result.highestmodseq = cm.destidx->highestmodseq; } - if (destfd != -1) - close(destfd); - done: - finish_copy_move(&idx_a, &idx_b, &il_a, &il_b, ok, &result, iev); + copy_move_finish(&cm, ok, &ss->iev); return (0); } blob - 4a85168d6e91df742c5d7a3a265c65d69f6f2598 blob + b7b8bb19580947ffe0a98e1aef02f008ac413bdb --- src/mbox_fetch.c +++ src/mbox_fetch.c @@ -35,10 +35,10 @@ #include "log.h" #include "store_internal.h" -static void fetch_walk_step(struct store_session *); -static void fetch_walk_finish(struct store_session *, int); -static const char *fetch_cached(struct store_session *, - const struct index_rec *, int, int *, uint32_t *); +static void fetch_walk_step(struct store_session *); +static void fetch_walk_finish(struct store_session *, int); +static const char *fetch_cached(struct store_session *, + const struct index_rec *, int, int *, uint32_t *); static void fetch_send_part(struct store_session *ss, int imsg_type, const char *what, @@ -63,7 +63,7 @@ fetch_send_part(struct store_session *ss, int imsg_typ free(combined); } -/* RFC 9051 SS6.4.5: BODY[...] sets \Seen, with STORE's own code */ +/* RFC 9051 section 6.4.5: BODY[...] sets \Seen, with STORE's own code */ static int fetch_set_seen(struct store_session *ss, int synced) { @@ -112,8 +112,8 @@ handle_mbox_fetch(struct imsg_mbox_fetch *req, const s return (1); if (fw->active) { - log_warnx("session %u: FETCH arrived during a paused FETCH, " - "abandoning the earlier walk", session_id); + log_warnx("session %u: FETCH during a paused FETCH, earlier " + "walk abandoned", session_id); index_free(&fw->idx); } memset(fw, 0, sizeof(*fw)); @@ -142,7 +142,7 @@ handle_mbox_fetch(struct imsg_mbox_fetch *req, const s fetch_walk_finish(ss, 0); return (0); } - /* RFC 9051 SS6.4.9 */ + /* RFC 9051 section 6.4.9 */ fw->nresolved = seqset_resolve(vr, nvr, fw->req.by_uid ? index_max_uid(idx) : (uint32_t)idx->nlines, !fw->req.by_uid, fw->resolved); @@ -159,7 +159,7 @@ handle_mbox_fetch(struct imsg_mbox_fetch *req, const s } } - /* RFC 7162 SS3.2.6: VANISHED (EARLIER) first */ + /* RFC 7162 section 3.2.6: VANISHED (EARLIER) first */ if (req->by_uid && req->want_vanished) { for (i = 0; i < fw->nresolved; i++) send_vanished_range(idx, fw->resolved[i].lo, @@ -208,32 +208,261 @@ fetch_cached(struct store_session *ss, const struct in return (text); } +static int +fetch_meta(struct store_session *ss, const struct index_rec *rec, uint32_t i, + struct imsg_mbox_fetch_meta *meta, int *have_file) +{ + struct store_fetch_walk *fw = &ss->fetch; + struct imsg_mbox_fetch *req = &fw->req; + off_t size = 0; + char suffix[64]; + + memset(meta, 0, sizeof(*meta)); + meta->seqno = view_seqno(ss, rec->uid, i); + meta->uid = rec->uid; + meta->modseq = rec->modseq; + meta->seen_set = fw->seen_modseq != 0 && rec->modseq == fw->seen_modseq; + + if (meta->seen_set || + (req->attrs & (MBOX_FETCH_RFC822_SIZE | MBOX_FETCH_FLAGS))) { + if (locate_message_file(&ss->cur_snap, ss->mailbox_dir_fd, + rec->basename, &size, suffix, sizeof(suffix)) == -1) { + log_warnx("session %u: message %s (uid %u) indexed but " + "missing on disk, skipped", session_id, + rec->basename, meta->uid); + return (-1); + } + *have_file = 1; + } + + if (req->attrs & MBOX_FETCH_RFC822_SIZE) + meta->size = (uint64_t)size; + if ((req->attrs & MBOX_FETCH_FLAGS) || meta->seen_set) + build_flags_string(*have_file ? suffix : "", rec->keywords, + meta->flags, sizeof(meta->flags)); + if (req->attrs & MBOX_FETCH_INTERNALDATE) + meta->internaldate = parse_maildir_timestamp(rec->basename); + return (0); +} + +static void +fetch_send_header(struct store_session *ss, const struct index_rec *rec, + uint32_t seqno) +{ + struct imsg_mbox_fetch *req = &ss->fetch.req; + struct imsg_mbox_fetch_header hdrmeta; + struct imsg_parser_req preq; + struct imsg_parser_rep prep; + char *hdrbuf = NULL; + uint32_t hdrlen = 0; + int mfd, rc; + + memset(&hdrmeta, 0, sizeof(hdrmeta)); + hdrmeta.seqno = seqno; + hdrmeta.uid = rec->uid; + if (req->attrs & MBOX_FETCH_BODY_HEADER) + rc = read_message_header(&ss->cur_snap, ss->mailbox_dir_fd, + rec->basename, &hdrbuf, &hdrlen); + else if ((mfd = open_message_file(&ss->cur_snap, ss->mailbox_dir_fd, + rec->basename)) == -1) + rc = -1; + else { + memset(&preq, 0, sizeof(preq)); + /* same size, and terminated on arrival */ + (void)strlcpy(preq.fields, req->header_fields, + sizeof(preq.fields)); + preq.fields_not = req->header_fields_not; + rc = parser_request(IMSG_PARSER_HEADER_FIELDS, "HEADER.FIELDS", + mfd, rec->basename, &preq, NULL, 0, FETCH_HEADER_MAX, &prep, + &hdrbuf); + hdrlen = prep.len; + close(mfd); + } + if (rc == 0) { + hdrmeta.found = 1; + hdrmeta.hdrlen = hdrlen; + } + + fetch_send_part(ss, IMSG_MBOX_FETCH_HEADER, "IMSG_MBOX_FETCH_HEADER", + &hdrmeta, sizeof(hdrmeta), hdrmeta.found, hdrbuf, hdrlen); + free(hdrbuf); +} + +/* WHOLE wins over TEXT and PART; -1 when out of descriptors */ +static int +fetch_send_body(struct store_session *ss, const struct index_rec *rec, + uint32_t seqno) +{ + struct store_fetch_walk *fw = &ss->fetch; + struct imsg_mbox_fetch *req = &fw->req; + struct imsg_mbox_fetch_body bodymeta; + struct imsg_parser_req preq; + struct imsg_parser_rep prep; + uint64_t off = 0, len = 0; + int fd = -1, fdbusy = 0; + int want_whole, want_part, want_text; + + want_whole = (req->attrs & MBOX_FETCH_BODY_WHOLE) != 0; + want_part = !want_whole && (req->attrs & MBOX_FETCH_BODY_PART) != 0 && + !(req->attrs & MBOX_FETCH_BODY_TEXT); + want_text = !want_whole && !want_part; + + memset(&bodymeta, 0, sizeof(bodymeta)); + bodymeta.seqno = seqno; + bodymeta.uid = rec->uid; + + if (want_part) { + memset(&preq, 0, sizeof(preq)); + preq.pathlen = parse_section_part(req->section_part, preq.path, + MIME_MAX_DEPTH); + if (preq.pathlen != -1 && + (fd = open_message_file(&ss->cur_snap, ss->mailbox_dir_fd, + rec->basename)) == -1) + fdbusy = errno == EMFILE || errno == ENFILE; + else if (preq.pathlen != -1 && + parser_request(IMSG_PARSER_PART, "BODY[]", fd, + rec->basename, &preq, NULL, 0, 0, &prep, NULL) == 0) { + bodymeta.found = 1; + off = prep.part_off; + len = prep.part_len; + } + /* the fd the extent was checked on */ + if (fd != -1 && (!bodymeta.found || len == 0)) { + close(fd); + fd = -1; + } + } else { + fd = message_body_range(&ss->cur_snap, ss->mailbox_dir_fd, + rec->basename, want_text, &off, &len, &fdbusy); + if (fd != -1) + bodymeta.found = 1; + } + + /* out of descriptors: retry once sent */ + if (fdbusy && fw->fds > 0) + return (-1); + + if (bodymeta.found) { + partial_range(req->has_partial, req->partial_start, + req->partial_count, &off, &len); + if (len == 0 && fd != -1) { + close(fd); + fd = -1; + } + bodymeta.offset = off; + bodymeta.length = len; + } + + /* imsg_compose() owns fd only once it succeeds */ + if (imsg_compose(&ss->iev.ibuf, IMSG_MBOX_FETCH_BODY, 0, 0, fd, + &bodymeta, sizeof(bodymeta)) == -1) { + log_warn("session %u: imsg_compose IMSG_MBOX_FETCH_BODY", + session_id); + if (fd != -1) + close(fd); + } else { + fw->composed += sizeof(bodymeta); + if (fd != -1) + fw->fds++; + } + return (0); +} + +/* a cached ENVELOPE or BODYSTRUCTURE, else the parser's; NULL if neither */ +static const char * +fetch_parsed_text(struct store_session *ss, const struct index_rec *rec, + int item, int *have_file, char **bufp, uint32_t *lenp) +{ + struct imsg_parser_rep prep; + const char *text, *what; + uint32_t max; + int mfd, type; + + *bufp = NULL; + if ((text = fetch_cached(ss, rec, item, have_file, lenp)) != NULL) + return (text); + if ((mfd = open_message_file(&ss->cur_snap, ss->mailbox_dir_fd, + rec->basename)) == -1) + return (NULL); + if (item == PCACHE_ENVELOPE) { + type = IMSG_PARSER_ENVELOPE; + what = "ENVELOPE"; + max = ENVELOPE_MAX; + } else { + type = IMSG_PARSER_BODYSTRUCTURE; + what = "BODYSTRUCTURE"; + max = BODYSTRUCTURE_MAX; + } + if (parser_request(type, what, mfd, rec->basename, NULL, NULL, 0, max, + &prep, bufp) == 0) { + *lenp = prep.len; + pcache_put(ss->selected_mailbox, ss->fetch.idx.uidvalidity, + rec->uid, rec->basename, item, *bufp, prep.len); + text = *bufp; + } + close(mfd); + return (text); +} + +static void +fetch_send_envelope(struct store_session *ss, const struct index_rec *rec, + uint32_t seqno, int *have_file) +{ + struct imsg_mbox_fetch_envelope envmeta; + const char *env; + char *envbuf; + + memset(&envmeta, 0, sizeof(envmeta)); + envmeta.seqno = seqno; + envmeta.uid = rec->uid; + env = fetch_parsed_text(ss, rec, PCACHE_ENVELOPE, have_file, &envbuf, + &envmeta.envlen); + envmeta.found = env != NULL; + fetch_send_part(ss, IMSG_MBOX_FETCH_ENVELOPE, + "IMSG_MBOX_FETCH_ENVELOPE", &envmeta, sizeof(envmeta), + envmeta.found, env, envmeta.envlen); + free(envbuf); +} + +static void +fetch_send_bodystructure(struct store_session *ss, + const struct index_rec *rec, uint32_t seqno, int *have_file) +{ + struct imsg_mbox_fetch_bodystructure bsmeta; + const char *bs; + char *bsbuf; + + memset(&bsmeta, 0, sizeof(bsmeta)); + bsmeta.seqno = seqno; + bsmeta.uid = rec->uid; + bs = fetch_parsed_text(ss, rec, PCACHE_BODYSTRUCTURE, have_file, &bsbuf, + &bsmeta.bslen); + bsmeta.found = bs != NULL; + fetch_send_part(ss, IMSG_MBOX_FETCH_BODYSTRUCTURE, + "IMSG_MBOX_FETCH_BODYSTRUCTURE", &bsmeta, sizeof(bsmeta), + bsmeta.found, bs, bsmeta.bslen); + free(bsbuf); +} + /* returns, still active, once FETCH_BATCH_MAX is queued */ static void fetch_walk_step(struct store_session *ss) { - struct store_fetch_walk *fw = &ss->fetch; - struct mbox_index *idx = &fw->idx; - struct imsg_mbox_fetch *req = &fw->req; - struct imsgev *iev = &ss->iev; - struct seq_range *resolved = fw->resolved; - uint32_t nresolved = fw->nresolved; - uint32_t max_hi = fw->max_hi; - uint32_t i; + struct store_fetch_walk *fw = &ss->fetch; + struct mbox_index *idx = &fw->idx; + struct imsg_mbox_fetch *req = &fw->req; + struct imsg_mbox_fetch_meta meta; + struct index_rec rec; + enum seqset_pos pos; + uint32_t i; + int have_file, ready; for (i = fw->next; i <= (uint32_t)idx->nlines; i++) { - struct imsg_mbox_fetch_meta meta; - struct index_rec rec; - enum seqset_pos pos; - off_t size = 0; - char suffix[64]; - int have_file = 0; - if (index_parse_line(idx->lines[i - 1], &rec) == -1) continue; - pos = seqset_position(resolved, nresolved, max_hi, req->by_uid, - rec.uid, i); + pos = seqset_position(fw->resolved, fw->nresolved, fw->max_hi, + req->by_uid, rec.uid, i); if (pos == SEQSET_PAST_END) break; if (pos == SEQSET_SKIP) @@ -244,9 +473,7 @@ fetch_walk_step(struct store_session *ss) if (!fw->no_parser && fetch_needs_parser(req, ss->selected_mailbox, idx->uidvalidity, &rec)) { - int ready = parser_ready(); - - if (ready == 0) { + if ((ready = parser_ready()) == 0) { fw->next = i; fw->wait_parser = 1; return; @@ -255,237 +482,29 @@ fetch_walk_step(struct store_session *ss) fw->no_parser = 1; } - memset(&meta, 0, sizeof(meta)); - meta.seqno = view_seqno(ss, rec.uid, i); - meta.uid = rec.uid; - meta.modseq = rec.modseq; - meta.seen_set = fw->seen_modseq != 0 && - rec.modseq == fw->seen_modseq; + have_file = 0; + if (fetch_meta(ss, &rec, i, &meta, &have_file) == -1) + continue; - if (meta.seen_set || (req->attrs & - (MBOX_FETCH_RFC822_SIZE | MBOX_FETCH_FLAGS))) { - if (locate_message_file(&ss->cur_snap, - ss->mailbox_dir_fd, rec.basename, &size, suffix, - sizeof(suffix)) == -1) { - log_warnx("session %u: message %s (uid %u) " - "indexed but missing on disk, skipped", - session_id, rec.basename, meta.uid); - continue; - } - have_file = 1; - } - - if (req->attrs & MBOX_FETCH_RFC822_SIZE) - meta.size = (uint64_t)size; - if ((req->attrs & MBOX_FETCH_FLAGS) || meta.seen_set) - build_flags_string(have_file ? suffix : "", - rec.keywords, meta.flags, sizeof(meta.flags)); - if (req->attrs & MBOX_FETCH_INTERNALDATE) - meta.internaldate = parse_maildir_timestamp( - rec.basename); - /* FETCH_HEADER precedes FETCH_META */ if (req->attrs & (MBOX_FETCH_BODY_HEADER | - MBOX_FETCH_HEADER_FIELDS)) { - struct imsg_mbox_fetch_header hdrmeta; - struct imsg_parser_req preq; - struct imsg_parser_rep prep; - char *hdrbuf = NULL; - uint32_t hdrlen = 0; - int mfd, rc; + MBOX_FETCH_HEADER_FIELDS)) + fetch_send_header(ss, &rec, meta.seqno); - memset(&hdrmeta, 0, sizeof(hdrmeta)); - hdrmeta.seqno = view_seqno(ss, rec.uid, i); - hdrmeta.uid = rec.uid; - if (req->attrs & MBOX_FETCH_BODY_HEADER) - rc = read_message_header(&ss->cur_snap, - ss->mailbox_dir_fd, rec.basename, &hdrbuf, - &hdrlen); - else if ((mfd = open_message_file(&ss->cur_snap, - ss->mailbox_dir_fd, rec.basename)) == -1) - rc = -1; - else { - memset(&preq, 0, sizeof(preq)); - /* same size, and terminated on arrival */ - (void)strlcpy(preq.fields, req->header_fields, - sizeof(preq.fields)); - preq.fields_not = req->header_fields_not; - rc = parser_request(IMSG_PARSER_HEADER_FIELDS, - "HEADER.FIELDS", mfd, rec.basename, &preq, - NULL, 0, FETCH_HEADER_MAX, &prep, &hdrbuf); - hdrlen = prep.len; - close(mfd); - } - if (rc == 0) { - hdrmeta.found = 1; - hdrmeta.hdrlen = hdrlen; - } - - fetch_send_part(ss, IMSG_MBOX_FETCH_HEADER, - "IMSG_MBOX_FETCH_HEADER", &hdrmeta, sizeof(hdrmeta), - hdrmeta.found, hdrbuf, hdrlen); - free(hdrbuf); + if ((req->attrs & (MBOX_FETCH_BODY_WHOLE | + MBOX_FETCH_BODY_TEXT | MBOX_FETCH_BODY_PART)) && + fetch_send_body(ss, &rec, meta.seqno) == -1) { + fw->next = i; + return; } - /* WHOLE wins over TEXT and PART */ - if (req->attrs & (MBOX_FETCH_BODY_WHOLE | MBOX_FETCH_BODY_TEXT | - MBOX_FETCH_BODY_PART)) { - struct imsg_mbox_fetch_body bodymeta; - uint64_t off = 0, len = 0; - int fd = -1, fdbusy = 0; - int want_whole = - (req->attrs & MBOX_FETCH_BODY_WHOLE) != 0; - int want_part = - !want_whole && - (req->attrs & MBOX_FETCH_BODY_PART) != 0 && - !(req->attrs & MBOX_FETCH_BODY_TEXT); - int want_text = - !want_whole && - !want_part; + if (req->attrs & MBOX_FETCH_ENVELOPE) + fetch_send_envelope(ss, &rec, meta.seqno, &have_file); + if (req->attrs & MBOX_FETCH_BODYSTRUCTURE) + fetch_send_bodystructure(ss, &rec, meta.seqno, + &have_file); - memset(&bodymeta, 0, sizeof(bodymeta)); - bodymeta.seqno = view_seqno(ss, rec.uid, i); - bodymeta.uid = rec.uid; - - if (want_part) { - struct imsg_parser_req preq; - struct imsg_parser_rep prep; - - memset(&preq, 0, sizeof(preq)); - preq.pathlen = parse_section_part( - req->section_part, preq.path, - MIME_MAX_DEPTH); - if (preq.pathlen != -1 && - (fd = open_message_file(&ss->cur_snap, - ss->mailbox_dir_fd, rec.basename)) == -1) - fdbusy = errno == EMFILE || - errno == ENFILE; - else if (preq.pathlen != -1 && - parser_request(IMSG_PARSER_PART, - "BODY[]", fd, rec.basename, &preq, - NULL, 0, 0, &prep, NULL) == 0) { - bodymeta.found = 1; - off = prep.part_off; - len = prep.part_len; - } - /* the fd the extent was checked on */ - if (fd != -1 && (!bodymeta.found || len == 0)) { - close(fd); - fd = -1; - } - } else { - fd = message_body_range(&ss->cur_snap, - ss->mailbox_dir_fd, rec.basename, want_text, - &off, &len, &fdbusy); - if (fd != -1) - bodymeta.found = 1; - } - - /* out of descriptors: retry once sent */ - if (fdbusy && fw->fds > 0) { - fw->next = i; - return; - } - - if (bodymeta.found) { - partial_range(req->has_partial, - req->partial_start, req->partial_count, - &off, &len); - if (len == 0 && fd != -1) { - close(fd); - fd = -1; - } - bodymeta.offset = off; - bodymeta.length = len; - } - - /* imsg_compose() owns fd only once it succeeds */ - if (imsg_compose(&iev->ibuf, IMSG_MBOX_FETCH_BODY, 0, 0, - fd, &bodymeta, sizeof(bodymeta)) == -1) { - log_warn("session %u: imsg_compose " - "IMSG_MBOX_FETCH_BODY", session_id); - if (fd != -1) - close(fd); - } else { - fw->composed += sizeof(bodymeta); - if (fd != -1) - fw->fds++; - } - } - - if (req->attrs & MBOX_FETCH_ENVELOPE) { - struct imsg_mbox_fetch_envelope envmeta; - struct imsg_parser_rep prep; - const char *env; - char *envbuf = NULL; - int mfd; - - memset(&envmeta, 0, sizeof(envmeta)); - envmeta.seqno = view_seqno(ss, rec.uid, i); - envmeta.uid = rec.uid; - if ((env = fetch_cached(ss, &rec, PCACHE_ENVELOPE, - &have_file, &envmeta.envlen)) != NULL) - envmeta.found = 1; - else if ((mfd = open_message_file(&ss->cur_snap, - ss->mailbox_dir_fd, rec.basename)) != -1) { - if (parser_request(IMSG_PARSER_ENVELOPE, - "ENVELOPE", mfd, rec.basename, NULL, NULL, - 0, ENVELOPE_MAX, &prep, &envbuf) == 0) { - envmeta.found = 1; - envmeta.envlen = prep.len; - env = envbuf; - pcache_put(ss->selected_mailbox, - idx->uidvalidity, rec.uid, - rec.basename, PCACHE_ENVELOPE, - envbuf, prep.len); - } - close(mfd); - } - - fetch_send_part(ss, IMSG_MBOX_FETCH_ENVELOPE, - "IMSG_MBOX_FETCH_ENVELOPE", &envmeta, - sizeof(envmeta), - envmeta.found, env, envmeta.envlen); - free(envbuf); - } - - if (req->attrs & MBOX_FETCH_BODYSTRUCTURE) { - struct imsg_mbox_fetch_bodystructure bsmeta; - struct imsg_parser_rep prep; - const char *bs; - char *bsbuf = NULL; - int mfd; - - memset(&bsmeta, 0, sizeof(bsmeta)); - bsmeta.seqno = view_seqno(ss, rec.uid, i); - bsmeta.uid = rec.uid; - if ((bs = fetch_cached(ss, &rec, PCACHE_BODYSTRUCTURE, - &have_file, &bsmeta.bslen)) != NULL) - bsmeta.found = 1; - else if ((mfd = open_message_file(&ss->cur_snap, - ss->mailbox_dir_fd, rec.basename)) != -1) { - if (parser_request(IMSG_PARSER_BODYSTRUCTURE, - "BODYSTRUCTURE", mfd, rec.basename, NULL, - NULL, 0, BODYSTRUCTURE_MAX, &prep, - &bsbuf) == 0) { - bsmeta.found = 1; - bsmeta.bslen = prep.len; - bs = bsbuf; - pcache_put(ss->selected_mailbox, - idx->uidvalidity, rec.uid, - rec.basename, PCACHE_BODYSTRUCTURE, - bsbuf, prep.len); - } - close(mfd); - } - - fetch_send_part(ss, IMSG_MBOX_FETCH_BODYSTRUCTURE, - "IMSG_MBOX_FETCH_BODYSTRUCTURE", &bsmeta, - sizeof(bsmeta), bsmeta.found, bs, bsmeta.bslen); - free(bsbuf); - } - - if (imsg_compose(&iev->ibuf, IMSG_MBOX_FETCH_META, 0, 0, -1, + if (imsg_compose(&ss->iev.ibuf, IMSG_MBOX_FETCH_META, 0, 0, -1, &meta, sizeof(meta)) == -1) log_warn("session %u: imsg_compose " "IMSG_MBOX_FETCH_META", session_id); @@ -607,7 +626,7 @@ sysflags_to_letters(uint32_t sysflags, char *out, size out[i] = '\0'; } -/* RFC 9051 SS6.3.11 STATUS */ +/* RFC 9051 section 6.3.11 STATUS */ int handle_mbox_status(struct imsg_mbox_status *req, struct store_session *ss) { @@ -679,9 +698,8 @@ handle_mbox_status(struct imsg_mbox_status *req, struc rec.basename, &size, suffix, sizeof(suffix)) == -1) { log_warnx("session %u: message %s indexed but " - "missing on disk, skipped for STATUS " - "UNSEEN/DELETED/SIZE", session_id, - rec.basename); + "missing on disk, skipped (STATUS)", + session_id, rec.basename); continue; } blob - 87c2870025ee974842c127c02c638b19577c9cda blob + 191a3b187becea845e6322a2b81e4de3a08380f2 --- src/mbox_manage.c +++ src/mbox_manage.c @@ -17,7 +17,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - +#include #include #include #include @@ -43,12 +43,12 @@ int handle_mbox_select(struct imsg_mbox_select *req, const struct seq_range *ranges, uint32_t nranges, struct store_session *ss) { - struct imsgev *iev = &ss->iev; - struct mbox_index idx; - struct imsg_mbox_selected reply; - struct index_lock il = INDEX_LOCK_INIT; - const char *target; - int dfd, locked; + struct imsgev *iev = &ss->iev; + struct mbox_index idx; + struct imsg_mbox_selected reply; + struct index_lock il = INDEX_LOCK_INIT; + const char *target; + int dfd, locked; memset(&reply, 0, sizeof(reply)); @@ -101,16 +101,7 @@ handle_mbox_select(struct imsg_mbox_select *req, close(ss->mailbox_dir_fd); ss->mailbox_dir_fd = dfd; - if (strlcpy(ss->selected_mailbox, target, - sizeof(ss->selected_mailbox)) >= sizeof(ss->selected_mailbox)) { - log_warnx("session %u: SELECT %s: name too long to " - "record, can't happen (validated above, same-size " - "buffers)", session_id, req->mailbox); - reply.error = MBOX_OP_ERR_GENERIC; - index_free(&idx); - index_lock_release(&il); - goto send; - } + strlcpy(ss->selected_mailbox, target, sizeof(ss->selected_mailbox)); reply.error = MBOX_OP_OK; ss->mailbox_selected = 1; @@ -142,7 +133,7 @@ ensure_maildir_dirs(int dfd, const char *prefix) char path[MBOX_NAME_MAX + 8]; size_t i; - for (i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) { + for (i = 0; i < nitems(dirs); i++) { if (snprintf(path, sizeof(path), "%s%s", prefix, dirs[i]) >= (int)sizeof(path)) { log_warnx("session %u: mailbox path too long", @@ -157,7 +148,6 @@ ensure_maildir_dirs(int dfd, const char *prefix) return (0); } - #define STORE_MINSPACE 5 #define STORE_MININODES 5 @@ -176,25 +166,22 @@ store_space_ok(int fd) avail = sfs.f_bavail > 0 ? (uint64_t)sfs.f_bavail : 0; if (sfs.f_blocks > 0 && avail * 100 < (avail + used) * STORE_MINSPACE) { - log_warnx("session %u: less than %d%% of the mail store's " - "space is free, refusing to add to it", session_id, - STORE_MINSPACE); + log_warnx("session %u: under %d%% of the store's space free, " + "refused", session_id, STORE_MINSPACE); return (0); } used = sfs.f_files - sfs.f_ffree; avail = sfs.f_favail > 0 ? (uint64_t)sfs.f_favail : 0; if (sfs.f_files > 0 && avail * 100 < (avail + used) * STORE_MININODES) { - log_warnx("session %u: less than %d%% of the mail store's " - "inodes are free, refusing to add to it", session_id, - STORE_MININODES); + log_warnx("session %u: under %d%% of the store's inodes free, " + "refused", session_id, STORE_MININODES); return (0); } return (1); } -/* RFC 9051 SS6.3.4 CREATE */ - +/* RFC 9051 section 6.3.4 CREATE */ void handle_mbox_create(struct imsg_mbox_create *req, struct imsgev *iev) { @@ -225,7 +212,7 @@ handle_mbox_create(struct imsg_mbox_create *req, struc } else { log_debug("session %u: CREATE %s: already exists", session_id, req->mailbox); - /* RFC 5530 SS3 ALREADYEXISTS */ + /* RFC 5530 section 3 ALREADYEXISTS */ result.error = MBOX_OP_ERR_ALREADY_EXISTS; } goto send; @@ -251,12 +238,18 @@ send: static int remove_maildir_subtree(int dfd, const char *prefix) { - static const char *dirs[] = { "tmp", "new", "cur" }; + static const char *dirs[] = { "tmp", "new", "cur" }; + /* every file created here must go before rmdir(2) */ + static const char *files[] = { + STORE_INDEX_NAME, + STORE_INDEX_TMP_NAME, + STORE_INDEX_LOCK_NAME, + }; /* room for STORE_INDEX_NAME after the longest name */ - char path[MBOX_NAME_MAX + 32]; - size_t i; + char path[MBOX_NAME_MAX + 32]; + size_t i; - for (i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) { + for (i = 0; i < nitems(dirs); i++) { DIR *dp; const struct dirent *de; int ok = 1; @@ -267,15 +260,7 @@ remove_maildir_subtree(int dfd, const char *prefix) session_id); return (-1); } - { - int subfd; - - dp = NULL; - subfd = openat(dfd, path, O_RDONLY | O_DIRECTORY); - if (subfd != -1 && (dp = fdopendir(subfd)) == NULL) - close(subfd); - } - if (dp == NULL) { + if ((dp = opendirat(dfd, path)) == NULL) { if (errno == ENOENT) /* tmp/ may never have been created */ continue; @@ -305,9 +290,8 @@ remove_maildir_subtree(int dfd, const char *prefix) continue; } if (!S_ISREG(st.st_mode)) { - log_warnx("session %u: DELETE: refusing, " - "%s is not a regular file", session_id, - entpath); + log_warnx("session %u: DELETE %s: not a " + "regular file", session_id, entpath); ok = 0; continue; } @@ -328,35 +312,24 @@ remove_maildir_subtree(int dfd, const char *prefix) } } - /* every file created here must go before rmdir(2) */ - { - static const char *files[] = { - STORE_INDEX_NAME, - STORE_INDEX_TMP_NAME, - STORE_INDEX_LOCK_NAME, - }; - size_t f; - - for (f = 0; f < sizeof(files) / sizeof(files[0]); f++) { - if (snprintf(path, sizeof(path), "%s%s", prefix, - files[f]) >= (int)sizeof(path)) { - log_warnx("session %u: DELETE: index path too " - "long", session_id); - return (-1); - } - if (unlinkat(dfd, path, 0) == -1 && errno != ENOENT) { - log_warn("session %u: DELETE: unlink %s", - session_id, path); - return (-1); - } + for (i = 0; i < nitems(files); i++) { + if (snprintf(path, sizeof(path), "%s%s", prefix, files[i]) >= + (int)sizeof(path)) { + log_warnx("session %u: DELETE: index path too long", + session_id); + return (-1); } + if (unlinkat(dfd, path, 0) == -1 && errno != ENOENT) { + log_warn("session %u: DELETE: unlink %s", session_id, + path); + return (-1); + } } return (0); } -/* RFC 9051 SS6.3.5 DELETE */ - +/* RFC 9051 section 6.3.5 DELETE */ void handle_mbox_delete(struct imsg_mbox_delete *req, struct store_session *ss) { @@ -375,12 +348,11 @@ handle_mbox_delete(struct imsg_mbox_delete *req, struc goto send; } - if (fstatat(maildir_root_fd, req->mailbox, &st, AT_SYMLINK_NOFOLLOW) == -1 || !S_ISDIR(st.st_mode)) { log_debug("session %u: DELETE %s: no such mailbox", session_id, req->mailbox); - /* RFC 5530 SS3 NONEXISTENT */ + /* RFC 5530 section 3 NONEXISTENT */ result.error = MBOX_OP_ERR_NO_SUCH_MAILBOX; goto send; } @@ -418,8 +390,7 @@ send: session_id); } -/* RFC 9051 SS6.3.6 RENAME; INBOX is the root and cannot move */ - +/* RFC 9051 section 6.3.6 RENAME; INBOX is the root and cannot move */ void handle_mbox_rename(struct imsg_mbox_rename *req, struct store_session *ss) { @@ -439,22 +410,21 @@ handle_mbox_rename(struct imsg_mbox_rename *req, struc goto send; } - if (fstatat(maildir_root_fd, req->oldname, &st, AT_SYMLINK_NOFOLLOW) == -1 || !S_ISDIR(st.st_mode)) { log_debug("session %u: RENAME %s: no such mailbox", session_id, req->oldname); - /* RFC 5530 SS3 NONEXISTENT */ + /* RFC 5530 section 3 NONEXISTENT */ result.error = MBOX_OP_ERR_NO_SUCH_MAILBOX; goto send; } - /* RFC 9051 SS6.3.6: error to rename onto an existing name */ + /* RFC 9051 section 6.3.6: error to rename onto an existing name */ if (fstatat(maildir_root_fd, req->newname, &st, AT_SYMLINK_NOFOLLOW) == 0 || errno != ENOENT) { log_debug("session %u: RENAME %s -> %s: destination exists", session_id, req->oldname, req->newname); - /* RFC 5530 SS3 ALREADYEXISTS */ + /* RFC 5530 section 3 ALREADYEXISTS */ result.error = MBOX_OP_ERR_ALREADY_EXISTS; goto send; } @@ -473,14 +443,9 @@ send: if (result.error == MBOX_OP_OK) pcache_drop_mailbox(req->oldname); if (result.error == MBOX_OP_OK && - strcmp(ss->selected_mailbox, req->oldname) == 0) { - if (strlcpy(ss->selected_mailbox, req->newname, - sizeof(ss->selected_mailbox)) >= - sizeof(ss->selected_mailbox)) - log_warnx("session %u: RENAME %s -> %s: new name too " - "long to record, can't happen (validated)", - session_id, req->oldname, req->newname); - } + strcmp(ss->selected_mailbox, req->oldname) == 0) + strlcpy(ss->selected_mailbox, req->newname, + sizeof(ss->selected_mailbox)); if (imsg_compose(&iev->ibuf, IMSG_MBOX_RESULT, 0, 0, -1, &result, sizeof(result)) == -1) log_warn("session %u: imsg_compose IMSG_MBOX_RESULT", @@ -593,19 +558,15 @@ sublist_remove(struct sublist *sl, const char *name) static int sublist_load(struct sublist *sl) { - FILE *fp; - char line[MBOX_NAME_MAX + 1]; + FILE *fp = NULL; + char *line = NULL; + size_t linesize = 0; + int fd; sl->present = 0; - { - int fd; - - fp = NULL; - fd = openat(maildir_root_fd, STORE_SUBSCRIPTIONS_NAME, - O_RDONLY); - if (fd != -1 && (fp = fdopen(fd, "r")) == NULL) - close(fd); - } + fd = openat(maildir_root_fd, STORE_SUBSCRIPTIONS_NAME, O_RDONLY); + if (fd != -1 && (fp = fdopen(fd, "r")) == NULL) + close(fd); if (fp == NULL) { if (errno == ENOENT) return (0); @@ -615,28 +576,15 @@ sublist_load(struct sublist *sl) } sl->present = 1; - while (fgets(line, sizeof(line), fp) != NULL) { - /* fgets(3) splits an over-long line */ - if (strchr(line, '\n') == NULL && - strlen(line) == sizeof(line) - 1) { - int c = fgetc(fp); - - if (c != EOF && c != '\n') { - log_warnx("session %u: over-long line in %s, " - "refusing to parse it", session_id, - STORE_SUBSCRIPTIONS_NAME); - goto fail; - } - } + while (getline(&line, &linesize, fp) != -1) { line[strcspn(line, "\n")] = '\0'; if (line[0] == '\0') continue; /* a name SUBSCRIBE would refuse: the file was hand-edited */ if (!mailbox_name_syntax_ok(line)) { - log_warnx("session %u: unusable name in %s, refusing " - "to parse it", session_id, - STORE_SUBSCRIPTIONS_NAME); + log_warnx("session %u: %s: unusable name, not parsed", + session_id, STORE_SUBSCRIPTIONS_NAME); goto fail; } if (sublist_has(sl, line)) @@ -649,10 +597,12 @@ sublist_load(struct sublist *sl) STORE_SUBSCRIPTIONS_NAME); goto fail; } + free(line); fclose(fp); return (0); fail: + free(line); fclose(fp); sublist_free(sl); sl->present = 0; @@ -763,16 +713,7 @@ subs_materialise(struct sublist *sl) struct dirent *de; int badname; - { - int rootfd; - - dp = NULL; - rootfd = openat(maildir_root_fd, ".", - O_RDONLY | O_DIRECTORY); - if (rootfd != -1 && (dp = fdopendir(rootfd)) == NULL) - close(rootfd); - } - if (dp == NULL) { + if ((dp = opendirat(maildir_root_fd, ".")) == NULL) { log_warn("session %u: UNSUBSCRIBE: opendir .", session_id); return (-1); } @@ -795,13 +736,7 @@ list_item_send(struct imsgev *iev, const char *name, i struct imsg_mbox_list_item item; memset(&item, 0, sizeof(item)); - if (strlcpy(item.mailbox, name, sizeof(item.mailbox)) >= - sizeof(item.mailbox)) { - log_warnx("session %u: LIST: %s truncated, can't happen (the " - "name rule already bounds it under MBOX_NAME_MAX)", - session_id, name); - return (0); - } + strlcpy(item.mailbox, name, sizeof(item.mailbox)); item.exists = exists; if (imsg_compose(&iev->ibuf, IMSG_MBOX_LIST_ITEM, 0, 0, -1, &item, sizeof(item)) == -1) { @@ -812,8 +747,7 @@ list_item_send(struct imsgev *iev, const char *name, i return (1); } -/* RFC 9051 SS6.3.9 LIST; INBOX is not sent */ - +/* RFC 9051 section 6.3.9 LIST; INBOX is not sent */ void handle_mbox_list(struct imsg_mbox_list *req, struct imsgev *iev) { @@ -827,20 +761,10 @@ handle_mbox_list(struct imsg_mbox_list *req, struct im memset(&result, 0, sizeof(result)); - if (req->subscribed_only && sublist_load(&sl) == -1) sl.present = 0; - { - int rootfd; - - dp = NULL; - rootfd = openat(maildir_root_fd, ".", - O_RDONLY | O_DIRECTORY); - if (rootfd != -1 && (dp = fdopendir(rootfd)) == NULL) - close(rootfd); - } - if (dp == NULL) { + if ((dp = opendirat(maildir_root_fd, ".")) == NULL) { log_warn("session %u: LIST: opendir .", session_id); result.error = MBOX_OP_ERR_GENERIC; goto send; @@ -851,12 +775,9 @@ handle_mbox_list(struct imsg_mbox_list *req, struct im /* a rejected name hides real mail: say so */ if (badname && !skip_warned) { skip_warned = 1; - log_warnx("session %u: LIST: skipping %s: " - "not a valid mailbox name (RFC 9051 " - "SS5.1); it will not be listed or " - "selectable. Further skips this " - "connection are not logged", session_id, - de->d_name); + log_warnx("session %u: LIST: %s: invalid " + "mailbox name, skipped", + session_id, de->d_name); } continue; } @@ -867,7 +788,7 @@ handle_mbox_list(struct imsg_mbox_list *req, struct im } closedir(dp); - /* RFC 9051 SS6.3.9.6: subscribed names with no mailbox */ + /* RFC 9051 section 6.3.9.6: subscribed names with no mailbox */ for (i = 0; i < sl.n; i++) { struct stat st; @@ -887,8 +808,7 @@ send: session_id); } -/* RFC 9051 SS6.3.7 SUBSCRIBE; idempotent */ - +/* RFC 9051 section 6.3.7 SUBSCRIBE; idempotent */ void handle_mbox_subscribe(struct imsg_mbox_subscribe *req, struct imsgev *iev) { @@ -910,8 +830,7 @@ handle_mbox_subscribe(struct imsg_mbox_subscribe *req, goto send; } - - /* RFC 9051 SS6.3.7 lets a server require the mailbox exist */ + /* RFC 9051 section 6.3.7 lets a server require the mailbox exist */ if (fstatat(maildir_root_fd, req->mailbox, &st, AT_SYMLINK_NOFOLLOW) == -1 || !S_ISDIR(st.st_mode)) { log_debug("session %u: SUBSCRIBE %s: no such mailbox", @@ -945,8 +864,7 @@ send: session_id); } -/* RFC 9051 SS6.3.8 UNSUBSCRIBE; idempotent */ - +/* RFC 9051 section 6.3.8 UNSUBSCRIBE; idempotent */ void handle_mbox_unsubscribe(struct imsg_mbox_subscribe *req, struct imsgev *iev) { @@ -962,7 +880,7 @@ handle_mbox_unsubscribe(struct imsg_mbox_subscribe *re result.error = MBOX_OP_ERR_GENERIC; goto send; } - /* RFC 9051 SS6.3.8: no existence test */ + /* RFC 9051 section 6.3.8: no existence test */ if (!mailbox_name_valid(req->mailbox)) { log_debug("session %u: UNSUBSCRIBE %s: invalid name", session_id, req->mailbox); @@ -970,7 +888,6 @@ handle_mbox_unsubscribe(struct imsg_mbox_subscribe *re goto send; } - if (subs_lock_acquire(&lockfd) == -1 || sublist_load(&sl) == -1) { result.error = MBOX_OP_ERR_GENERIC; goto send; @@ -982,7 +899,7 @@ handle_mbox_unsubscribe(struct imsg_mbox_subscribe *re goto send; } if (!sublist_has(&sl, req->mailbox)) { - /* SS6.3.8: unsubscribing what isn't subscribed returns OK */ + /* section 6.3.8: unsubscribing the unsubscribed is OK */ result.error = MBOX_OP_OK; goto send; } @@ -1006,16 +923,13 @@ send: const char * append_hostname(void) { - static char hostbuf[256]; + static char hostbuf[HOST_NAME_MAX + 1]; static int resolved; if (!resolved) { if (gethostname(hostbuf, sizeof(hostbuf)) == -1) { log_warn("session %u: gethostname", session_id); - if (strlcpy(hostbuf, "imapd", sizeof(hostbuf)) >= - sizeof(hostbuf)) - log_warnx("session %u: gethostname fallback " - "truncated, can't happen", session_id); + strlcpy(hostbuf, "imapd", sizeof(hostbuf)); } resolved = 1; } @@ -1052,8 +966,8 @@ handle_mbox_append_begin(struct store_session *ss, const struct imsg_mbox_append *req) { struct store_append *ap = &ss->append; - char target[MBOX_NAME_MAX]; - int64_t delivery_ts; + char target[MBOX_NAME_MAX]; + int64_t delivery_ts; if (ap->active) { log_warnx("session %u: APPEND begun with another in flight, " @@ -1078,8 +992,8 @@ handle_mbox_append_begin(struct store_session *ss, return; } if (!index_field_valid(req->keywords)) { - log_warnx("session %u: APPEND: refusing unsafe keywords " - "field", session_id); + log_warnx("session %u: APPEND: unsafe keywords field", + session_id); return; } @@ -1136,7 +1050,7 @@ void handle_mbox_append_data(struct store_session *ss, const char *buf, size_t len) { struct store_append *ap = &ss->append; - size_t written = 0; + size_t written = 0; if (!ap->active) { log_warnx("session %u: APPEND data with no APPEND in flight, " blob - f27afc00ad6efce70e76eb235576e2801e307357 blob + dd0be27fb30c0b6486c528ce9d6f7b8ec30da378 --- src/mbox_search.c +++ src/mbox_search.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -44,15 +45,13 @@ kw_list_contains(const char *list, const char *kw) char *save; if (strlcpy(tmp, list, sizeof(tmp)) >= sizeof(tmp)) { - log_warnx("session %u: kw_list_contains: keyword list " - "truncated, can't happen (both same-size " - "MBOX_FLAGS_MAX buffers); treating as not-found", + log_warnx("session %u: keyword list too long, no match", session_id); return (0); } for (tok = strtok_r(tmp, ",", &save); tok != NULL; tok = strtok_r(NULL, ",", &save)) { - /* RFC 9051 SS2.3.2: flags/keywords are case-insensitive */ + /* RFC 9051 section 2.3.2: flags are case-insensitive */ if (strcasecmp(tok, kw) == 0) return (1); } @@ -76,7 +75,7 @@ append_kw_token(char *out, size_t outsize, int *firstp return (1); } -/* RFC 9051 SS6.4.6: SET ignores the old keywords */ +/* RFC 9051 section 6.4.6: SET ignores the old keywords */ int merge_keywords(int mode, const char *old_kws, const char *new_kws, char *out, size_t outsize) @@ -90,10 +89,8 @@ merge_keywords(int mode, const char *old_kws, const ch if (mode == MBOX_STORE_REMOVE) { if (strlcpy(tmp, old_kws, sizeof(tmp)) >= sizeof(tmp)) { - log_warnx("session %u: merge_keywords: old_kws " - "truncated, can't happen (same-size " - "MBOX_FLAGS_MAX buffers); refusing to guess at " - "the keyword set", session_id); + log_warnx("session %u: STORE: old keyword list too " + "long", session_id); return (-1); } for (tok = strtok_r(tmp, ",", &save); tok != NULL; @@ -108,10 +105,8 @@ merge_keywords(int mode, const char *old_kws, const ch if (mode == MBOX_STORE_ADD) { if (strlcpy(tmp, old_kws, sizeof(tmp)) >= sizeof(tmp)) { - log_warnx("session %u: merge_keywords: old_kws " - "truncated, can't happen (same-size " - "MBOX_FLAGS_MAX buffers); refusing to guess at " - "the keyword set", session_id); + log_warnx("session %u: STORE: old keyword list too " + "long", session_id); return (-1); } for (tok = strtok_r(tmp, ",", &save); tok != NULL; @@ -122,9 +117,8 @@ merge_keywords(int mode, const char *old_kws, const ch } if (strlcpy(tmp, new_kws, sizeof(tmp)) >= sizeof(tmp)) { - log_warnx("session %u: merge_keywords: new_kws truncated, " - "can't happen (same-size MBOX_FLAGS_MAX buffers); " - "refusing to guess at the keyword set", session_id); + log_warnx("session %u: STORE: new keyword list too long", + session_id); return (-1); } for (tok = strtok_r(tmp, ",", &save); tok != NULL; @@ -158,8 +152,8 @@ struct search_msg_ctx { const char *keywords; int64_t internaldate; uint64_t size; - uint64_t modseq; /* RFC 7162 SS3.1.5 MODSEQ search key */ - const struct seq_range *saved; + uint64_t modseq; /* RFC 7162 section 3.1.5 MODSEQ key */ + const struct seq_range *saved; uint32_t nsaved; }; @@ -217,7 +211,7 @@ search_eval_leaf(const struct search_node *n, const st } } -/* 1 for the RFC 9051 SS6.4.4 keys the parser-worker answers */ +/* 1 for the RFC 9051 section 6.4.4 keys the parser-worker answers */ int search_op_content(int op) { @@ -296,8 +290,8 @@ handle_mbox_search(struct imsg_mbox_search *req, struc if (sw->active) { /* unreachable while the listener holds the next command */ - log_warnx("session %u: SEARCH arrived during a SEARCH, " - "abandoning the earlier walk", session_id); + log_warnx("session %u: SEARCH during a SEARCH, earlier walk " + "abandoned", session_id); search_walk_abort(ss); } memset(sw, 0, sizeof(*sw)); @@ -377,7 +371,7 @@ handle_mbox_search(struct imsg_mbox_search *req, struc return (0); } -/* answers 0 or 1, or -1 if it could not be checked */ +/* 0 or 1; -1 if it could not be checked; -2 while no parser is ready */ static int search_walk_ask(struct store_session *ss, const struct search_msg_ctx *m, const char *basename) @@ -399,8 +393,8 @@ search_walk_ask(struct store_session *ss, const struct } if ((mfd = open_message_file(&ss->cur_snap, ss->mailbox_dir_fd, basename)) == -1) { - log_warnx("session %u: message %s cannot be opened, SEARCH " - "cannot check it", session_id, basename); + log_warnx("session %u: message %s: open failed, SEARCH cannot " + "check it", session_id, basename); return (-1); } memset(&preq, 0, sizeof(preq)); @@ -439,8 +433,7 @@ search_walk_step(struct store_session *ss) if (++sw->walked > SEARCH_YIELD_MESSAGES) { sw->walked = 0; sw->next = i; - tv.tv_sec = 0; - tv.tv_usec = 0; + timerclear(&tv); evtimer_add(&sw->yield_ev, &tv); return; } @@ -474,8 +467,7 @@ search_walk_step(struct store_session *ss) if (sw->asked >= SEARCH_YIELD_REQUESTS) { sw->asked = 0; sw->next = i; - tv.tv_sec = 0; - tv.tv_usec = 0; + timerclear(&tv); evtimer_add(&sw->yield_ev, &tv); return; } @@ -530,7 +522,7 @@ search_walk_finish(struct store_session *ss, int ok) sw->active = 0; } -/* RFC 9051 SS6.4.4.1: matches on adjacent index lines share a range */ +/* RFC 9051 section 6.4.4.1: matches on adjacent index lines share a range */ static void search_save_match(struct store_search_walk *sw, uint32_t line, uint32_t uid) { @@ -548,7 +540,7 @@ search_save_match(struct store_search_walk *sw, uint32 sw->sv_line = line; } -/* RFC 9051 SS6.4.4.1 and Table 4; 0 when refused, as NOTSAVED */ +/* RFC 9051 section 6.4.4.1 and Table 4; 0 when refused, as NOTSAVED */ static int search_save_finish(struct store_session *ss, int ok) { @@ -589,8 +581,6 @@ search_walk_yield(int fd, short event, void *arg) { struct store_session *ss = arg; - (void)fd; - (void)event; session_id = ss->id; if (ss->search.active) search_walk_step(ss); @@ -630,4 +620,3 @@ search_walk_parser(struct store_session *ss, int ok) } search_walk_step(ss); } - blob - 7bb5732cb223f7c13c61e2c4dcfe219cd96454ab blob + 875bad33c23f506e33a31f3cf3038b986b00ae65 --- src/mbox_store.c +++ src/mbox_store.c @@ -82,12 +82,11 @@ store_plan(struct store_session *ss, const struct imsg break; } - /* RFC 9051 SS6.4.6: refuse rather than store a truncation */ + /* RFC 9051 section 6.4.6: refuse rather than store a truncation */ if (merge_keywords(req->mode, rec->keywords, req->keywords, newkeywords, sizeof(newkeywords)) == -1) { - log_warnx("session %u: STORE: merged keyword set for uid %u " - "exceeds %zu bytes, failing STORE", session_id, rec->uid, - sizeof(newkeywords)); + log_warnx("session %u: STORE: uid %u: merged keywords over " + "%zu bytes", session_id, rec->uid, sizeof(newkeywords)); return (-1); } sysflags_to_letters(new_sysflags, st->letters, sizeof(st->letters)); @@ -150,7 +149,7 @@ store_undo(struct store_session *ss, const struct mbox } } -/* RFC 9051 SS6.4.6 STORE: plan every message, then change; LOCK_EX held */ +/* RFC 9051 section 6.4.6 STORE: plan all, then change; LOCK_EX held */ int store_apply(struct store_session *ss, const struct imsg_mbox_store *req, struct mbox_index *idx, const struct seq_range *resolved, @@ -278,7 +277,6 @@ handle_mbox_store(struct imsg_mbox_store *req, const s uint32_t nranges, struct store_session *ss) { struct imsgev *iev = &ss->iev; - int locked; struct mbox_index idx; struct imsg_mbox_result result; struct index_lock il = INDEX_LOCK_INIT; @@ -286,15 +284,15 @@ handle_mbox_store(struct imsg_mbox_store *req, const s struct seq_range vr[SEQSET_MAX_RANGES]; struct store_step *steps = NULL; size_t nsteps = 0, k; + uint64_t reported = 0; uint32_t nresolved, nvr, sent = 0; - uint64_t reported = 0; int ok = 1, by_uid, synced, used, rc; - int ghost, limit = 0; + int ghost, limit = 0, locked; memset(&idx, 0, sizeof(idx)); if (!index_field_valid(req->keywords)) { - log_warnx("session %u: STORE: refusing unsafe keywords field", + log_warnx("session %u: STORE: unsafe keywords field", session_id); ok = 0; goto done; @@ -323,7 +321,7 @@ handle_mbox_store(struct imsg_mbox_store *req, const s reported = idx.highestmodseq; - /* RFC 9051 SS6.4.9 */ + /* RFC 9051 section 6.4.9 */ nresolved = seqset_resolve(vr, nvr, by_uid ? index_max_uid(&idx) : (uint32_t)idx.nlines, !by_uid, resolved); ghost = !req->by_uid && !used && view_names_ghost(ss, &idx, @@ -354,7 +352,7 @@ handle_mbox_store(struct imsg_mbox_store *req, const s continue; } - /* RFC 7162 SS3.1.3: UNCHANGEDSINCE echoes even under .SILENT */ + /* RFC 7162 section 3.1.3: UNCHANGEDSINCE echoes with .SILENT */ if (!req->silent || req->has_unchangedsince) { struct imsg_mbox_fetch_meta meta; char newsuffix[16]; @@ -381,7 +379,7 @@ done: free(steps); memset(&result, 0, sizeof(result)); - /* RFC 2180 SS4.2.1-SS4.2.3 */ + /* RFC 2180 sections 4.2.1-4.2.3 */ result.error = !ok ? (limit ? MBOX_OP_ERR_LIMIT : MBOX_OP_ERR_GENERIC) : ghost && !req->silent ? MBOX_OP_ERR_EXPUNGEISSUED : MBOX_OP_OK; @@ -450,9 +448,9 @@ handle_mbox_expunge(struct imsg_mbox_expunge *req, out = 0; for (in = 0; in < idx.nlines; in++) { struct index_rec rec; - const char *lp; - uint32_t sysflags; - char suffix[64]; + const char *lp; + char suffix[64]; + uint32_t sysflags; if (index_parse_line(idx.lines[in], &rec) == -1) { idx.lines[out++] = idx.lines[in]; @@ -461,8 +459,8 @@ handle_mbox_expunge(struct imsg_mbox_expunge *req, if (locate_message_file(&ss->cur_snap, ss->mailbox_dir_fd, rec.basename, NULL, suffix, sizeof(suffix)) == -1) { log_warnx("session %u: message %s indexed but missing " - "on disk, kept in index, not counted as " - "expunged", session_id, rec.basename); + "on disk, kept, not expunged", + session_id, rec.basename); idx.lines[out++] = idx.lines[in]; continue; } @@ -472,7 +470,7 @@ handle_mbox_expunge(struct imsg_mbox_expunge *req, idx.lines[out++] = idx.lines[in]; continue; } - /* RFC 9051 SS6.4.9: \Deleted outside the ranges is kept */ + /* RFC 9051 section 6.4.9: \Deleted outside the set is kept */ if (req->by_uid && !seqset_contains(resolved, nresolved, rec.uid)) { idx.lines[out++] = idx.lines[in]; @@ -515,7 +513,7 @@ handle_mbox_expunge(struct imsg_mbox_expunge *req, for (k = 0; k < ngone; k++) { struct index_rec rec; - char path[600]; + char path[600]; if (index_parse_line(gone[k].line, &rec) == -1) continue; @@ -536,7 +534,7 @@ handle_mbox_expunge(struct imsg_mbox_expunge *req, } done: - /* RFC 9051 SS6.4.1: CLOSE deselects only once its expunge is saved. */ + /* RFC 9051 section 6.4.1: CLOSE deselects once its expunge is saved */ if (req->silent && ok) ss->mailbox_selected = 0; blob - f0673170737641f25ea55077ef7a880eec3800ad blob + 061da5af7ca779d4c9007d8673d3754560c9c766 --- src/mboxname.c +++ src/mboxname.c @@ -22,7 +22,7 @@ #include "mboxname.h" #include "utf8.h" -/* RFC 9051 SS5.1: INBOX is case-insensitive */ +/* RFC 9051 section 5.1: INBOX is case-insensitive */ int mailbox_name_is_inbox(const char *name) { @@ -44,10 +44,10 @@ mailbox_name_syntax_ok(const char *name) for (i = 0; i < len; i++) { unsigned char c = (unsigned char)name[i]; - /* RFC 9051 SS5.1.1: "/" is the hierarchy delimiter */ + /* RFC 9051 section 5.1.1: "/" is the hierarchy delimiter */ if (c == '/') return (0); - /* RFC 9051 SS5.1 lets a server refuse CTL names */ + /* RFC 9051 section 5.1 lets a server refuse CTL names */ if (c < 0x20 || c == 0x7f) return (0); } blob - 66731709f1033ef153c187bd122bfb804719994d blob + 16a1c724e3db800c87639c65f736eebf05a1e92c --- src/mboxname.h +++ src/mboxname.h @@ -40,7 +40,7 @@ int mailbox_name_syntax_ok(const char *); int mailbox_name_reserved(const char *); -/* RFC 9051 SS5.1: INBOX is case-insensitive and always exists */ +/* RFC 9051 section 5.1: INBOX is case-insensitive and always exists */ int mailbox_name_is_inbox(const char *); #endif /* IMAPD_MBOXNAME_H */ blob - b31e1c0eec8e826c1f842dc5fa52f2781378025e blob + 139f080299a8866b11aabd0d6578e8ca39bcf134 --- src/mime.c +++ src/mime.c @@ -16,6 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include #include #include @@ -59,18 +60,14 @@ cur_snapshot_discard(struct cur_snapshot *snap) static int cur_snapshot_build(struct cur_snapshot *snap, int dfd) { - DIR *dp = NULL; + DIR *dp; const struct dirent *de; char **names = NULL, **tmp; size_t n = 0, cap = 0; - int curfd; cur_snapshot_discard(snap); - curfd = openat(dfd, "cur", O_RDONLY | O_DIRECTORY); - if (curfd != -1 && (dp = fdopendir(curfd)) == NULL) - close(curfd); - if (dp == NULL) { + if ((dp = opendirat(dfd, "cur")) == NULL) { snap->failed = 1; return (-1); } @@ -142,8 +139,8 @@ cur_entry_take(const char *name, size_t baselen, char return (-1); if (suffix_out != NULL && strlcpy(suffix_out, name + baselen, suffix_out_size) >= suffix_out_size) { - log_warnx("session %u: %s: flag suffix truncated, refusing " - "to report a possibly-wrong flag set", session_id, name); + log_warnx("session %u: %s: flag suffix truncated", + session_id, name); return (-1); } return (0); @@ -157,23 +154,16 @@ scan_cur_for_basename(struct cur_snapshot *snap, int d DIR *dp; const struct dirent *de; const char *hit; - size_t baselen = strlen(basename); + size_t baselen; int rv = -1; + baselen = strlen(basename); if ((hit = cur_snapshot_find(snap, dfd, basename, baselen)) != NULL) return (cur_entry_take(hit, baselen, path, pathsize, suffix_out, suffix_out_size)); /* absent from the snapshot, so it may have arrived since: look */ - { - int curfd; - - dp = NULL; - curfd = openat(dfd, "cur", O_RDONLY | O_DIRECTORY); - if (curfd != -1 && (dp = fdopendir(curfd)) == NULL) - close(curfd); - } - if (dp == NULL) { + if ((dp = opendirat(dfd, "cur")) == NULL) { if (errno != ENOENT) log_warn("session %u: opendir cur", session_id); return (-1); @@ -197,8 +187,9 @@ locate_message_file(struct cur_snapshot *snap, int dfd struct stat st; const char *hit; char path[600]; - size_t baselen = strlen(basename); + size_t baselen; + baselen = strlen(basename); suffix_out[0] = '\0'; if ((hit = cur_snapshot_find(snap, dfd, basename, baselen)) != NULL) { @@ -323,16 +314,15 @@ read_header_from_fd(int fd, const char *basename, char limit = found_sep ? sepindex : (size_t)total; for (i = 0; i < limit && i + 1 < (size_t)total; i++) { if (readbuf[i] == '\0') { - log_warnx("session %u: message %s has a NUL byte in " - "its header, BODY.PEEK[HEADER] skipped", - session_id, basename); + log_warnx("session %u: message %s: NUL in header, " + "BODY.PEEK[HEADER] skipped", session_id, basename); return (-1); } } if (!found_sep || sepindex > FETCH_HEADER_MAX) { - log_warnx("session %u: message %s: no header/body separator " - "found within %d bytes, BODY.PEEK[HEADER] skipped", + log_warnx("session %u: message %s: no header end within %d " + "bytes, BODY.PEEK[HEADER] skipped", session_id, basename, FETCH_HEADER_MAX); return (-1); } @@ -379,7 +369,6 @@ read_body_from_fd(int fd, const char *basename, int te n = read(fd, readbuf + total, readbuf_size - total); if (n == -1) { if (errno == EINTR) - /* as mbox_copy.c's staging read does */ continue; log_warn("session %u: read message body (%s)", session_id, basename); @@ -437,7 +426,7 @@ toolarge: return (-1); } -/* RFC 9051 SS6.4.5.1: ASCII case-insensitive */ +/* RFC 9051 section 6.4.5.1: ASCII case-insensitive */ int header_field_name_matches(const char *name, size_t namelen, const char *list) { @@ -447,13 +436,7 @@ header_field_name_matches(const char *name, size_t nam if (namelen == 0 || namelen >= sizeof(listcopy)) return (0); - if (strlcpy(listcopy, list, sizeof(listcopy)) >= sizeof(listcopy)) { - log_warnx("session %u: header_field_name_matches: list " - "truncated, can't happen (list bounded by same-size " - "HEADER_FIELDS_MAX buffer upstream); treating as " - "not-found", session_id); - return (0); - } + strlcpy(listcopy, list, sizeof(listcopy)); for (tok = strtok_r(listcopy, " ", &save); tok != NULL; tok = strtok_r(NULL, " ", &save)) { if (strlen(tok) == namelen && @@ -463,7 +446,7 @@ header_field_name_matches(const char *name, size_t nam return (0); } -/* one field plus its RFC 5322 SS2.2.3 obs-fold lines */ +/* one field plus its RFC 5322 section 2.2.3 obs-fold lines */ struct hdr_field { size_t start; size_t name_end; @@ -502,7 +485,7 @@ hdr_next_field(const char *hdr, size_t hdrlen, size_t if (hdr[f->colon] == ':') break; } - /* RFC 5322 SS4.5: white space may precede the colon */ + /* RFC 5322 section 4.5: white space may precede the colon */ f->name_end = f->colon; while (f->colon < f->line_end && f->name_end > f->start && (hdr[f->name_end - 1] == ' ' || hdr[f->name_end - 1] == '\t')) @@ -523,7 +506,7 @@ hdr_next_field(const char *hdr, size_t hdrlen, size_t return (1); } -/* RFC 9051 SS6.4.5.1 HEADER.FIELDS[.NOT] */ +/* RFC 9051 section 6.4.5.1 HEADER.FIELDS[.NOT] */ int filter_header_fields(const char *hdr, size_t hdrlen, const char *fields_spec, int want_not, char *out, size_t *outlen_out) @@ -542,7 +525,7 @@ filter_header_fields(const char *hdr, size_t hdrlen, c /* no terminating blank line */ return (-1); if (r == 0) { - /* RFC 9051 SS6.4.5: the blank line is included */ + /* RFC 9051 section 6.4.5: the blank line is included */ memcpy(out + outlen, hdr + f.start, f.end - f.start); outlen += f.end - f.start; *outlen_out = outlen; @@ -597,14 +580,15 @@ read_message_header_fields(int fd, const char *basenam return (0); } -/* RFC 5322 SS2.2.3 unfolding; NIL vs "" per RFC 9051 SS7.5.2 */ +/* RFC 5322 section 2.2.3 unfolding; NIL vs "" per RFC 9051 section 7.5.2 */ int extract_header_field(const char *hdr, size_t hdrlen, const char *name, char **val_out, size_t *vallen_out) { - size_t namelen = strlen(name); + size_t namelen; size_t off = 0; + namelen = strlen(name); *val_out = NULL; *vallen_out = 0; @@ -664,7 +648,7 @@ header_next_field(const char *hdr, size_t hdrlen, size for (;;) { if (hdr_next_field(hdr, hdrlen, off, &f) != 1) return (0); - /* a line with no colon is not a field (RFC 5322 SS2.2) */ + /* a line with no colon is not a field (RFC 5322 section 2.2) */ if (f.colon == f.line_end) continue; vstart = f.colon + 1; @@ -708,7 +692,7 @@ find_header_body_split(const char *buf, size_t len, si return (-1); } -/* RFC 2045 SS5.1 tspecials; a `token` excludes SPACE, CTLs, and these */ +/* RFC 2045 section 5.1 tspecials; a `token` excludes SPACE, CTLs, and these */ int mime_is_tspecial(char c) { @@ -734,7 +718,7 @@ mime_read_token_or_qstring(const char *s, size_t len, (*pos)++; c = s[*pos]; } - /* RFC 5322 SS4: keep CR and LF; a NUL would end out */ + /* keep CR and LF (RFC 5322 section 4); NUL ends out */ if (c == '\0') c = ' '; if (outlen + 1 >= outsize) @@ -772,12 +756,9 @@ mime_str_upper(char *s) } } -/* RFC 2045 SS5.1 Content-Type; SS5.2 default on failure */ +/* RFC 2045 section 5.1 Content-Type; section 5.2 default on failure */ int -parse_content_type(const char *hdr, size_t hdrlen, char *type_out, - size_t typesize, char *subtype_out, size_t subtypesize, - char *params_fmt_out, size_t params_fmt_outsize, char *boundary_out, - size_t boundary_outsize, int *has_boundary_out) +parse_content_type(const char *hdr, size_t hdrlen, struct content_type *ct) { char *val = NULL; size_t vallen = 0; @@ -786,48 +767,38 @@ parse_content_type(const char *hdr, size_t hdrlen, cha int nparams = 0; int use_default = 0; /* -1: envbuf_append*() never NUL-terminates, reserve a byte */ - size_t pfsize = (params_fmt_outsize > 0) ? params_fmt_outsize - 1 : 0; + size_t pfsize = sizeof(ct->params_fmt) - 1; - *has_boundary_out = 0; - boundary_out[0] = '\0'; + memset(ct, 0, sizeof(*ct)); - if (pfsize == 0) - return (-1); - if (extract_header_field(hdr, hdrlen, "Content-Type", &val, &vallen) == -1 || vallen == 0) use_default = 1; if (!use_default && (mime_read_token_or_qstring(val, vallen, &pos, - type_out, typesize) == -1 || pos >= vallen || val[pos] != '/')) + ct->type, sizeof(ct->type)) == -1 || pos >= vallen || + val[pos] != '/')) use_default = 1; if (!use_default) { pos++; - if (mime_read_token_or_qstring(val, vallen, &pos, subtype_out, - subtypesize) == -1) + if (mime_read_token_or_qstring(val, vallen, &pos, ct->subtype, + sizeof(ct->subtype)) == -1) use_default = 1; } if (use_default) { free(val); - if (strlcpy(type_out, "TEXT", typesize) >= typesize || - strlcpy(subtype_out, "PLAIN", subtypesize) >= - subtypesize) { - log_warnx("session %u: parse_content_type: default " - "type/subtype truncated, caller-supplied " - "buffer too small for \"TEXT\"/\"PLAIN\"", - session_id); - return (-1); - } - if (envbuf_append_str(params_fmt_out, pfsize, &plen, + strlcpy(ct->type, "TEXT", sizeof(ct->type)); + strlcpy(ct->subtype, "PLAIN", sizeof(ct->subtype)); + if (envbuf_append_str(ct->params_fmt, pfsize, &plen, "(\"CHARSET\" \"US-ASCII\")") == -1) return (-1); - params_fmt_out[plen] = '\0'; + ct->params_fmt[plen] = '\0'; return (0); } - mime_str_upper(type_out); - mime_str_upper(subtype_out); + mime_str_upper(ct->type); + mime_str_upper(ct->subtype); for (;;) { char attr[64], value[256]; @@ -854,47 +825,45 @@ parse_content_type(const char *hdr, size_t hdrlen, cha break; mime_str_upper(attr); - if (envbuf_append_str(params_fmt_out, pfsize, + if (envbuf_append_str(ct->params_fmt, pfsize, &plen, nparams == 0 ? "(" : " ") == -1 || - envbuf_append_nstring(params_fmt_out, pfsize, + envbuf_append_nstring(ct->params_fmt, pfsize, &plen, attr, strlen(attr)) == -1 || - envbuf_append(params_fmt_out, pfsize, &plen, + envbuf_append(ct->params_fmt, pfsize, &plen, " ", 1) == -1 || - envbuf_append_nstring(params_fmt_out, pfsize, + envbuf_append_nstring(ct->params_fmt, pfsize, &plen, value, strlen(value)) == -1) { free(val); return (-1); } nparams++; - if (strcasecmp(attr, "BOUNDARY") == 0) { - /* RFC 2046 SS5.1.1: at most 70 characters */ - if (strlcpy(boundary_out, value, boundary_outsize) < - boundary_outsize) - *has_boundary_out = 1; - } + if (strcasecmp(attr, "BOUNDARY") == 0 && + strlcpy(ct->boundary, value, sizeof(ct->boundary)) < + sizeof(ct->boundary)) + ct->has_boundary = 1; } free(val); if (nparams > 0) { - if (envbuf_append_str(params_fmt_out, pfsize, &plen, + if (envbuf_append_str(ct->params_fmt, pfsize, &plen, ")") == -1) return (-1); - params_fmt_out[plen] = '\0'; + ct->params_fmt[plen] = '\0'; return (0); } - if (envbuf_append_str(params_fmt_out, pfsize, &plen, "NIL") == -1) + if (envbuf_append_str(ct->params_fmt, pfsize, &plen, "NIL") == -1) return (-1); - params_fmt_out[plen] = '\0'; + ct->params_fmt[plen] = '\0'; return (0); } -/* RFC 2046 SS5.1.1 */ +/* RFC 2046 section 5.1.1 */ int split_multipart(const char *body, size_t bodylen, const char *boundary, size_t *part_starts, size_t *part_ends, int *nparts_out, int maxparts) { - /* "--" + boundary; RFC 2046 SS5.1.1 caps boundary at 70 characters */ + /* "--" + boundary; RFC 2046 section 5.1.1 caps it at 70 characters */ char needle[2 + 70 + 1]; size_t needlelen; size_t pos = 0; @@ -902,12 +871,7 @@ split_multipart(const char *body, size_t bodylen, cons int n = 0; int closed = 0; - if (strlcpy(needle, "--", sizeof(needle)) >= sizeof(needle)) { - log_warnx("session %u: split_multipart: \"--\" truncated, " - "can't happen (2 bytes into a 73-byte buffer)", - session_id); - return (-1); - } + strlcpy(needle, "--", sizeof(needle)); needlelen = strlcat(needle, boundary, sizeof(needle)); if (needlelen >= sizeof(needle)) return (-1); /* boundary too long, non-conformant */ @@ -987,7 +951,7 @@ split_multipart(const char *body, size_t bodylen, cons return (0); } -/* RFC 9051 SS6.4.5 section-part */ +/* RFC 9051 section 6.4.5 section-part */ int parse_section_part(const char *s, int *path, int maxpath) { @@ -1018,7 +982,7 @@ parse_section_part(const char *s, int *path, int maxpa return (-1); s++; if (*s == '\0') - return (-1); /* trailing dot */ + return (-1); } return (n); } @@ -1028,27 +992,22 @@ find_mime_part(int depth, const char *hdr, size_t hdrl size_t bodylen, const int *path, int pathlen, const char **part_out, size_t *partlen_out) { - char type[64], subtype[64]; - char params_fmt[600]; - char boundary[70 + 1]; - int has_boundary; - size_t part_starts[MIME_MAX_PARTS], part_ends[MIME_MAX_PARTS]; - int n, want; - const char *pbuf, *pbody; - size_t plen, phdrend, pbodylen, ih; + struct content_type ct; + const char *pbuf, *pbody; + size_t part_starts[MIME_MAX_PARTS]; + size_t part_ends[MIME_MAX_PARTS]; + size_t plen, phdrend, pbodylen, ih; + int n, want; if (depth > MIME_MAX_DEPTH) return (-1); - params_fmt[0] = '\0'; - if (parse_content_type(hdr, hdrlen, type, sizeof(type), subtype, - sizeof(subtype), params_fmt, sizeof(params_fmt), boundary, - sizeof(boundary), &has_boundary) == -1) + if (parse_content_type(hdr, hdrlen, &ct) == -1) return (-1); - if (strcasecmp(type, "MULTIPART") != 0 || !has_boundary) + if (strcasecmp(ct.type, "MULTIPART") != 0 || !ct.has_boundary) return (-1); - if (split_multipart(body, bodylen, boundary, part_starts, part_ends, - &n, MIME_MAX_PARTS) == -1) + if (split_multipart(body, bodylen, ct.boundary, part_starts, + part_ends, &n, MIME_MAX_PARTS) == -1) return (-1); want = path[0]; @@ -1062,27 +1021,24 @@ find_mime_part(int depth, const char *hdr, size_t hdrl else if (find_header_body_split(pbuf, plen, &phdrend) == -1) return (-1); - params_fmt[0] = '\0'; - if (parse_content_type(pbuf, phdrend, type, sizeof(type), subtype, - sizeof(subtype), params_fmt, sizeof(params_fmt), boundary, - sizeof(boundary), &has_boundary) == -1) + if (parse_content_type(pbuf, phdrend, &ct) == -1) return (-1); pbody = pbuf + phdrend; pbodylen = plen - phdrend; if (pathlen == 1) { /* a leaf or a message: no "combined children" of a multipart */ - if (strcasecmp(type, "MULTIPART") == 0) + if (strcasecmp(ct.type, "MULTIPART") == 0) return (-1); *part_out = pbody; *partlen_out = pbodylen; return (0); } - /* RFC 9051 SS6.4.5.1: a message's parts are numbered within it */ - if (strcasecmp(type, "MESSAGE") == 0 && - (strcasecmp(subtype, "RFC822") == 0 || - strcasecmp(subtype, "GLOBAL") == 0)) { + /* RFC 9051 section 6.4.5.1: a message's parts are numbered within it */ + if (strcasecmp(ct.type, "MESSAGE") == 0 && + (strcasecmp(ct.subtype, "RFC822") == 0 || + strcasecmp(ct.subtype, "GLOBAL") == 0)) { if (pbodylen == 0 || find_header_body_split(pbody, pbodylen, &ih) == -1) return (-1); @@ -1095,28 +1051,22 @@ find_mime_part(int depth, const char *hdr, size_t hdrl path + 1, pathlen - 1, part_out, partlen_out)); } -/* RFC 9051 SS6.4.5.1: a non-multipart message is its own part 1 */ +/* RFC 9051 section 6.4.5.1: a non-multipart message is its own part 1 */ int locate_mime_part(int depth, const char *hdr, size_t hdrlen, const char *body, size_t bodylen, const int *path, int pathlen, const char **part_out, size_t *partlen_out) { - char type[64], subtype[64]; - char params_fmt[600]; - char boundary[70 + 1]; - int has_boundary; - size_t ih; + struct content_type ct; + size_t ih; if (depth > MIME_MAX_DEPTH || pathlen < 1) return (-1); - params_fmt[0] = '\0'; - if (parse_content_type(hdr, hdrlen, type, sizeof(type), subtype, - sizeof(subtype), params_fmt, sizeof(params_fmt), boundary, - sizeof(boundary), &has_boundary) == -1) + if (parse_content_type(hdr, hdrlen, &ct) == -1) return (-1); - if (strcasecmp(type, "MULTIPART") == 0) + if (strcasecmp(ct.type, "MULTIPART") == 0) return (find_mime_part(depth + 1, hdr, hdrlen, body, bodylen, path, pathlen, part_out, partlen_out)); @@ -1127,16 +1077,16 @@ locate_mime_part(int depth, const char *hdr, size_t hd *partlen_out = bodylen; return (0); } - if (strcasecmp(type, "MESSAGE") != 0 || - (strcasecmp(subtype, "RFC822") != 0 && - strcasecmp(subtype, "GLOBAL") != 0) || bodylen == 0 || + if (strcasecmp(ct.type, "MESSAGE") != 0 || + (strcasecmp(ct.subtype, "RFC822") != 0 && + strcasecmp(ct.subtype, "GLOBAL") != 0) || bodylen == 0 || find_header_body_split(body, bodylen, &ih) == -1) return (-1); return (locate_mime_part(depth + 1, body, ih, body + ih, bodylen - ih, path + 1, pathlen - 1, part_out, partlen_out)); } -/* RFC 9051 SS6.4.5 "" applied to a range; past its end is empty */ +/* RFC 9051 section 6.4.5 on a range; past the end is empty */ void partial_range(int has_partial, uint32_t start, uint32_t count, uint64_t *off, uint64_t *len) @@ -1176,7 +1126,7 @@ extract_mime_part(int fd, const char *basename, const return (-1); } - /* RFC 9051 SS6.4.5: a missing part is an empty item */ + /* RFC 9051 section 6.4.5: a missing part is an empty item */ if (locate_mime_part(0, wholebuf, hdrend, wholebuf + hdrend, wholelen - hdrend, path, pathlen, &part, &partlen) == 0) { *off_out = (uint64_t)(part - wholebuf); @@ -1240,8 +1190,8 @@ message_body_range(struct cur_snapshot *snap, int dfd, } if (!found) { - log_warnx("session %u: message %s: no header/body separator " - "found, BODY[TEXT] skipped", session_id, basename); + log_warnx("session %u: message %s: no header end, BODY[TEXT] " + "skipped", session_id, basename); close(fd); return (-1); } @@ -1250,6 +1200,18 @@ message_body_range(struct cur_snapshot *snap, int dfd, return (fd); } +static int +flag_append(char *out, size_t outsize, int *first, const char *flag) +{ + if ((!*first && strlcat(out, " ", outsize) >= outsize) || + strlcat(out, flag, outsize) >= outsize) { + log_warnx("session %u: flag list truncated", session_id); + return (0); + } + *first = 0; + return (1); +} + /* flag letters as in Courier's maildir(5) */ void build_flags_string(const char *maildir_suffix, const char *keywords, @@ -1266,6 +1228,8 @@ build_flags_string(const char *maildir_suffix, const c { 'T', "\\Deleted" }, }; const char *letters; + char *kw, *save; + char kwbuf[512]; size_t i; int first = 1; @@ -1274,57 +1238,23 @@ build_flags_string(const char *maildir_suffix, const c letters = strstr(maildir_suffix, "2,"); letters = (letters != NULL) ? letters + 2 : ""; - for (i = 0; i < sizeof(stdflags) / sizeof(stdflags[0]); i++) { - if (strchr(letters, stdflags[i].letter) == NULL) - continue; - if (!first && strlcat(out, " ", outsize) >= outsize) { - log_warnx("session %u: build_flags_string: out " - "truncated, caller-supplied buffer too small " - "for the flag list; stopping early", session_id); + for (i = 0; i < nitems(stdflags); i++) { + if (strchr(letters, stdflags[i].letter) != NULL && + !flag_append(out, outsize, &first, stdflags[i].flag)) return; - } - if (strlcat(out, stdflags[i].flag, outsize) >= outsize) { - log_warnx("session %u: build_flags_string: out " - "truncated, caller-supplied buffer too small " - "for the flag list; stopping early", session_id); - return; - } - first = 0; } - if (keywords != NULL && keywords[0] != '\0') { - char kwbuf[512]; - char *kw, *save; - - if (strlcpy(kwbuf, keywords, sizeof(kwbuf)) >= sizeof(kwbuf)) { - log_warnx("session %u: build_flags_string: keywords " - "too long for kwbuf, omitting keyword flags " - "rather than guessing at a truncated list", - session_id); + if (keywords == NULL || keywords[0] == '\0') + return; + strlcpy(kwbuf, keywords, sizeof(kwbuf)); + for (kw = strtok_r(kwbuf, ",", &save); kw != NULL; + kw = strtok_r(NULL, ",", &save)) { + if (!flag_append(out, outsize, &first, kw)) return; - } - for (kw = strtok_r(kwbuf, ",", &save); kw != NULL; - kw = strtok_r(NULL, ",", &save)) { - if (!first && strlcat(out, " ", outsize) >= outsize) { - log_warnx("session %u: build_flags_string: " - "out truncated, caller-supplied " - "buffer too small for the flag list; " - "stopping early", session_id); - return; - } - if (strlcat(out, kw, outsize) >= outsize) { - log_warnx("session %u: build_flags_string: " - "out truncated, caller-supplied " - "buffer too small for the flag list; " - "stopping early", session_id); - return; - } - first = 0; - } } } -/* RFC 9051 SS2.3.3: from the basename, not mtime */ +/* RFC 9051 section 2.3.3: from the basename, not mtime */ int64_t parse_maildir_timestamp(const char *basename) { blob - e6ffc62566f5ef03659e82ca925bbb36182680d5 blob + bf61ced9817a15ffeeaaf4c578c6c3ad2bb296c4 --- src/parent.c +++ src/parent.c @@ -7,23 +7,6 @@ * Copyright (c) 2008 Pierre-Yves Ritschard * Copyright (c) 2008 Reyk Floeter * - * This file's boot-time peer-wiring handshake -- setup_peer_send() and - * setup_done_send(), and the IMSG_SETUP_PEER/IMSG_SETUP_DONE message - * names they and imapd.h use -- follows smtpd's setup_peers()/ - * setup_done() (usr.sbin/smtpd/smtpd.c:861-902) and its identically- - * named IMSG_SETUP_PEER/IMSG_SETUP_DONE enumerators (smtpd.h:211-213), - * which no other daemon in the OpenBSD base system defines under those - * names. The functions here are reworked onto imapd's own struct imsgev - * and multi-child bookkeeping rather than copied verbatim, but the - * message protocol and handshake shape are smtpd's; see the inline - * citations at this file's setup_peer_send() and setup_done_send(). - * - * send_keymgr_init()'s TLS key permission check -- fstat(2), then - * st_uid != 0 and st_mode & (S_IRWXU|S_IRWXG|S_IRWXO) & ~0740 -- reuses - * smtpd's ssl_load_key() check (usr.sbin/smtpd/ssl.c:112-140) verbatim - * for that mask and rejection order; only the fixed 0740 bound and the - * imsg delivery around it are this file's own. - * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. @@ -37,6 +20,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include #include #include @@ -163,6 +147,7 @@ static void store_fork_failed(struct open_session *, static int store_child_attach(struct store_child *, struct open_session *); static void store_child_session_gone(struct store_child *); +static void store_child_wire(struct store_child *); static void store_child_parser(struct store_child *); static void store_child_dispatch(int, short, void *); static void store_child_timeout(int, short, void *); @@ -191,8 +176,6 @@ parent_main(const char *conffile, int argc, char *argv int i; struct rlimit rl; - (void)argc; - if (geteuid() != 0) fatalx("parent must start as root (running as uid %u)", (unsigned int)geteuid()); @@ -223,8 +206,7 @@ parent_main(const char *conffile, int argc, char *argv bind_listen_socket(conf->listen_addr, conf->port_implicit_tls, tls_fds); if (n_cleartext == 0 && n_tls == 0) - fatalx("no listener configured: imapd.conf named no usable " - "\"listen on\" address/port"); + fatalx("no usable \"listen on\" address in imapd.conf"); event_init(); @@ -255,10 +237,8 @@ parent_main(const char *conffile, int argc, char *argv /* no setproctitle() here: rc.d matches the parent's command line */ /* proc exec sendfd: every connection forks and fd-passes */ -#ifdef __OpenBSD__ if (pledge("stdio rpath inet proc exec sendfd", NULL) == -1) fatal("pledge"); -#endif event_dispatch(); fatalx("parent: exited event loop"); @@ -274,7 +254,7 @@ exec_self_as(const char *role) nargv[n++] = progpath; nargv[n++] = "-x"; /* execv(3) takes char *const argv[] but never writes through it */ - nargv[n++] = (char *)(uintptr_t)role; + nargv[n++] = (char *)role; for (i = 1; saved_argv[i] != NULL; i++) { if (strcmp(saved_argv[i], "-x") == 0) { /* a trailing -x would read past argv's NULL */ @@ -283,11 +263,10 @@ exec_self_as(const char *role) i++; continue; } - if (n >= (int)(sizeof(nargv) / sizeof(nargv[0])) - 1) { + if (n >= (int)nitems(nargv) - 1) { /* truncating could split an option from its value */ - log_warnx("exec_self_as: argv too long to pass to the " - "%s child, refusing to exec a truncated command " - "line", role); + log_warnx("exec_self_as: argv too long for the %s " + "child", role); _exit(1); } nargv[n++] = saved_argv[i]; @@ -488,25 +467,20 @@ parent_dispatch_child(int fd, short event, void *arg) /* check against sessions the parent knows */ if ((os = open_session_find(req.session_id)) == NULL) { - log_warnx("refusing IMSG_AUTH_CRED for session " - "%u: not a session parent knows is open " - "(never opened, already closed, or evicted " - "by OPEN_SESSION_MAX)", - req.session_id); + log_warnx("IMSG_AUTH_CRED for unknown session " + "%u, refused", req.session_id); break; } /* only this session's own auth-worker may grant it */ if (iev != os->auth_iev) { - log_warnx("refusing IMSG_AUTH_CRED for " - "session %u: not from that session's own " - "auth-worker channel", + log_warnx("IMSG_AUTH_CRED for session %u from " + "the wrong channel, refused", req.session_id); break; } if (os->authenticated) { - log_warnx("refusing IMSG_AUTH_CRED for session " - "%u: already authenticated, refusing " - "duplicate grant", req.session_id); + log_warnx("IMSG_AUTH_CRED for session %u: " + "already authenticated", req.session_id); /* reply: the listener waits with no timeout */ store_fork_failed(os, 0); break; @@ -530,7 +504,6 @@ parent_dispatch_child(int fd, short event, void *arg) imsg_free(&imsg); } imsgev_rearm_read(iev); - (void)fd; } /* reap_child() frees c once SIGCHLD reports the exit */ @@ -609,8 +582,6 @@ accept_resume(int fd, short event, void *arg) { int i; - (void)fd; (void)event; (void)arg; - if (shutting_down) return; for (i = 0; i < n_cleartext; i++) @@ -660,7 +631,7 @@ count_startups_from(const struct sockaddr_storage *ss) return (n); } -/* RFC 9051 SS7.1.5 BYE, on the cleartext port only */ +/* RFC 9051 section 7.1.5 BYE, on the cleartext port only */ static void refuse_connection(int fd, int cleartext) { @@ -680,8 +651,6 @@ parent_accept(int fd, short event, void *arg) int client_fd, flags, implicit_tls, on = 1; unsigned int nstartups, nopen, nsource; - (void)event; - if ((client_fd = accept(fd, (struct sockaddr *)&ss, &sslen)) == -1) { /* else the listen event fires again at once, and spins */ if (errno == EMFILE || errno == ENFILE) { @@ -711,8 +680,7 @@ parent_accept(int fd, short event, void *arg) nsource = count_startups_from(&ss); if (nsource >= gconf->max_startups_per_source) { log_debug("startups per-source: refusing connection " - "(%u unauthenticated already open from its " - "address)", nsource); + "(%u unauthenticated from its address)", nsource); refuse_connection(client_fd, arg == (void *)0); return; } @@ -763,8 +731,8 @@ spawn_connection(int client_fd, int implicit_tls, TAILQ_FOREACH(it, &open_sessions, entry) nopen++; if (nopen >= OPEN_SESSION_MAX) { - log_warnx("refusing connection: %u open sessions already " - "tracked (OPEN_SESSION_MAX)", nopen); + log_warnx("refusing connection: %u sessions open " + "(OPEN_SESSION_MAX)", nopen); close(client_fd); return; } @@ -779,8 +747,8 @@ spawn_connection(int client_fd, int implicit_tls, if ((listener_pid = fork_child_nonfatal(PROC_LISTENER, &new_listener_iev, parent_dispatch_child)) == -1) { - log_warnx("session %u: refusing connection: could not spawn " - "a listener-worker", session_id); + log_warnx("session %u: refusing connection: no listener-worker", + session_id); free(os); close(client_fd); return; @@ -792,10 +760,7 @@ spawn_connection(int client_fd, int implicit_tls, if ((auth_pid = fork_child_nonfatal(PROC_AUTH, &new_auth_iev, parent_dispatch_child)) == -1) { - log_warnx("session %u: no auth-worker available, this " - "session's first AUTHENTICATE will fail instead of the " - "connection itself (listener-worker detects a missing " - "auth peer the same way it detects one dying later)", + log_warnx("session %u: no auth-worker for this connection", session_id); } else { os->auth_pid = auth_pid; @@ -821,8 +786,7 @@ spawn_connection(int client_fd, int implicit_tls, if (imsg_compose(&new_listener_iev->ibuf, IMSG_LISTENER_SESSION_INIT, 0, 0, client_fd, &init, sizeof(init)) == -1) { log_warnx("session %u: imsg_compose " - "IMSG_LISTENER_SESSION_INIT failed, connection lost", - session_id); + "IMSG_LISTENER_SESSION_INIT, connection lost", session_id); close(client_fd); goto fail_workers; } @@ -851,10 +815,11 @@ maildir_path_is_safe(const char *p) if (p == NULL || p[0] == '\0' || p[0] == '/') return (0); for (;;) { - const char *slash = strchr(seg, '/'); - size_t len = slash ? (size_t)(slash - seg) : - strlen(seg); + const char *slash; + size_t len; + slash = strchr(seg, '/'); + len = slash ? (size_t)(slash - seg) : strlen(seg); if (len == 1 && seg[0] == '.') return (0); if (len == 2 && seg[0] == '.' && seg[1] == '.') @@ -909,8 +874,8 @@ parent_handle_store_fork(struct open_session *os, uid_ strcmp(it->maildir, maildir) == 0) { if (it->nsessions >= gconf->account_sessions) { log_info("session %u: refusing login: uid %u " - "already has %u sessions (account " - "sessions)", session_id, (unsigned int)uid, + "has %u sessions (account sessions)", + session_id, (unsigned int)uid, it->nsessions); store_fork_failed(os, 1); return; @@ -969,17 +934,15 @@ parent_handle_store_fork(struct open_session *os, uid_ if (strlcpy(init_payload.spool_root, gconf->spool_root, sizeof(init_payload.spool_root)) >= sizeof(init_payload.spool_root)) { - log_warnx("session %u: spool_root truncated spawning store " - "child, refusing to wire a session to a possibly " - "wrong spool root", session_id); + log_warnx("session %u: spool_root truncated, store not spawned", + session_id); goto fail_kill; } if (strlcpy(init_payload.maildir, maildir, sizeof(init_payload.maildir)) >= sizeof(init_payload.maildir)) { - log_warnx("session %u: maildir truncated spawning store " - "child, refusing to wire a session to a possibly " - "wrong mailbox", session_id); + log_warnx("session %u: maildir truncated, store not spawned", + session_id); goto fail_kill; } init_payload.bodystructure_read_max = gconf->bodystructure_read_max; @@ -1037,14 +1000,32 @@ store_fork_failed(struct open_session *os, int limit) static int store_child_attach(struct store_child *sc, struct open_session *os) { - if (setup_peer_send(&sc->iev, os->listener_iev, IMSG_SETUP_PEER, - "IMSG_SETUP_PEER", os->session_id) == -1) + if (!sc->pending && setup_peer_send(&sc->iev, os->listener_iev, + IMSG_SETUP_PEER, "IMSG_SETUP_PEER", os->session_id) == -1) return (-1); os->store = sc; sc->nsessions++; return (0); } +/* a login is answered OK only once its store is in the maildir */ +static void +store_child_wire(struct store_child *sc) +{ + struct open_session *os; + + TAILQ_FOREACH(os, &open_sessions, entry) { + if (os->store != sc || os->listener_iev == NULL) + continue; + if (setup_peer_send(&sc->iev, os->listener_iev, + IMSG_SETUP_PEER, "IMSG_SETUP_PEER", os->session_id) == 0) + continue; + os->store = NULL; + store_child_session_gone(sc); + store_fork_failed(os, 0); + } +} + /* the parent, not the store child, decides when it is done */ static void store_child_session_gone(struct store_child *sc) @@ -1143,6 +1124,7 @@ store_child_dispatch(int fd, short event, void *arg) evtimer_del(&sc->timeout_ev); sc->pending = 0; imsg_free(&imsg); + store_child_wire(sc); continue; } if (imsg_get_type(&imsg) == IMSG_PARSER_WANT) { @@ -1156,7 +1138,6 @@ store_child_dispatch(int fd, short event, void *arg) imsg_free(&imsg); } imsgev_rearm_read(&sc->iev); - (void)fd; } static void @@ -1164,8 +1145,6 @@ store_child_timeout(int fd, short event, void *arg) { struct store_child *sc = arg; - (void)fd; - (void)event; log_warnx("session %u: store setup timed out after %ds", sc->session_id, STORE_SETUP_TIMEOUT_SEC); store_child_fail(sc); @@ -1286,14 +1265,12 @@ read_file_capped(FILE *fp, char *buf, size_t bufsize, n = fread(buf, 1, bufsize, fp); if (n == bufsize) { if (fgetc(fp) != EOF) { - log_warnx("%s: larger than %zu bytes, refusing to " - "send a truncated %s (TLS will be disabled)", - path, bufsize, what); + log_warnx("%s: over %zu bytes, %s not sent, TLS " + "disabled", path, bufsize, what); return (-1); } } else if (!feof(fp)) { - log_warnx("%s: short read that wasn't a clean EOF, refusing " - "to send a possibly-truncated %s (TLS will be disabled)", + log_warnx("%s: short read, %s not sent, TLS disabled", path, what); return (-1); } @@ -1352,12 +1329,12 @@ send_keymgr_init(struct imsgev *iev, struct openimap_c log_warn("fstat %s", conf->tls_key_file); fclose(fp); } else if (st.st_uid != 0) { - log_warnx("%s: not owned by uid 0, refusing to load " - "(TLS will be disabled)", conf->tls_key_file); + log_warnx("%s: not owned by uid 0, TLS disabled", + conf->tls_key_file); fclose(fp); } else if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~0740) { - log_warnx("%s: insecure permissions, must be at most " - "rwxr----- (TLS will be disabled)", conf->tls_key_file); + log_warnx("%s: insecure permissions, TLS disabled", + conf->tls_key_file); fclose(fp); } else { if (read_file_capped(fp, buf, sizeof(buf), &n, @@ -1389,8 +1366,7 @@ send_auth_init(struct imsgev *iev, struct openimap_con memset(&init, 0, sizeof(init)); if (strlcpy(init.cred_file, conf->cred_file, sizeof(init.cred_file)) >= sizeof(init.cred_file)) { - log_warnx("cred_file truncated sending IMSG_AUTH_INIT, config " - "value too long for the wire struct's field"); + log_warnx("cred_file truncated, IMSG_AUTH_INIT not sent"); return (-1); } @@ -1412,8 +1388,6 @@ sighup_handler(int fd, short event, void *arg) { struct openimap_config newconf; - (void)fd; (void)event; (void)arg; - log_info("SIGHUP: reloading %s", conf_path); memset(&newconf, 0, sizeof(newconf)); @@ -1426,24 +1400,19 @@ sighup_handler(int fd, short event, void *arg) if (strcmp(newconf.listen_addr, gconf->listen_addr) != 0 || newconf.port_cleartext != gconf->port_cleartext || newconf.port_implicit_tls != gconf->port_implicit_tls) { - log_warnx("SIGHUP: %s: \"listen on\" changed but listening " - "sockets cannot be rebound without a restart, still " - "serving %s:%u / %s:%u", conf_path, gconf->listen_addr, - gconf->port_cleartext, gconf->listen_addr, - gconf->port_implicit_tls); + log_warnx("SIGHUP: %s: \"listen on\" changed, takes effect on " + "restart", conf_path); } if (strcmp(newconf.cred_file, gconf->cred_file) != 0) { - log_warnx("SIGHUP: %s: \"credentials\" changed but each " - "auth-worker chroots to the old path's directory, a " - "restart is required for this to take effect", conf_path); + log_warnx("SIGHUP: %s: \"credentials\" changed, takes effect " + "on restart", conf_path); } if (strlen(newconf.spool_root) >= sizeof(gconf->spool_root) || strlen(newconf.tls_cert_file) >= sizeof(gconf->tls_cert_file) || strlen(newconf.tls_key_file) >= sizeof(gconf->tls_key_file)) { - log_warnx("SIGHUP: %s: reloaded value too long for gconf's " - "field, keeping the already-running configuration", - conf_path); + log_warnx("SIGHUP: %s: value too long, keeping the running " + "configuration", conf_path); return; } @@ -1470,9 +1439,8 @@ sighup_handler(int fd, short event, void *arg) /* keymgr holds the key for every connection, so it needs a push */ if (iev_keymgr != NULL) { if (send_keymgr_init(iev_keymgr, gconf) == -1) - log_warnx("SIGHUP: pushing the reloaded certificate " - "and key to keymgr failed; it is still using the " - "previously loaded material"); + log_warnx("SIGHUP: keymgr did not take the new " + "certificate and key, keeping the old"); } else log_warnx("SIGHUP: keymgr is gone, TLS private key not " "reloaded"); @@ -1502,8 +1470,6 @@ sigterm_force(int fd, short event, void *arg) struct store_child *sc; int n = 0; - (void)fd; (void)event; (void)arg; - TAILQ_FOREACH(c, &children, entry) { kill(c->pid, SIGKILL); n++; @@ -1526,8 +1492,6 @@ sigterm_handler(int fd, short event, void *arg) struct timeval tv; int i; - (void)fd; (void)event; (void)arg; - /* a 2nd SIGTERM does not wait, as l2tpd.c:509-512 also does */ if (shutting_down) { log_info("SIGTERM again: not waiting for children"); @@ -1574,7 +1538,6 @@ sigchld_handler(int fd, short event, void *arg) pid_t pid; int status; - (void)fd; (void)event; (void)arg; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) reap_child(pid, status); @@ -1609,29 +1572,15 @@ reap_child(pid_t pid, int status) "shutdown", pid); else if (WIFSIGNALED(status)) log_warnx("keymgr[%d] terminated by " - "signal %d, new TLS handshakes " - "will now fail (already-" - "negotiated TLS sessions are " - "unaffected); run \"rcctl " - "restart imapd\" to recover", - pid, WTERMSIG(status)); + "signal %d, TLS handshakes will " + "fail", pid, WTERMSIG(status)); else if (WIFEXITED(status)) - log_warnx("keymgr[%d] exited " - "unexpectedly, status %d, new " - "TLS handshakes will now fail " - "(already-negotiated TLS " - "sessions are unaffected); run " - "\"rcctl restart imapd\" to " - "recover", pid, - WEXITSTATUS(status)); + log_warnx("keymgr[%d] exited, status " + "%d, TLS handshakes will fail", + pid, WEXITSTATUS(status)); else - log_warnx("keymgr[%d] exited " - "unexpectedly, new TLS " - "handshakes will now fail " - "(already-negotiated TLS " - "sessions are unaffected); run " - "\"rcctl restart imapd\" to " - "recover", pid); + log_warnx("keymgr[%d] exited, TLS " + "handshakes will fail", pid); iev_keymgr = NULL; free(c); return; @@ -1678,10 +1627,8 @@ reap_child(pid_t pid, int status) free(os); } else { log_debug("session %u: auth-worker[%d] " - "exited (status %d); this session's " - "listener-worker will detect this on " - "its own auth channel", os->session_id, - pid, status); + "exited, status %d", + os->session_id, pid, status); os->auth_iev = NULL; os->auth_pid = 0; } blob - cfad15bf92d2bb0d9cfdf97b9ce1d6cd053156ae blob + 53bc7f3ae111d129938cd551bfeb10c68a178044 --- src/parse.y +++ src/parse.y @@ -7,14 +7,6 @@ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. * Copyright (c) 2001 Theo de Raadt. All rights reserved. * - * The parser skeleton below (lgetc()/lungetc()/findeol(), the hand- - * written yylex() built on top of them, pushfile()/popfile(), and - * symset()/symget()) follows the structure common to ripd's, smtpd's, - * and httpd's own parse.y in the OpenBSD base system, all of which - * carry this same four-name copyright chain at their root. This file's - * grammar and domain-specific rules are original; the above four names - * are carried forward for the shared parser-skeleton lineage only. - * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. @@ -772,7 +764,7 @@ config_load(const char *path, struct openimap_config * conf = xconf; memset(conf, 0, sizeof(*conf)); - (void)strlcpy(conf->listen_addr, "0.0.0.0", sizeof(conf->listen_addr)); + (void)strlcpy(conf->listen_addr, "*", sizeof(conf->listen_addr)); conf->port_cleartext = 143; conf->port_implicit_tls = 993; conf->idle_poll_secs = IDLE_POLL_DEFAULT; blob - 89cadd6d7d8b8435bffef0913ac0d978bd4afb4e blob + 1c15f07121604a242b854c6e07d35ec683493006 --- src/parser.c +++ src/parser.c @@ -122,10 +122,8 @@ parser_main(void) imsgev_init(&iev_store, store_fd, parser_dispatch_store, NULL); /* no "rpath": the only bytes arrive on passed descriptors */ -#ifdef __OpenBSD__ if (pledge("stdio recvfd", NULL) == -1) fatal("pledge"); -#endif event_dispatch(); fatalx("parser: exited event loop"); @@ -266,8 +264,6 @@ parser_dispatch_store(int fd, short event, void *arg) struct imsg imsg; ssize_t n; - (void)fd; - if (event & EV_WRITE) { if (imsgbuf_write(&iev->ibuf) == -1) fatal("imsgbuf_write"); blob - fa25214c72a54d8338fdec64ec0a4114adcfd77c blob + 8b3ea7fcb79f6b83ec5eef538e392579ff15c0b7 --- src/search_cmd.c +++ src/search_cmd.c @@ -16,7 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - +#include #include #include #include @@ -87,7 +87,6 @@ struct search_parse_ctx { char pool[SEARCH_OPERANDS_MAX]; }; - static int search_push(struct search_parse_ctx *ctx, const struct search_node *node, const char **errmsg) @@ -100,7 +99,7 @@ search_push(struct search_parse_ctx *ctx, const struct return (0); } -/* RFC 9051 SS9 sequence-set, pushed as ORed ranges */ +/* RFC 9051 section 9 sequence-set, pushed as ORed ranges */ static int parse_search_seqset(char **pp, struct search_parse_ctx *ctx, int op, const char **errmsg) @@ -157,7 +156,7 @@ parse_search_seqset(char **pp, struct search_parse_ctx return (0); } -/* RFC 9051 SS6.4.4 search-key: 0 ok, -1 BAD, -2 NO */ +/* RFC 9051 section 6.4.4 search-key: 0 ok, -1 BAD, -2 NO */ int parse_search_key(char **pp, struct search_parse_ctx *ctx, const char **errmsg) { @@ -174,7 +173,6 @@ parse_search_key(char **pp, struct search_parse_ctx *c return (rc); } - static int read_search_token(char **pp, char *buf, size_t bufsize, const char *errtext, const char **errmsg) @@ -197,7 +195,7 @@ read_search_token(char **pp, char *buf, size_t bufsize return (0); } -/* RFC 9051 SS9 date, as the UTC midnight that begins it */ +/* RFC 9051 section 9 date, as the UTC midnight that begins it */ static int read_search_date(char **pp, int64_t *out, const char **errmsg) { @@ -208,9 +206,10 @@ read_search_date(char **pp, int64_t *out, const char * p++; if (*p == '"') { const char *start = p + 1; - char *end = strchr(start, '"'); + char *end; size_t len; + end = strchr(start, '"'); if (end == NULL) { *errmsg = "unterminated date string"; return (-1); @@ -245,7 +244,7 @@ search_pool_add(struct search_parse_ctx *ctx, char c) ctx->operand_len++; } -/* RFC 9051 SS9 astring operand */ +/* RFC 9051 section 9 astring operand */ static int read_search_astring(char **pp, struct search_parse_ctx *ctx, uint32_t *offp, uint32_t *lenp, const char **errmsg) @@ -269,7 +268,7 @@ read_search_astring(char **pp, struct search_parse_ctx *errmsg = "unterminated quoted string"; return (-1); } - /* RFC 9051 SS9: quoted-specials are the only escapes */ + /* RFC 9051 section 9: only quoted-specials escape */ if (*p == '\\' && *++p != '"' && *p != '\\') { *errmsg = "malformed quoted string"; return (-1); @@ -278,7 +277,7 @@ read_search_astring(char **pp, struct search_parse_ctx } p++; } else { - /* RFC 9051 SS9 ASTRING-CHAR */ + /* RFC 9051 section 9 ASTRING-CHAR */ for (; *p != '\0' && *p != ' ' && *p != ')'; p++) { if ((unsigned char)*p <= 0x1f || (unsigned char)*p >= 0x7f || @@ -300,13 +299,136 @@ read_search_astring(char **pp, struct search_parse_ctx return (0); } +/* what follows a search key, before it is pushed as one node */ +enum search_arg { + SARG_NONE, + SARG_KEYWORD, + SARG_DATE, + SARG_NUMBER, + SARG_MODSEQ, + SARG_STRING, + SARG_HEADER, + SARG_UIDSET, + SARG_KEY, + SARG_KEY2 +}; + +static const struct search_keyword { + const char *name; + int op; + enum search_arg arg; +} search_keywords[] = { + { "ALL", SEARCH_OP_ALL, SARG_NONE }, + { "ANSWERED", SEARCH_OP_ANSWERED, SARG_NONE }, + { "UNANSWERED", SEARCH_OP_UNANSWERED, SARG_NONE }, + { "DELETED", SEARCH_OP_DELETED, SARG_NONE }, + { "UNDELETED", SEARCH_OP_UNDELETED, SARG_NONE }, + { "DRAFT", SEARCH_OP_DRAFT, SARG_NONE }, + { "UNDRAFT", SEARCH_OP_UNDRAFT, SARG_NONE }, + { "FLAGGED", SEARCH_OP_FLAGGED, SARG_NONE }, + { "UNFLAGGED", SEARCH_OP_UNFLAGGED, SARG_NONE }, + { "SEEN", SEARCH_OP_SEEN, SARG_NONE }, + { "UNSEEN", SEARCH_OP_UNSEEN, SARG_NONE }, + { "KEYWORD", SEARCH_OP_KEYWORD, SARG_KEYWORD }, + { "UNKEYWORD", SEARCH_OP_UNKEYWORD, SARG_KEYWORD }, + { "BEFORE", SEARCH_OP_BEFORE, SARG_DATE }, + { "ON", SEARCH_OP_ON, SARG_DATE }, + { "SINCE", SEARCH_OP_SINCE, SARG_DATE }, + { "LARGER", SEARCH_OP_LARGER, SARG_NUMBER }, + { "SMALLER", SEARCH_OP_SMALLER, SARG_NUMBER }, + { "UID", SEARCH_OP_UIDSET, SARG_UIDSET }, + { "MODSEQ", SEARCH_OP_MODSEQ, SARG_MODSEQ }, + { "NOT", SEARCH_OP_NOT, SARG_KEY }, + { "OR", SEARCH_OP_OR, SARG_KEY2 }, + /* RFC 9051 section 6.4.4 content keys, answered from the message */ + { "SENTBEFORE", SEARCH_OP_SENTBEFORE, SARG_DATE }, + { "SENTON", SEARCH_OP_SENTON, SARG_DATE }, + { "SENTSINCE", SEARCH_OP_SENTSINCE, SARG_DATE }, + { "BCC", SEARCH_OP_BCC, SARG_STRING }, + { "BODY", SEARCH_OP_BODY, SARG_STRING }, + { "CC", SEARCH_OP_CC, SARG_STRING }, + { "FROM", SEARCH_OP_FROM, SARG_STRING }, + { "HEADER", SEARCH_OP_HEADER, SARG_HEADER }, + { "SUBJECT", SEARCH_OP_SUBJECT, SARG_STRING }, + { "TEXT", SEARCH_OP_TEXT, SARG_STRING }, + { "TO", SEARCH_OP_TO, SARG_STRING }, +}; + +static int +read_search_number(char **pp, int64_t *out, int modseq, const char **errmsg) +{ + const char *errstr; + char numbuf[24]; + + if (read_search_token(pp, numbuf, sizeof(numbuf), modseq ? + "missing or malformed MODSEQ value" : + "missing or malformed octet count", errmsg) == -1) + return (-1); + errstr = "not a number"; + if (numbuf[0] >= '0' && numbuf[0] <= '9') + *out = strtonum(numbuf, 0, INT64_MAX, &errstr); + if (errstr != NULL) { + *errmsg = modseq ? "malformed MODSEQ value" : + "malformed octet count"; + return (-1); + } + return (0); +} + +/* RFC 7162 section 3.1.5: MODSEQ [entry-name type-req]; both are skipped */ +static int +skip_modseq_entry(char **pp, const char **errmsg) +{ + char *p = *pp, *end; + + while (*p == ' ') + p++; + if (*p == '\0' || isdigit((unsigned char)*p)) { + *pp = p; + return (0); + } + if (*p == '"') { + if ((end = strchr(p + 1, '"')) == NULL) { + *errmsg = "unterminated MODSEQ entry-name"; + return (-1); + } + p = end + 1; + } else { + while (*p != '\0' && *p != ' ' && *p != ')') + p++; + } + while (*p == ' ') + p++; + + if (strncasecmp(p, "priv", 4) == 0 && + (p[4] == ' ' || p[4] == '\0' || p[4] == ')')) + p += 4; + else if (strncasecmp(p, "shared", 6) == 0 && + (p[6] == ' ' || p[6] == '\0' || p[6] == ')')) + p += 6; + else if (strncasecmp(p, "all", 3) == 0 && + (p[3] == ' ' || p[3] == '\0' || p[3] == ')')) + p += 3; + else { + *errmsg = "expected priv/shared/all after MODSEQ entry-name"; + return (-1); + } + while (*p == ' ') + p++; + *pp = p; + return (0); +} + int parse_search_key_inner(char **pp, struct search_parse_ctx *ctx, const char **errmsg) { - char *p = *pp; - char word[32]; - size_t wlen; + const struct search_keyword *kw; + struct search_node node; + char *p = *pp, *start; + char word[32]; + size_t wlen, i; + int rc; while (*p == ' ') p++; @@ -317,11 +439,8 @@ parse_search_key_inner(char **pp, struct search_parse_ } if (*p == '(') { - int rc; - p++; - rc = parse_search_key_list(&p, ctx, errmsg, 1); - if (rc != 0) + if ((rc = parse_search_key_list(&p, ctx, errmsg, 1)) != 0) return (rc); while (*p == ' ') p++; @@ -342,326 +461,94 @@ parse_search_key_inner(char **pp, struct search_parse_ return (0); } - { - char *start = p; + start = p; + while (*p != '\0' && *p != ' ' && *p != ')') + p++; + wlen = (size_t)(p - start); + if (wlen == 0 || wlen >= sizeof(word)) { + *errmsg = "unknown search key"; + return (-1); + } + memcpy(word, start, wlen); + word[wlen] = '\0'; - while (*p != '\0' && *p != ' ' && *p != ')') - p++; - wlen = (size_t)(p - start); - if (wlen == 0 || wlen >= sizeof(word)) { - *errmsg = "unknown search key"; - return (-1); + kw = NULL; + for (i = 0; i < nitems(search_keywords); i++) { + if (strcasecmp(word, search_keywords[i].name) == 0) { + kw = &search_keywords[i]; + break; } - memcpy(word, start, wlen); - word[wlen] = '\0'; } - - if (strcasecmp(word, "ALL") == 0) { - struct search_node node; - - memset(&node, 0, sizeof(node)); - node.op = SEARCH_OP_ALL; - if (search_push(ctx, &node, errmsg) == -1) - return (-1); - *pp = p; - return (0); + if (kw == NULL) { + *errmsg = "unknown search key"; + return (-1); } - { - static const struct { - const char *name; - int op; - } boolkeys[] = { - { "ANSWERED", SEARCH_OP_ANSWERED }, - { "UNANSWERED", SEARCH_OP_UNANSWERED }, - { "DELETED", SEARCH_OP_DELETED }, - { "UNDELETED", SEARCH_OP_UNDELETED }, - { "DRAFT", SEARCH_OP_DRAFT }, - { "UNDRAFT", SEARCH_OP_UNDRAFT }, - { "FLAGGED", SEARCH_OP_FLAGGED }, - { "UNFLAGGED", SEARCH_OP_UNFLAGGED }, - { "SEEN", SEARCH_OP_SEEN }, - { "UNSEEN", SEARCH_OP_UNSEEN }, - }; - size_t i; - - for (i = 0; i < sizeof(boolkeys) / sizeof(boolkeys[0]); i++) { - struct search_node node; - - if (strcasecmp(word, boolkeys[i].name) != 0) - continue; - memset(&node, 0, sizeof(node)); - node.op = boolkeys[i].op; - if (search_push(ctx, &node, errmsg) == -1) - return (-1); - *pp = p; - return (0); - } - } - - if (strcasecmp(word, "KEYWORD") == 0 || - strcasecmp(word, "UNKEYWORD") == 0) { - struct search_node node; - - memset(&node, 0, sizeof(node)); - node.op = (strcasecmp(word, "KEYWORD") == 0) ? - SEARCH_OP_KEYWORD : SEARCH_OP_UNKEYWORD; - + memset(&node, 0, sizeof(node)); + node.op = kw->op; + switch (kw->arg) { + case SARG_NONE: + break; + case SARG_KEYWORD: if (read_search_token(&p, node.keyword, sizeof(node.keyword), "missing or too-long KEYWORD/UNKEYWORD argument", errmsg) == -1) return (-1); - - if (search_push(ctx, &node, errmsg) == -1) - return (-1); - *pp = p; - return (0); - } - - if (strcasecmp(word, "BEFORE") == 0 || strcasecmp(word, "ON") == 0 || - strcasecmp(word, "SINCE") == 0) { - struct search_node node; - - memset(&node, 0, sizeof(node)); - if (strcasecmp(word, "BEFORE") == 0) - node.op = SEARCH_OP_BEFORE; - else if (strcasecmp(word, "ON") == 0) - node.op = SEARCH_OP_ON; - else - node.op = SEARCH_OP_SINCE; - + break; + case SARG_DATE: if (read_search_date(&p, &node.num, errmsg) == -1) return (-1); - - if (search_push(ctx, &node, errmsg) == -1) + break; + case SARG_NUMBER: + if (read_search_number(&p, &node.num, 0, errmsg) == -1) return (-1); - *pp = p; - return (0); - } - - if (strcasecmp(word, "LARGER") == 0 || - strcasecmp(word, "SMALLER") == 0) { - struct search_node node; - char numbuf[24]; - char *numend; - unsigned long long v; - - memset(&node, 0, sizeof(node)); - node.op = (strcasecmp(word, "LARGER") == 0) ? - SEARCH_OP_LARGER : SEARCH_OP_SMALLER; - - if (read_search_token(&p, numbuf, sizeof(numbuf), - "missing or malformed octet count", errmsg) == -1) + break; + case SARG_MODSEQ: + if (skip_modseq_entry(&p, errmsg) == -1 || + read_search_number(&p, &node.num, 1, errmsg) == -1) return (-1); - - errno = 0; - v = strtoull(numbuf, &numend, 10); - if (*numend != '\0' || errno == ERANGE) { - *errmsg = "malformed octet count"; - return (-1); - } - /* node.num is signed: refuse above INT64_MAX */ - if (v > (unsigned long long)INT64_MAX) { - *errmsg = "octet count out of range"; - return (-1); - } - node.num = (int64_t)v; - - if (search_push(ctx, &node, errmsg) == -1) - return (-1); - *pp = p; - return (0); - } - - if (strcasecmp(word, "UID") == 0) { - while (*p == ' ') - p++; - if (parse_search_seqset(&p, ctx, SEARCH_OP_UIDSET, errmsg) == - -1) - return (-1); - *pp = p; - return (0); - } - - if (strcasecmp(word, "MODSEQ") == 0) { - struct search_node node; - char numbuf[24]; - char *numend; - unsigned long long v; - - memset(&node, 0, sizeof(node)); - node.op = SEARCH_OP_MODSEQ; - - while (*p == ' ') - p++; - - /* RFC 7162 SS3.1.5 entry-name and type-req are skipped */ - if (*p != '\0' && !isdigit((unsigned char)*p)) { - if (*p == '"') { - char *end = strchr(p + 1, '"'); - - if (end == NULL) { - *errmsg = "unterminated MODSEQ " - "entry-name"; - return (-1); - } - p = end + 1; - } else { - while (*p != '\0' && *p != ' ' && *p != ')') - p++; - } - while (*p == ' ') - p++; - - if (strncasecmp(p, "priv", 4) == 0 && - (p[4] == ' ' || p[4] == '\0' || p[4] == ')')) - p += 4; - else if (strncasecmp(p, "shared", 6) == 0 && - (p[6] == ' ' || p[6] == '\0' || p[6] == ')')) - p += 6; - else if (strncasecmp(p, "all", 3) == 0 && - (p[3] == ' ' || p[3] == '\0' || p[3] == ')')) - p += 3; - else { - *errmsg = "expected priv/shared/all after " - "MODSEQ entry-name"; - return (-1); - } - while (*p == ' ') - p++; - } - - if (read_search_token(&p, numbuf, sizeof(numbuf), - "missing or malformed MODSEQ value", errmsg) == -1) - return (-1); - - errno = 0; - v = strtoull(numbuf, &numend, 10); - if (*numend != '\0' || errno == ERANGE) { - *errmsg = "malformed MODSEQ value"; - return (-1); - } - /* same signed-cast trap as LARGER/SMALLER above */ - if (v > (unsigned long long)INT64_MAX) { - *errmsg = "MODSEQ value out of range"; - return (-1); - } - node.num = (int64_t)v; - - if (search_push(ctx, &node, errmsg) == -1) - return (-1); ctx->uses_modseq = 1; - *pp = p; - return (0); - } - - if (strcasecmp(word, "NOT") == 0) { - struct search_node node; - int rc; - - rc = parse_search_key(&p, ctx, errmsg); - if (rc != 0) - return (rc); - - memset(&node, 0, sizeof(node)); - node.op = SEARCH_OP_NOT; - if (search_push(ctx, &node, errmsg) == -1) + break; + case SARG_HEADER: + case SARG_STRING: + if (kw->arg == SARG_HEADER && read_search_astring(&p, ctx, + &node.name_off, &node.name_len, errmsg) == -1) return (-1); + if (read_search_astring(&p, ctx, &node.str_off, &node.str_len, + errmsg) == -1) + return (-1); + break; + case SARG_UIDSET: + while (*p == ' ') + p++; + if (parse_search_seqset(&p, ctx, SEARCH_OP_UIDSET, errmsg) == + -1) + return (-1); *pp = p; - return (0); - } - - if (strcasecmp(word, "OR") == 0) { - struct search_node node; - int rc; - - rc = parse_search_key(&p, ctx, errmsg); - if (rc != 0) + return (0); /* the set is its own node */ + case SARG_KEY2: + if ((rc = parse_search_key(&p, ctx, errmsg)) != 0) return (rc); - rc = parse_search_key(&p, ctx, errmsg); - if (rc != 0) + /* FALLTHROUGH */ + case SARG_KEY: + if ((rc = parse_search_key(&p, ctx, errmsg)) != 0) return (rc); - - memset(&node, 0, sizeof(node)); - node.op = SEARCH_OP_OR; - if (search_push(ctx, &node, errmsg) == -1) - return (-1); - *pp = p; - return (0); + break; } - /* RFC 9051 SS6.4.4 content keys, answered from the message */ - if (strcasecmp(word, "SENTBEFORE") == 0 || - strcasecmp(word, "SENTON") == 0 || - strcasecmp(word, "SENTSINCE") == 0) { - struct search_node node; - - memset(&node, 0, sizeof(node)); - if (strcasecmp(word, "SENTBEFORE") == 0) - node.op = SEARCH_OP_SENTBEFORE; - else if (strcasecmp(word, "SENTON") == 0) - node.op = SEARCH_OP_SENTON; - else - node.op = SEARCH_OP_SENTSINCE; - - if (read_search_date(&p, &node.num, errmsg) == -1) - return (-1); - - if (search_push(ctx, &node, errmsg) == -1) - return (-1); - *pp = p; - return (0); - } - - { - static const struct { - const char *name; - int op; - } string_keys[] = { - { "BCC", SEARCH_OP_BCC }, - { "BODY", SEARCH_OP_BODY }, - { "CC", SEARCH_OP_CC }, - { "FROM", SEARCH_OP_FROM }, - { "HEADER", SEARCH_OP_HEADER }, - { "SUBJECT", SEARCH_OP_SUBJECT }, - { "TEXT", SEARCH_OP_TEXT }, - { "TO", SEARCH_OP_TO }, - }; - struct search_node node; - size_t i; - - for (i = 0; i < sizeof(string_keys) / sizeof(string_keys[0]); - i++) { - if (strcasecmp(word, string_keys[i].name) != 0) - continue; - memset(&node, 0, sizeof(node)); - node.op = string_keys[i].op; - /* HEADER's field-name is an astring too */ - if (node.op == SEARCH_OP_HEADER && - read_search_astring(&p, ctx, &node.name_off, - &node.name_len, errmsg) == -1) - return (-1); - if (read_search_astring(&p, ctx, &node.str_off, - &node.str_len, errmsg) == -1) - return (-1); - - if (search_push(ctx, &node, errmsg) == -1) - return (-1); - *pp = p; - return (0); - } - } - - *errmsg = "unknown search key"; - return (-1); + if (search_push(ctx, &node, errmsg) == -1) + return (-1); + *pp = p; + return (0); } -/* RFC 9051 SS6.4.4 search-key list, ANDed */ +/* RFC 9051 section 6.4.4 search-key list, ANDed */ int parse_search_key_list(char **pp, struct search_parse_ctx *ctx, const char **errmsg, int in_parens) { - int rc; + struct search_node combine; + int rc; rc = parse_search_key(pp, ctx, errmsg); if (rc != 0) @@ -696,14 +583,10 @@ parse_search_key_list(char **pp, struct search_parse_c if (rc != 0) return (rc); - { - struct search_node combine; - - memset(&combine, 0, sizeof(combine)); - combine.op = SEARCH_OP_AND; - if (search_push(ctx, &combine, errmsg) == -1) - return (-1); - } + memset(&combine, 0, sizeof(combine)); + combine.op = SEARCH_OP_AND; + if (search_push(ctx, &combine, errmsg) == -1) + return (-1); } } @@ -719,7 +602,7 @@ search_program_parse(char *args, struct search_node *n memset(&ctx, 0, sizeof(ctx)); rc = parse_search_key_list(&p, &ctx, &errmsg, 0); - /* RFC 9051 SS7.1: LIMIT, an implementation limit was reached */ + /* RFC 9051 section 7.1: LIMIT, an implementation limit was reached */ if (rc == 0 && ctx.operand_len > SEARCH_OPERANDS_MAX) { errmsg = "[LIMIT] search strings exceed 4096 octets in all"; rc = -2; @@ -741,7 +624,7 @@ search_program_parse(char *args, struct search_node *n return (0); } -/* RFC 9051 SS6.4.4 search-return-opts */ +/* RFC 9051 section 6.4.4 search-return-opts */ int parse_search_return_opts(char **pp, uint32_t *opts_out, const char **errmsg) { @@ -800,7 +683,7 @@ parse_search_return_opts(char **pp, uint32_t *opts_out } } -/* RFC 9051 SS9 charset: atom / quoted */ +/* RFC 9051 section 9 charset: atom / quoted */ static int read_search_charset(char **pp, char *out, size_t outsize) { @@ -840,29 +723,31 @@ read_search_charset(char **pp, char *out, size_t outsi static void search_dispatch_finish(struct session *, const struct search_parse_result *, struct search_node *); -/* RFC 9051 SS6.4.4.1: a SAVE answered NO here empties "$" too */ +/* RFC 9051 section 6.4.4.1: a SAVE answered NO here empties "$" too */ static void search_saved_clear(struct session *s, uint32_t opts) { if ((opts & SEARCH_RETURN_SAVE) && s->store_iev != NULL) - (void)send_mbox_request(s, IMSG_MBOX_SAVED_CLEAR, "SEARCH", + (void)send_mbox_request(s, IMSG_MBOX_SAVED_CLEAR, "IMSG_MBOX_SAVED_CLEAR", NULL, 0, NULL, 0, 0); } -/* RFC 9051 SS6.4.4 SEARCH; US-ASCII and UTF-8 only */ +/* RFC 9051 section 6.4.4 SEARCH; US-ASCII and UTF-8 only */ int cmd_search(struct session *s, const char *tag, char *args) { - return search_dispatch(s, tag, args, 0); + return (search_dispatch(s, tag, args, 0)); } int search_dispatch(struct session *s, const char *tag, char *args, int by_uid) { + struct search_parse_result res; + struct search_node nodes[SEARCH_PROGRAM_MAX_NODES]; char *p; - uint32_t return_opts = 0; - int rc; const char *errmsg; + uint32_t return_opts = 0; + int rc; if (args == NULL) { session_reply(s, tag, "BAD", "SEARCH requires search criteria"); @@ -893,7 +778,8 @@ search_dispatch(struct session *s, const char *tag, ch } if (return_opts == 0) - return_opts = SEARCH_RETURN_ALL; /* RFC 9051 SS6.4.4 */ + /* RFC 9051 section 6.4.4 */ + return_opts = SEARCH_RETURN_ALL; if (strncasecmp(p, "CHARSET", 7) == 0 && (p[7] == ' ' || p[7] == '\0')) { @@ -910,7 +796,7 @@ search_dispatch(struct session *s, const char *tag, ch if (strcasecmp(charset, "US-ASCII") != 0 && strcasecmp(charset, "UTF-8") != 0) { search_saved_clear(s, return_opts); - /* RFC 9051 SS9 puts the charset list in parens */ + /* RFC 9051 section 9 puts the charset list in parens */ session_reply(s, tag, "NO", "[BADCHARSET (US-ASCII UTF-8)] unsupported " "CHARSET"); @@ -933,35 +819,27 @@ search_dispatch(struct session *s, const char *tag, ch return (1); } - { - struct search_parse_result res; - struct search_node nodes[SEARCH_PROGRAM_MAX_NODES]; + memset(&res, 0, sizeof(res)); + res.rc = search_program_parse(p, nodes, &res.nnodes, &res.uses_modseq, + res.pool, &res.poollen, res.errmsg, sizeof(res.errmsg)); + if (res.rc == -1 && strcmp(res.errmsg, literal_wanted) == 0) + return (session_arg_error(s, tag, literal_wanted)); - memset(&res, 0, sizeof(res)); - res.rc = search_program_parse(p, nodes, &res.nnodes, - &res.uses_modseq, res.pool, &res.poollen, res.errmsg, - sizeof(res.errmsg)); - if (res.rc == -1 && strcmp(res.errmsg, literal_wanted) == 0) - return (session_arg_error(s, tag, literal_wanted)); + free(s->search_matches); + s->search_matches = NULL; + s->search_nmatches = 0; + s->search_matches_cap = 0; + s->search_alloc_failed = 0; + s->search_return_opts = return_opts; + s->cmd_by_uid = by_uid; - free(s->search_matches); - s->search_matches = NULL; - s->search_nmatches = 0; - s->search_matches_cap = 0; - s->search_alloc_failed = 0; - s->search_return_opts = return_opts; - s->cmd_by_uid = by_uid; - - if (strlcpy(s->pending_tag, tag, sizeof(s->pending_tag)) >= - sizeof(s->pending_tag)) { - search_saved_clear(s, return_opts); - session_reply(s, tag, "NO", - "[SERVERBUG] internal error"); - return (1); - } - search_dispatch_finish(s, &res, - res.nnodes > 0 ? nodes : NULL); + if (strlcpy(s->pending_tag, tag, sizeof(s->pending_tag)) >= + sizeof(s->pending_tag)) { + search_saved_clear(s, return_opts); + session_reply(s, tag, "NO", "[SERVERBUG] internal error"); + return (1); } + search_dispatch_finish(s, &res, res.nnodes > 0 ? nodes : NULL); return (1); } @@ -970,6 +848,8 @@ static void search_dispatch_finish(struct session *s, const struct search_parse_result *res, struct search_node *nodes) { + struct imsg_mbox_search req; + if (res->rc == -1) { session_reply(s, s->pending_tag, "BAD", res->errmsg); s->state = SESSION_SELECTED; @@ -985,30 +865,25 @@ search_dispatch_finish(struct session *s, s->search_used_modseq = res->uses_modseq; s->search_max_modseq = 0; - /* RFC 7162 SS3.1: MODSEQ enables CONDSTORE */ + /* RFC 7162 section 3.1: MODSEQ enables CONDSTORE */ if (res->uses_modseq) session_condstore_enable(s); s->state = SESSION_SEARCHING; - { - struct imsg_mbox_search req; + memset(&req, 0, sizeof(req)); + req.nnodes = res->nnodes; + req.poollen = res->poollen; + req.return_opts = s->search_return_opts; + memcpy(req.pool, res->pool, res->poollen); - memset(&req, 0, sizeof(req)); - req.nnodes = res->nnodes; - req.poollen = res->poollen; - req.return_opts = s->search_return_opts; - memcpy(req.pool, res->pool, res->poollen); - - if (!send_mbox_request(s, IMSG_MBOX_SEARCH, "SEARCH", - "IMSG_MBOX_SEARCH", &req, sizeof(req), nodes, res->nnodes, - sizeof(struct search_node))) { - search_saved_clear(s, s->search_return_opts); - session_reply(s, s->pending_tag, "NO", - "[SERVERBUG] internal error"); - s->state = SESSION_SELECTED; - return; - } + if (send_mbox_request(s, IMSG_MBOX_SEARCH, + "IMSG_MBOX_SEARCH", &req, sizeof(req), nodes, res->nnodes, + sizeof(struct search_node)) == -1) { + search_saved_clear(s, s->search_return_opts); + session_reply(s, s->pending_tag, "NO", + "[SERVERBUG] internal error"); + s->state = SESSION_SELECTED; } } @@ -1022,9 +897,9 @@ session_handle_mbox_search_match(struct session *s, if (s->search_nmatches == s->search_matches_cap) { uint32_t newcap = s->search_matches_cap ? s->search_matches_cap * 2 : 32; - uint32_t *n = reallocarray(s->search_matches, newcap, - sizeof(uint32_t)); + uint32_t *n; + n = reallocarray(s->search_matches, newcap, sizeof(uint32_t)); if (n == NULL) { log_warn("session %u: realloc SEARCH match array", s->id); @@ -1035,11 +910,11 @@ session_handle_mbox_search_match(struct session *s, s->search_matches_cap = newcap; } - /* RFC 9051 SS6.4.9: UID SEARCH reports UIDs */ + /* RFC 9051 section 6.4.9: UID SEARCH reports UIDs */ s->search_matches[s->search_nmatches++] = s->cmd_by_uid ? m->uid : m->seqno; - /* RFC 7162 SS3.1.6 */ + /* RFC 7162 section 3.1.6 */ if (m->modseq > s->search_max_modseq) s->search_max_modseq = m->modseq; } @@ -1047,7 +922,7 @@ session_handle_mbox_search_match(struct session *s, #define SEARCH_RESP_PREFIX_MAX 256 #define SEARCH_ALL_PER_MATCH 11 /* "4294967295" + one separator */ -/* RFC 9051 SS7.3.4 ESEARCH response */ +/* RFC 9051 section 7.3.4 ESEARCH response */ void session_finish_search(struct session *s, struct imsg_mbox_result *res) { @@ -1072,7 +947,7 @@ session_finish_search(struct session *s, struct imsg_m } if (res->error != MBOX_OP_OK || s->search_alloc_failed) goto fail; - /* RFC 9051 SS6.4.4: SAVE alone suppresses ESEARCH */ + /* RFC 9051 section 6.4.4: SAVE alone suppresses ESEARCH */ if (s->search_return_opts == SEARCH_RETURN_SAVE) goto done; @@ -1091,7 +966,7 @@ session_finish_search(struct session *s, struct imsg_m } len = (size_t)n; - /* RFC 9051 SS9: "UID" after correlator, before MIN/MAX/ALL/MODSEQ */ + /* RFC 9051 section 9: "UID" after the correlator, before the others */ if (s->cmd_by_uid && len < bufsize) len += (size_t)snprintf(buf + len, bufsize - len, " UID"); @@ -1116,11 +991,9 @@ session_finish_search(struct session *s, struct imsg_m s->search_nmatches, &truncated); len += listlen; if (truncated) - log_warnx("session %u: SEARCH ALL response " - "truncated at %zu bytes (%u total " - "matches) -- can't happen, bufsize is " - "sized for the worst case", s->id, - bufsize, s->search_nmatches); + fatalx("session %u: SEARCH ALL truncated at " + "%zu bytes (%u matches)", s->id, bufsize, + s->search_nmatches); } } @@ -1128,7 +1001,7 @@ session_finish_search(struct session *s, struct imsg_m len += (size_t)snprintf(buf + len, bufsize - len, " COUNT %u", s->search_nmatches); - /* RFC 7162 SS3.1.10: MODSEQ in ESEARCH */ + /* RFC 7162 section 3.1.10: MODSEQ in ESEARCH */ if (s->search_used_modseq && s->search_nmatches > 0 && len < bufsize) len += (size_t)snprintf(buf + len, bufsize - len, @@ -1144,7 +1017,7 @@ session_finish_search(struct session *s, struct imsg_m free(buf); done: - /* as RFC 9051 SS6.4.9's "UID completed" */ + /* as RFC 9051 section 6.4.9's "UID completed" */ session_reply(s, s->pending_tag, "OK", s->cmd_by_uid ? "UID SEARCH completed" : "SEARCH completed"); goto cleanup; blob - 39a322115575c507c1a25286fcf680d5f615b106 blob + b2237913a69fabeb9c2f650a4f8f514a76dbb15c --- src/search_match.c +++ src/search_match.c @@ -97,7 +97,7 @@ static int match_body(const char *, size_t, size_t, const struct imsg_parser_leaf *, uint32_t, const char *, char *); -/* RFC 9051 SS6.4.4 folds the ASCII range only, byte by byte */ +/* RFC 9051 section 6.4.4 folds the ASCII range only, byte by byte */ static int ascii_lower(int c) { @@ -154,7 +154,7 @@ hex_value(int c) return (-1); } -/* RFC 2047 SS2 encoded-word */ +/* RFC 2047 section 2 encoded-word */ static size_t decode_word(const char *w, size_t len, char *out) { @@ -225,7 +225,7 @@ encoded_word_len(const char *s, size_t len) return (0); } -/* RFC 5322 SS2.2.3 unfolding and RFC 2047 SS6.1 decoding */ +/* RFC 5322 section 2.2.3 unfolding and RFC 2047 section 6.1 decoding */ static char * decode_value(const char *v, size_t vlen, size_t *outlen) { @@ -273,14 +273,14 @@ decode_value(const char *v, size_t vlen, size_t *outle return (out); } -/* RFC 5322 SS3.3 allows FWS, folds included, between a date's tokens */ +/* RFC 5322 section 3.3 allows FWS, folds included, between a date's tokens */ static int date_fws(int c) { return (c == ' ' || c == '\t' || c == '\r' || c == '\n'); } -/* RFC 9051 SS6.4.4: the day only, ignoring time and zone */ +/* RFC 9051 section 6.4.4: the day only, ignoring time and zone */ static int date_field_day(const char *v, size_t vlen, int64_t *out) { @@ -291,7 +291,7 @@ date_field_day(const char *v, size_t vlen, int64_t *ou while (p < end && date_fws((unsigned char)*p)) p++; - /* RFC 5322 SS3.3: day-of-week is optional */ + /* RFC 5322 section 3.3: day-of-week is optional */ if (p < end && ascii_lower((unsigned char)*p) >= 'a' && ascii_lower((unsigned char)*p) <= 'z') { while (p < end && *p != ',') @@ -402,7 +402,7 @@ toolarge: return (-1); } -/* RFC 9051 SS6.4.4: addresses as ENVELOPE shows them, not group names */ +/* RFC 9051 section 6.4.4: addresses as ENVELOPE shows them, not group names */ static int match_addresses(const char *v, size_t vlen, const char *needle, size_t needlelen) @@ -412,7 +412,7 @@ match_addresses(const char *v, size_t vlen, const char size_t i, ulen = 0, pos = 0, toklen, declen; int hit = 0, ingroup = 0, kind; - /* RFC 5322 SS2.2.3: unfolding drops CR and LF */ + /* RFC 5322 section 2.2.3: unfolding drops CR and LF */ if ((unf = malloc(vlen + 1)) == NULL) return (-1); for (i = 0; i < vlen; i++) { @@ -563,7 +563,7 @@ kmp_contains(const char *hay, size_t haylen, const cha return (0); } -/* RFC 2045 SS6.8: characters outside the alphabet are ignored */ +/* RFC 2045 section 6.8: characters outside the alphabet are ignored */ static size_t decode_b64(const char *in, size_t len, char *out) { @@ -583,7 +583,7 @@ decode_b64(const char *in, size_t len, char *out) return (n); } -/* RFC 2045 SS6.7; an "=" that is neither form is kept as it is */ +/* RFC 2045 section 6.7; an "=" that is neither form is kept as it is */ static size_t decode_qp(const char *in, size_t len, char *out) { @@ -635,7 +635,7 @@ scan_text(struct body_walk *w, const char *t, size_t l } } -/* RFC 9051 SS6.4.4: TEXT sees every header, MIME part headers too */ +/* RFC 9051 section 6.4.4: TEXT sees every header, MIME part headers too */ static int scan_header(struct body_walk *w, const char *hdr, size_t hdrlen) { @@ -689,7 +689,7 @@ scan_body(struct body_walk *w, const char *body, size_ return (0); } -/* RFC 2045 SS5.2, SS6.4; RFC 2046 SS5.1.3, SS5.1.5 */ +/* RFC 2045 section 5.2, section 6.4; RFC 2046 section 5.1.3, section 5.1.5 */ static int part_type(const char *hdr, size_t hdrlen, int digest, char *boundary, size_t boundarysize, int *enc, int *subdigest) @@ -714,14 +714,14 @@ part_type(const char *hdr, size_t hdrlen, int digest, (vallen == 4 && (strncasecmp(val, "7bit", 4) == 0 || strncasecmp(val, "8bit", 4) == 0)) || (vallen == 6 && strncasecmp(val, "binary", 6) == 0))) { - /* SS6.4: an unknown one makes it octet-stream */ + /* section 6.4: an unknown one makes it octet-stream */ free(val); return (PART_SKIP); } free(val); } - /* SS5.2: none, or an invalid one, is text/plain */ + /* section 5.2: none, or an invalid one, is text/plain */ if (extract_header_field(hdr, hdrlen, "Content-Type", &val, &vallen) == -1 || vallen == 0) { free(val); @@ -753,7 +753,7 @@ part_type(const char *hdr, size_t hdrlen, int digest, if (mime_read_token_or_qstring(val, vallen, &pos, value, sizeof(value)) == -1) break; - /* RFC 2046 SS5.1.1: at most 70 characters */ + /* RFC 2046 section 5.1.1: at most 70 characters */ if (strcasecmp(attr, "boundary") == 0 && strlcpy(boundary, value, boundarysize) < boundarysize) has_boundary = 1; @@ -761,7 +761,7 @@ part_type(const char *hdr, size_t hdrlen, int digest, free(val); if (strcasecmp(type, "multipart") == 0) { - /* RFC 2046 SS5.1.1 requires the boundary: invalid without */ + /* RFC 2046 section 5.1.1: invalid without a boundary */ if (!has_boundary) return (PART_TEXT); *subdigest = strcasecmp(subtype, "digest") == 0; @@ -772,13 +772,13 @@ part_type(const char *hdr, size_t hdrlen, int digest, PART_TEXT; else kind = strcasecmp(type, "text") == 0 ? PART_TEXT : PART_SKIP; - /* RFC 2046 SS5.1.1, SS5.2.1: these are never encoded */ + /* RFC 2046 section 5.1.1, section 5.2.1: these are never encoded */ if (kind != PART_TEXT) *enc = ENC_IDENTITY; return (kind); } -/* RFC 2046 SS5.1.1: the next "--boundary" line at or after *pos */ +/* RFC 2046 section 5.1.1: the next "--boundary" line at or after *pos */ static int next_delimiter(const char *body, size_t len, const char *delim, size_t dlen, size_t *pos, size_t *start, int *closing) @@ -828,9 +828,10 @@ walk_multipart(struct body_walk *w, int depth, const c if (dlen >= sizeof(delim)) return (0); for (;;) { - int found = next_delimiter(body, len, delim, dlen, &pos, - &dstart, &closing); + int found; + found = next_delimiter(body, len, delim, dlen, &pos, &dstart, + &closing); if (!found) dstart = len; if (inpart) { @@ -893,7 +894,7 @@ walk_entity(struct body_walk *w, int depth, const char } } -/* RFC 9051 SS6.4.4 BODY and TEXT over TEXT and MESSAGE parts */ +/* RFC 9051 section 6.4.4 BODY and TEXT over TEXT and MESSAGE parts */ static int match_body(const char *msg, size_t hdrlen, size_t len, const struct imsg_parser_leaf *leaves, uint32_t nleaves, blob - 39c29d5483acbef9f5451c27c58821b90f75bc4e blob + 510e38cf36986d52626ac5b038cee07ca77e0914 --- src/store.c +++ src/store.c @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - #include #include #include @@ -100,11 +99,11 @@ store_main(void) { struct imsgbuf ibuf3; struct imsg imsg; - ssize_t n; struct imsg_store_init init; + char unveil_path[sizeof(init.maildir) + 1]; + ssize_t n; gid_t gid; - imsgev_ibuf_init(&ibuf3, 3); for (;;) { @@ -151,21 +150,15 @@ store_main(void) setproctitle("store uid %u", (unsigned int)init.uid); /* confine before the ack, so a failure can be reported */ + if (snprintf(unveil_path, sizeof(unveil_path), "/%s", + init.maildir) >= (int)sizeof(unveil_path)) + fatalx("session %u: maildir path too long: %s", + session_id, init.maildir); + if (unveil(unveil_path, "rwc") == -1) + fatal("session %u: unveil %s", session_id, unveil_path); + if (chdir(unveil_path) == -1) + fatal("session %u: chdir %s", session_id, unveil_path); - { - char unveil_path[sizeof(init.maildir) + 1]; - - if (snprintf(unveil_path, sizeof(unveil_path), "/%s", - init.maildir) >= (int)sizeof(unveil_path)) - fatalx("session %u: maildir path too long: %s", - session_id, init.maildir); - if (unveil(unveil_path, "rwc") == -1) - fatal("unveil %s", unveil_path); - - if (chdir(unveil_path) == -1) - fatal("chdir %s", unveil_path); - } - /* unveil(2) covers lookups relative to these (sys/kern/vfs_lookup.c) */ if ((maildir_root_fd = open(".", O_RDONLY | O_DIRECTORY)) == -1) fatal("open maildir root"); @@ -181,14 +174,12 @@ store_main(void) imsgev_init_from_ibuf(&parent_iev, &ibuf3, store_parent_dispatch, NULL); - /* the handshake may already have read the first attach: take it */ + /* the handshake may have read a later message too: take it */ store_parent_dispatch(parent_iev.ibuf.fd, 0, &parent_iev); /* sendfd: FETCH hands the listener a read-only descriptor */ -#ifdef __OpenBSD__ if (pledge("stdio rpath wpath cpath flock recvfd sendfd", NULL) == -1) fatal("pledge"); -#endif log_debug("session %u: store ready (uid %u, gid %u, spool %s, " "maildir %s)", session_id, init.uid, init.gid, init.spool_root, @@ -255,7 +246,6 @@ store_parent_dispatch(int fd, short event, void *arg) imsg_free(&imsg); } imsgev_rearm_read(iev); - (void)fd; } static void @@ -280,8 +270,8 @@ store_attach(uint32_t id, int fd) break; } if (id == 0 || ss != NULL) { - log_warnx("session %u: refusing a session that is 0 or " - "already attached", id); + log_warnx("session %u: attach refused: id 0 or already " + "attached", id); close(fd); return; } @@ -345,7 +335,7 @@ parser_reply_safe(const char *buf, size_t len) return (!inquote && !escaped && depth == 0); } -/* RFC 9051 SS9: a literal may not hold NUL */ +/* RFC 9051 section 9: a literal may not hold NUL */ static int parser_literal_safe(const char *buf, size_t len) { @@ -543,9 +533,9 @@ parser_request(uint32_t type, const char *label, int f *rep_out = rep; rc = 0; } else - log_warnx("session %u: parser-worker returned " - "a %s that cannot be sent, message %s " - "skipped", session_id, label, basename); + log_warnx("session %u: parser %s unsendable, " + "message %s skipped", + session_id, label, basename); } else if (rep.found && rep.len > 0 && rep.len <= maxlen && imsg_get_len(&imsg) == rep.len) { if ((*buf_out = malloc(rep.len)) == NULL) @@ -559,9 +549,9 @@ parser_request(uint32_t type, const char *label, int f *buf_out = NULL; } else if (!parser_reply_valid(type, *buf_out, rep.len, maxlen)) { - log_warnx("session %u: parser-worker returned " - "a %s that cannot be sent, message %s " - "skipped", session_id, label, basename); + log_warnx("session %u: parser %s unsendable, " + "message %s skipped", + session_id, label, basename); free(*buf_out); *buf_out = NULL; } else { @@ -658,9 +648,8 @@ parser_fail(const char *basename) parser_strikes[i].count = 0; } if (++parser_strikes[i].count == PARSER_STRIKES) - log_warnx("session %u: message %s has failed the " - "parser-worker %d times, not sending it again", - session_id, basename, PARSER_STRIKES); + log_warnx("session %u: message %s: parser failed %d times, " + "not retried", session_id, basename, PARSER_STRIKES); } static int @@ -686,7 +675,7 @@ parser_install(int fd) return; } if (parser_up) { - log_warnx("store: a second parser-worker, refusing it"); + log_warnx("store: second parser-worker refused"); close(fd); return; } @@ -723,9 +712,6 @@ parser_walks(int ok) static void parser_idle(int fd, short event, void *arg) { - (void)fd; - (void)event; - (void)arg; log_debug("store: parser-worker idle, letting it go"); parser_drop(); } @@ -733,9 +719,6 @@ parser_idle(int fd, short event, void *arg) static void parser_wait_expired(int fd, short event, void *arg) { - (void)fd; - (void)event; - (void)arg; log_warnx("store: no parser-worker within %d seconds", PARSER_REPLY_TIMEOUT_SEC); parser_give_up(); @@ -744,18 +727,15 @@ parser_wait_expired(int fd, short event, void *arg) int mailbox_name_valid(const char *name) { - /* not NUL-terminated by imsg_get_data() */ if (memchr(name, '\0', MBOX_NAME_MAX) == NULL) { - log_warnx("session %u: mailbox name field is not " - "NUL-terminated, refusing", session_id); + log_warnx("session %u: mailbox name not NUL-terminated", + session_id); return (0); } return (mailbox_name_syntax_ok(name)); } - - int mailbox_open_dir(const char *target) { @@ -763,13 +743,29 @@ mailbox_open_dir(const char *target) O_RDONLY | O_DIRECTORY)); } +/* opendir(3) relative to dfd; NULL with errno set, nothing left open */ +DIR * +opendirat(int dfd, const char *name) +{ + DIR *dp; + int fd, saved_errno; + + if ((fd = openat(dfd, name, O_RDONLY | O_DIRECTORY)) == -1) + return (NULL); + if ((dp = fdopendir(fd)) == NULL) { + saved_errno = errno; + close(fd); + errno = saved_errno; + } + return (dp); +} + static int imsg_field_valid(const char *field, size_t size, const char *what) { if (memchr(field, '\0', size) != NULL) return (1); - log_warnx("session %u: %s is not NUL-terminated, refusing the " - "request", session_id, what); + log_warnx("session %u: %s not NUL-terminated", session_id, what); return (0); } @@ -783,18 +779,16 @@ search_nodes_valid(const struct search_node *nodes, ui for (i = 0; i < nnodes; i++) { n = &nodes[i]; if (memchr(n->keyword, '\0', sizeof(n->keyword)) == NULL) { - log_warnx("session %u: SEARCH node %u keyword is not " - "NUL-terminated, refusing the request", - session_id, i); + log_warnx("session %u: SEARCH node %u: keyword not " + "NUL-terminated", session_id, i); return (0); } if (search_op_content(n->op) && (n->str_off > poollen || n->str_len > poollen - n->str_off || n->name_off > poollen || n->name_len > poollen - n->name_off)) { - log_warnx("session %u: SEARCH node %u string lies " - "outside its pool, refusing the request", - session_id, i); + log_warnx("session %u: SEARCH node %u: string outside " + "its pool", session_id, i); return (0); } } @@ -817,9 +811,9 @@ recv_trailing_array(struct imsg *imsg, const char *wha } bodylen = imsg_get_len(imsg); if (bodylen != (size_t)count * elemsize) { - log_warnx("session %u: %s length mismatch (header says %u " - "elements, imsg has %zu bytes)", session_id, what, count, - bodylen); + log_warnx("session %u: %s length mismatch (header %u " + "elements, imsg %zu bytes)", + session_id, what, count, bodylen); return (NULL); } *ok_out = 1; @@ -863,7 +857,7 @@ deferred_expired(struct store_session *ss) return (now.tv_nsec >= d->give_up_at.tv_nsec); } -/* RFC 9051 SS7.1 INUSE: answers one command, having changed nothing. */ +/* RFC 9051 section 7.1 INUSE: answers one command, having changed nothing. */ static void deferred_answer_busy_for(uint32_t type, struct store_session *ss) { @@ -876,7 +870,7 @@ deferred_answer_busy_for(uint32_t type, struct store_s size_t len = 0; uint32_t rtype = 0; - /* RFC 9051 SS6.4.4.1: a SAVE answered NO empties "$" */ + /* RFC 9051 section 6.4.4.1: a SAVE answered NO empties "$" */ if (type == IMSG_MBOX_SEARCH && (ss->deferred.req.search.return_opts & SEARCH_RETURN_SAVE)) ss->nsaved = 0; @@ -917,10 +911,8 @@ deferred_answer_busy_for(uint32_t type, struct store_s len = sizeof(appended); break; default: - log_warnx("session %u: no busy reply for imsg %u, can't " - "happen; only deferrable types are deferred", session_id, + fatalx("session %u: no busy reply for imsg %u", session_id, type); - break; } if (data != NULL && imsg_compose(&iev->ibuf, rtype, 0, 0, -1, data, len) == -1) @@ -949,9 +941,8 @@ deferred_arm(struct store_session *ss) tv.tv_usec = (d->wait_ms % 1000) * 1000; evtimer_set(&d->ev, deferred_retry, ss); if (evtimer_add(&d->ev, &tv) == -1) { - log_warnx("session %u: no timer for the index lock retry; " - "answering busy now rather than waiting for ever", - session_id); + log_warnx("session %u: evtimer_add (lock retry), answering " + "busy", session_id); deferred_give_up(ss); return; } @@ -966,8 +957,6 @@ deferred_retry(int fd, short event, void *arg) struct store_deferred *d = &ss->deferred; int again; - (void)fd; - (void)event; session_id = ss->id; switch (d->type) { @@ -1007,10 +996,7 @@ deferred_retry(int fd, short event, void *arg) again = handle_mbox_append_end(ss); break; default: - log_warnx("session %u: cannot re-run imsg %u, can't happen", - session_id, d->type); - deferred_give_up(ss); - return; + fatalx("session %u: deferred imsg %u", session_id, d->type); } if (!again) { d->active = 0; @@ -1034,19 +1020,15 @@ defer_command(uint32_t type, const void *req, size_t r struct timespec now; if (d->active) { - log_warnx("session %u: a second command to defer while one " - "waits, refusing the new one", session_id); + log_warnx("session %u: a second deferred command, refused", + session_id); deferred_answer_busy_for(type, ss); return; } if (reqlen > sizeof(d->req) || nelts > maxelts || - (size_t)nelts * eltlen > sizeof(d->elts)) { - /* the handler answered nothing, so something must */ - log_warnx("session %u: imsg %u too large to defer, can't " - "happen, it is checked on receipt", session_id, type); - deferred_answer_busy_for(type, ss); - return; - } + (size_t)nelts * eltlen > sizeof(d->elts)) + fatalx("session %u: imsg %u too large to defer", session_id, + type); if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) { log_warn("session %u: clock_gettime", session_id); now.tv_sec = 0; @@ -1074,18 +1056,52 @@ require_mailbox_selected(struct store_session *ss, con { if (ss->mailbox_selected) return (1); - log_warnx("session %u: %s before a successful IMSG_MBOX_SELECT, " - "refusing", session_id, what); + log_warnx("session %u: %s before IMSG_MBOX_SELECT, refused", + session_id, what); return (0); } +static struct seq_range * +recv_ranges(struct imsg *imsg, const char *what, uint32_t nranges, int *ok) +{ + return (recv_trailing_array(imsg, what, nranges, SEQSET_MAX_RANGES, + sizeof(struct seq_range), ok)); +} + +static int +ranges_required(uint32_t nranges, const char *what) +{ + if (nranges != 0) + return (1); + log_warnx("session %u: %s requires at least one range", session_id, + what); + return (0); +} + +/* rc is the handler's: 1 keeps the command for the index lock */ +static void +ranged_done(struct store_session *ss, uint32_t type, const void *req, + size_t reqlen, struct seq_range *ranges, uint32_t nranges, int rc) +{ + if (rc == 1) + defer_command(type, req, reqlen, ranges, nranges, + SEQSET_MAX_RANGES, sizeof(*ranges), ss); + free(ranges); +} + void store_dispatch(int fd, short event, void *arg) { struct store_session *ss = arg; struct imsgev *iev = &ss->iev; - struct imsg imsg; - ssize_t n; + struct imsg imsg; + union mbox_request u; + struct seq_range *ranges; + struct search_node *nodes; + char buf[MAX_IMSGSIZE]; + size_t len; + ssize_t n; + int ok, rc; /* the account's other sessions share this process: label the log */ session_id = ss->id; @@ -1128,126 +1144,78 @@ store_dispatch(int fd, short event, void *arg) imsg_free(&imsg); store_end(ss); return; - case IMSG_MBOX_SELECT: { - struct imsg_mbox_select req; - struct seq_range *ranges; - int ok; - - if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_SELECT (header)"); + case IMSG_MBOX_SELECT: + if (!recv_header(&imsg, &u.select, sizeof(u.select), + "IMSG_MBOX_SELECT")) break; - } - ranges = recv_trailing_array(&imsg, "IMSG_MBOX_SELECT", - req.qresync_nranges, SEQSET_MAX_RANGES, - sizeof(struct seq_range), &ok); + ranges = recv_ranges(&imsg, "IMSG_MBOX_SELECT", + u.select.qresync_nranges, &ok); if (!ok) break; - /* RFC 9051 SS6.3.2: failure deselects */ + /* RFC 9051 section 6.3.2: failure deselects */ ss->mailbox_selected = 0; - - if (handle_mbox_select(&req, ranges, - req.qresync_nranges, ss) == 1) - defer_command(IMSG_MBOX_SELECT, &req, - sizeof(req), ranges, req.qresync_nranges, - SEQSET_MAX_RANGES, sizeof(*ranges), ss); - free(ranges); + rc = handle_mbox_select(&u.select, ranges, + u.select.qresync_nranges, ss); + ranged_done(ss, IMSG_MBOX_SELECT, &u.select, + sizeof(u.select), ranges, u.select.qresync_nranges, + rc); break; - } - case IMSG_MBOX_FETCH: { - struct imsg_mbox_fetch req; - struct seq_range *ranges; - int ok; - - if (!require_mailbox_selected(ss, "IMSG_MBOX_FETCH")) - break; - - if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_FETCH (header)"); - break; - } - if (!imsg_field_valid(req.header_fields, - sizeof(req.header_fields), + case IMSG_MBOX_FETCH: + if (!require_mailbox_selected(ss, "IMSG_MBOX_FETCH") || + !recv_header(&imsg, &u.fetch, sizeof(u.fetch), + "IMSG_MBOX_FETCH") || + !imsg_field_valid(u.fetch.header_fields, + sizeof(u.fetch.header_fields), "IMSG_MBOX_FETCH.header_fields") || - !imsg_field_valid(req.section_part, - sizeof(req.section_part), - "IMSG_MBOX_FETCH.section_part")) + !imsg_field_valid(u.fetch.section_part, + sizeof(u.fetch.section_part), + "IMSG_MBOX_FETCH.section_part") || + !ranges_required(u.fetch.nranges, + "IMSG_MBOX_FETCH")) break; - if (req.nranges == 0) { - log_warnx("session %u: IMSG_MBOX_FETCH " - "requires at least one range", session_id); - break; - } - ranges = recv_trailing_array(&imsg, "IMSG_MBOX_FETCH", - req.nranges, SEQSET_MAX_RANGES, - sizeof(struct seq_range), &ok); + ranges = recv_ranges(&imsg, "IMSG_MBOX_FETCH", + u.fetch.nranges, &ok); if (!ok) break; - if (handle_mbox_fetch(&req, ranges, req.nranges, - ss) == 1) - defer_command(IMSG_MBOX_FETCH, &req, - sizeof(req), ranges, req.nranges, - SEQSET_MAX_RANGES, sizeof(*ranges), ss); - free(ranges); + rc = handle_mbox_fetch(&u.fetch, ranges, + u.fetch.nranges, ss); + ranged_done(ss, IMSG_MBOX_FETCH, &u.fetch, + sizeof(u.fetch), ranges, u.fetch.nranges, rc); break; - } - case IMSG_MBOX_STORE: { - struct imsg_mbox_store req; - struct seq_range *ranges; - int ok; - - if (!require_mailbox_selected(ss, "IMSG_MBOX_STORE")) + case IMSG_MBOX_STORE: + if (!require_mailbox_selected(ss, "IMSG_MBOX_STORE") || + !recv_header(&imsg, &u.store, sizeof(u.store), + "IMSG_MBOX_STORE") || + !imsg_field_valid(u.store.keywords, + sizeof(u.store.keywords), + "IMSG_MBOX_STORE.keywords") || + !ranges_required(u.store.nranges, + "IMSG_MBOX_STORE")) break; - - if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_STORE (header)"); - break; - } - if (!imsg_field_valid(req.keywords, - sizeof(req.keywords), "IMSG_MBOX_STORE.keywords")) - break; - if (req.nranges == 0) { - log_warnx("session %u: IMSG_MBOX_STORE " - "requires at least one range", session_id); - break; - } - ranges = recv_trailing_array(&imsg, "IMSG_MBOX_STORE", - req.nranges, SEQSET_MAX_RANGES, - sizeof(struct seq_range), &ok); + ranges = recv_ranges(&imsg, "IMSG_MBOX_STORE", + u.store.nranges, &ok); if (!ok) break; - if (handle_mbox_store(&req, ranges, req.nranges, - ss) == 1) - defer_command(IMSG_MBOX_STORE, &req, - sizeof(req), ranges, req.nranges, - SEQSET_MAX_RANGES, sizeof(*ranges), ss); - free(ranges); + rc = handle_mbox_store(&u.store, ranges, + u.store.nranges, ss); + ranged_done(ss, IMSG_MBOX_STORE, &u.store, + sizeof(u.store), ranges, u.store.nranges, rc); break; - } - case IMSG_MBOX_EXPUNGE: { - struct imsg_mbox_expunge req; - struct seq_range *ranges; - int ok; - - if (!require_mailbox_selected(ss, "IMSG_MBOX_EXPUNGE")) + case IMSG_MBOX_EXPUNGE: + if (!require_mailbox_selected(ss, + "IMSG_MBOX_EXPUNGE") || + !recv_header(&imsg, &u.expunge, sizeof(u.expunge), + "IMSG_MBOX_EXPUNGE")) break; - - if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_EXPUNGE (header)"); - break; - } - ranges = recv_trailing_array(&imsg, - "IMSG_MBOX_EXPUNGE", req.nranges, - SEQSET_MAX_RANGES, sizeof(struct seq_range), &ok); + ranges = recv_ranges(&imsg, "IMSG_MBOX_EXPUNGE", + u.expunge.nranges, &ok); if (!ok) break; - if (handle_mbox_expunge(&req, ranges, req.nranges, - ss) == 1) - defer_command(IMSG_MBOX_EXPUNGE, &req, - sizeof(req), ranges, req.nranges, - SEQSET_MAX_RANGES, sizeof(*ranges), ss); - free(ranges); + rc = handle_mbox_expunge(&u.expunge, ranges, + u.expunge.nranges, ss); + ranged_done(ss, IMSG_MBOX_EXPUNGE, &u.expunge, + sizeof(u.expunge), ranges, u.expunge.nranges, rc); break; - } case IMSG_MBOX_IDLE_REFRESH: if (imsg_get_len(&imsg) != 0) { log_warnx("bad IMSG_MBOX_IDLE_REFRESH"); @@ -1258,24 +1226,16 @@ store_dispatch(int fd, short event, void *arg) "IMSG_MBOX_IDLE_REFRESH"); handle_mbox_idle_refresh(ss); break; - case IMSG_MBOX_APPEND: { - struct imsg_mbox_append req; - + case IMSG_MBOX_APPEND: /* no reply: the literal follows regardless */ - if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_APPEND"); - break; - } - if (!imsg_field_valid(req.keywords, - sizeof(req.keywords), "IMSG_MBOX_APPEND.keywords")) - break; - handle_mbox_append_begin(ss, &req); + if (recv_fixed(&imsg, &u.append, sizeof(u.append), + "IMSG_MBOX_APPEND") && + imsg_field_valid(u.append.keywords, + sizeof(u.append.keywords), + "IMSG_MBOX_APPEND.keywords")) + handle_mbox_append_begin(ss, &u.append); break; - } - case IMSG_MBOX_APPEND_DATA: { - char buf[MAX_IMSGSIZE]; - size_t len; - + case IMSG_MBOX_APPEND_DATA: len = imsg_get_len(&imsg); if (len == 0 || len > sizeof(buf) || imsg_get_buf(&imsg, buf, len) == -1) { @@ -1284,45 +1244,38 @@ store_dispatch(int fd, short event, void *arg) } handle_mbox_append_data(ss, buf, len); break; - } case IMSG_MBOX_APPEND_END: if (handle_mbox_append_end(ss) == 1) defer_command(IMSG_MBOX_APPEND_END, NULL, 0, NULL, 0, 0, 0, ss); break; - case IMSG_MBOX_SEARCH: { - struct imsg_mbox_search req; - struct search_node *nodes; - int ok; - - if (!require_mailbox_selected(ss, "IMSG_MBOX_SEARCH")) + case IMSG_MBOX_SEARCH: + if (!require_mailbox_selected(ss, "IMSG_MBOX_SEARCH") || + !recv_header(&imsg, &u.search, sizeof(u.search), + "IMSG_MBOX_SEARCH")) break; - - if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1 || - req.poollen > sizeof(req.pool)) { + if (u.search.poollen > sizeof(u.search.pool)) { log_warnx("bad IMSG_MBOX_SEARCH (header)"); break; } nodes = recv_trailing_array(&imsg, "IMSG_MBOX_SEARCH", - req.nnodes, SEARCH_PROGRAM_MAX_NODES, + u.search.nnodes, SEARCH_PROGRAM_MAX_NODES, sizeof(struct search_node), &ok); if (!ok) break; - if (nodes != NULL && - !search_nodes_valid(nodes, req.nnodes, - req.poollen)) { + if (nodes != NULL && !search_nodes_valid(nodes, + u.search.nnodes, u.search.poollen)) { free(nodes); break; } - if (handle_mbox_search(&req, nodes, req.nnodes, - ss) == 1) - defer_command(IMSG_MBOX_SEARCH, &req, - sizeof(req), nodes, req.nnodes, + if (handle_mbox_search(&u.search, nodes, + u.search.nnodes, ss) == 1) + defer_command(IMSG_MBOX_SEARCH, &u.search, + sizeof(u.search), nodes, u.search.nnodes, SEARCH_PROGRAM_MAX_NODES, sizeof(*nodes), ss); free(nodes); break; - } case IMSG_MBOX_SAVED_CLEAR: if (imsg_get_len(&imsg) != 0) { log_warnx("bad IMSG_MBOX_SAVED_CLEAR"); @@ -1330,138 +1283,73 @@ store_dispatch(int fd, short event, void *arg) } ss->nsaved = 0; break; - case IMSG_MBOX_STATUS: { - struct imsg_mbox_status req; - - if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_STATUS"); - break; - } - if (handle_mbox_status(&req, ss) == 1) - defer_command(IMSG_MBOX_STATUS, &req, - sizeof(req), NULL, 0, 0, 0, ss); + case IMSG_MBOX_STATUS: + if (recv_fixed(&imsg, &u.status, sizeof(u.status), + "IMSG_MBOX_STATUS") && + handle_mbox_status(&u.status, ss) == 1) + defer_command(IMSG_MBOX_STATUS, &u.status, + sizeof(u.status), NULL, 0, 0, 0, ss); break; - } - case IMSG_MBOX_COPY: { - struct imsg_mbox_copy req; - struct seq_range *ranges; - int ok; - - if (!require_mailbox_selected(ss, "IMSG_MBOX_COPY")) + case IMSG_MBOX_COPY: + if (!require_mailbox_selected(ss, "IMSG_MBOX_COPY") || + !recv_header(&imsg, &u.copy, sizeof(u.copy), + "IMSG_MBOX_COPY") || + !ranges_required(u.copy.nranges, "IMSG_MBOX_COPY")) break; - - if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_COPY (header)"); - break; - } - if (req.nranges == 0) { - log_warnx("session %u: IMSG_MBOX_COPY " - "requires at least one range", session_id); - break; - } - ranges = recv_trailing_array(&imsg, "IMSG_MBOX_COPY", - req.nranges, SEQSET_MAX_RANGES, - sizeof(struct seq_range), &ok); + ranges = recv_ranges(&imsg, "IMSG_MBOX_COPY", + u.copy.nranges, &ok); if (!ok) break; - if (handle_mbox_copy(&req, ranges, req.nranges, - ss) == 1) - defer_command(IMSG_MBOX_COPY, &req, - sizeof(req), ranges, req.nranges, - SEQSET_MAX_RANGES, sizeof(*ranges), ss); - free(ranges); + rc = handle_mbox_copy(&u.copy, ranges, u.copy.nranges, + ss); + ranged_done(ss, IMSG_MBOX_COPY, &u.copy, sizeof(u.copy), + ranges, u.copy.nranges, rc); break; - } - case IMSG_MBOX_MOVE: { - struct imsg_mbox_copy req; - struct seq_range *ranges; - int ok; - - if (!require_mailbox_selected(ss, "IMSG_MBOX_MOVE")) + case IMSG_MBOX_MOVE: + if (!require_mailbox_selected(ss, "IMSG_MBOX_MOVE") || + !recv_header(&imsg, &u.copy, sizeof(u.copy), + "IMSG_MBOX_MOVE") || + !ranges_required(u.copy.nranges, "IMSG_MBOX_MOVE")) break; - - if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_MOVE (header)"); - break; - } - if (req.nranges == 0) { - log_warnx("session %u: IMSG_MBOX_MOVE " - "requires at least one range", session_id); - break; - } - ranges = recv_trailing_array(&imsg, "IMSG_MBOX_MOVE", - req.nranges, SEQSET_MAX_RANGES, - sizeof(struct seq_range), &ok); + ranges = recv_ranges(&imsg, "IMSG_MBOX_MOVE", + u.copy.nranges, &ok); if (!ok) break; - if (handle_mbox_move(&req, ranges, req.nranges, - ss) == 1) - defer_command(IMSG_MBOX_MOVE, &req, - sizeof(req), ranges, req.nranges, - SEQSET_MAX_RANGES, sizeof(*ranges), ss); - free(ranges); + rc = handle_mbox_move(&u.copy, ranges, u.copy.nranges, + ss); + ranged_done(ss, IMSG_MBOX_MOVE, &u.copy, sizeof(u.copy), + ranges, u.copy.nranges, rc); break; - } - case IMSG_MBOX_LIST: { - struct imsg_mbox_list req; - - if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_LIST"); - break; - } - handle_mbox_list(&req, iev); + case IMSG_MBOX_LIST: + if (recv_fixed(&imsg, &u.list, sizeof(u.list), + "IMSG_MBOX_LIST")) + handle_mbox_list(&u.list, iev); break; - } - case IMSG_MBOX_CREATE: { - struct imsg_mbox_create req; - - if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_CREATE"); - break; - } - handle_mbox_create(&req, iev); + case IMSG_MBOX_CREATE: + if (recv_fixed(&imsg, &u.create, sizeof(u.create), + "IMSG_MBOX_CREATE")) + handle_mbox_create(&u.create, iev); break; - } - case IMSG_MBOX_DELETE: { - struct imsg_mbox_delete req; - - if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_DELETE"); - break; - } - handle_mbox_delete(&req, ss); + case IMSG_MBOX_DELETE: + if (recv_fixed(&imsg, &u.delete, sizeof(u.delete), + "IMSG_MBOX_DELETE")) + handle_mbox_delete(&u.delete, ss); break; - } - case IMSG_MBOX_RENAME: { - struct imsg_mbox_rename req; - - if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_RENAME"); - break; - } - handle_mbox_rename(&req, ss); + case IMSG_MBOX_RENAME: + if (recv_fixed(&imsg, &u.rename, sizeof(u.rename), + "IMSG_MBOX_RENAME")) + handle_mbox_rename(&u.rename, ss); break; - } - case IMSG_MBOX_SUBSCRIBE: { - struct imsg_mbox_subscribe req; - - if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_SUBSCRIBE"); - break; - } - handle_mbox_subscribe(&req, iev); + case IMSG_MBOX_SUBSCRIBE: + if (recv_fixed(&imsg, &u.subscribe, sizeof(u.subscribe), + "IMSG_MBOX_SUBSCRIBE")) + handle_mbox_subscribe(&u.subscribe, iev); break; - } - case IMSG_MBOX_UNSUBSCRIBE: { - struct imsg_mbox_subscribe req; - - if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) { - log_warnx("bad IMSG_MBOX_UNSUBSCRIBE"); - break; - } - handle_mbox_unsubscribe(&req, iev); + case IMSG_MBOX_UNSUBSCRIBE: + if (recv_fixed(&imsg, &u.subscribe, sizeof(u.subscribe), + "IMSG_MBOX_UNSUBSCRIBE")) + handle_mbox_unsubscribe(&u.subscribe, iev); break; - } default: log_debug("store_dispatch: unhandled %d (session %u)", imsg_get_type(&imsg), session_id); @@ -1474,7 +1362,6 @@ store_dispatch(int fd, short event, void *arg) cur_snapshot_discard(&ss->cur_snap); } imsgev_rearm_read(iev); - (void)fd; } static void blob - 0694acc1ffb3e92c45054d2d96a511d9ccdbcd5d blob + f82ed4aea344afcc854de6974ae2f4a236c22df2 --- src/store_cache.c +++ src/store_cache.c @@ -18,7 +18,7 @@ /* * ENVELOPE and BODYSTRUCTURE text, as checked by parser_request(), kept - * per account worker. The key is RFC 9051 SS2.3.1.1's mailbox name, + * per account worker. The key is RFC 9051 section 2.3.1.1's mailbox name, * UIDVALIDITY and UID; the index's file name is checked on every hit. * Evicted oldest first, as usr.sbin/smtpd/queue_backend.c evicts. */ @@ -53,10 +53,11 @@ struct pcache_entry { RB_HEAD(pcache_tree, pcache_entry); TAILQ_HEAD(pcache_lru, pcache_entry); -static int pcache_cmp(struct pcache_entry *, struct pcache_entry *); -static struct pcache_entry *pcache_find(const char *, uint32_t, uint32_t, - const char *); -static void pcache_free(struct pcache_entry *); +static int pcache_cmp(struct pcache_entry *, + struct pcache_entry *); +static struct pcache_entry *pcache_find(const char *, uint32_t, uint32_t, + const char *); +static void pcache_free(struct pcache_entry *); RB_PROTOTYPE_STATIC(pcache_tree, pcache_entry, node, pcache_cmp); RB_GENERATE_STATIC(pcache_tree, pcache_entry, node, pcache_cmp); @@ -233,7 +234,7 @@ pcache_drop_mailbox(const char *mailbox) } } -/* a new UIDVALIDITY (RFC 9051 SS2.3.1.1) ends the old entries */ +/* a new UIDVALIDITY (RFC 9051 section 2.3.1.1) ends the old entries */ void pcache_check_mailbox(const char *mailbox, uint32_t uidvalidity) { blob - 35ffc566b73f498e61f59bf40776748d26b87b89 blob + 1a90d4829bde5cb8355514fef2b9a56494558e36 --- src/store_cmd.c +++ src/store_cmd.c @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - #include #include #include @@ -44,9 +43,6 @@ int cmd_idle(struct session *s, const char *tag, char *args) { - /* RFC 9051 SS6.3.13: IDLE takes no arguments */ - (void)args; - if (strlcpy(s->pending_tag, tag, sizeof(s->pending_tag)) >= sizeof(s->pending_tag)) { session_reply(s, tag, "NO", "[SERVERBUG] internal error"); @@ -63,31 +59,27 @@ cmd_idle(struct session *s, const char *tag, char *arg return (1); } -/* RFC 9051 SS6.4.1 CLOSE: a silent EXPUNGE */ +/* RFC 9051 section 6.4.1 CLOSE: a silent EXPUNGE */ int cmd_close(struct session *s, const char *tag, char *args) { - (void)args; - return session_request_expunge(s, tag, 1, 0, NULL, 0); + return (session_request_expunge(s, tag, 1, 0, NULL, 0)); } -/* RFC 9051 SS6.4.2 UNSELECT */ +/* RFC 9051 section 6.4.2 UNSELECT */ int cmd_unselect(struct session *s, const char *tag, char *args) { - (void)args; - s->state = SESSION_AUTHENTICATED; session_reply(s, tag, "OK", "Unselect completed"); return (1); } -/* RFC 9051 SS6.4.3 EXPUNGE */ +/* RFC 9051 section 6.4.3 EXPUNGE */ int cmd_expunge(struct session *s, const char *tag, char *args) { - (void)args; - return session_request_expunge(s, tag, 0, 0, NULL, 0); + return (session_request_expunge(s, tag, 0, 0, NULL, 0)); } int @@ -132,9 +124,7 @@ parse_store_flags(char *flagspec, uint32_t *sysflags_o else if (strcasecmp(tok, "\\Draft") == 0) sysflags |= MBOX_FLAG_DRAFT; else if (strcasecmp(tok, "\\Recent") == 0) { - *errmsg = "\\Recent cannot be set, RFC 9051 " - "deprecates it and excludes it from the " - "flag grammar entirely"; + *errmsg = "\\Recent cannot be set"; return (-2); } else { *errmsg = "unsupported system flag" @@ -171,7 +161,7 @@ parse_store_flags(char *flagspec, uint32_t *sysflags_o return (0); } -/* RFC 7162 SS3.1.3 UNCHANGEDSINCE; 0 is legal */ +/* RFC 7162 section 3.1.3 UNCHANGEDSINCE; 0 is legal */ int parse_store_modifiers(char *modspec, struct imsg_mbox_store *req, const char **errmsg) @@ -191,23 +181,21 @@ parse_store_modifiers(char *modspec, struct imsg_mbox_ for (tok = strtok_r(p, " ", &save); tok != NULL; tok = strtok_r(NULL, " ", &save)) { if (strcasecmp(tok, "UNCHANGEDSINCE") == 0) { - const char *valtok = strtok_r(NULL, " ", &save); - char *ep; + const char *valtok, *errstr; + valtok = strtok_r(NULL, " ", &save); if (valtok == NULL) { *errmsg = "UNCHANGEDSINCE requires a " "mod-sequence value"; return (-1); } - /* strtoull(3) accepts a sign */ if (*valtok < '0' || *valtok > '9') { *errmsg = "invalid UNCHANGEDSINCE mod-sequence"; return (-1); } - errno = 0; - req->unchangedsince = strtoull(valtok, &ep, 10); - if (*ep != '\0' || errno != 0 || - req->unchangedsince > MODSEQ_MAX) { + req->unchangedsince = strtonum(valtok, 0, MODSEQ_MAX, + &errstr); + if (errstr != NULL) { *errmsg = "invalid UNCHANGEDSINCE mod-sequence"; return (-1); } @@ -221,11 +209,11 @@ parse_store_modifiers(char *modspec, struct imsg_mbox_ return (0); } -/* RFC 9051 SS6.4.6: store-modifiers lead */ +/* RFC 9051 section 6.4.6: store-modifiers lead */ int cmd_store_cmd(struct session *s, const char *tag, char *args) { - return store_do(s, tag, args, 0); + return (store_do(s, tag, args, 0)); } int @@ -352,7 +340,7 @@ store_do(struct session *s, const char *tag, char *arg } if (s->mbox_readonly) { - /* RFC 9051 SS6.3.3: EXAMINE is read-only */ + /* RFC 9051 section 6.3.3: EXAMINE is read-only */ session_reply(s, tag, "NO", "[CANNOT] Mailbox is read-only " "(selected via EXAMINE)"); return (1); @@ -378,7 +366,7 @@ store_do(struct session *s, const char *tag, char *arg return (1); } - /* RFC 7162 SS3.1: UNCHANGEDSINCE enables CONDSTORE */ + /* RFC 7162 section 3.1: UNCHANGEDSINCE enables CONDSTORE */ if (req.has_unchangedsince) session_condstore_enable(s); @@ -391,8 +379,8 @@ store_do(struct session *s, const char *tag, char *arg s->cmd_by_uid = by_uid; s->state = SESSION_STORING; - if (!send_mbox_request(s, IMSG_MBOX_STORE, cmdname, "IMSG_MBOX_STORE", - &req, sizeof(req), ranges, nranges, sizeof(struct seq_range))) { + if (send_mbox_request(s, IMSG_MBOX_STORE, "IMSG_MBOX_STORE", &req, + sizeof(req), ranges, nranges, sizeof(struct seq_range)) == -1) { session_reply(s, tag, "NO", "[SERVERBUG] internal error"); s->state = SESSION_SELECTED; return (1); @@ -401,7 +389,7 @@ store_do(struct session *s, const char *tag, char *arg return (1); } -/* RFC 9051 SS6.4.7 COPY and SS6.4.8 MOVE */ +/* RFC 9051 section 6.4.7 COPY and section 6.4.8 MOVE */ int copy_move_dispatch(struct session *s, const char *tag, char *args, int by_uid, int is_move) @@ -455,7 +443,7 @@ copy_move_dispatch(struct session *s, const char *tag, if (listener_reject_bad_utf8(s, tag, mailbox)) return (1); - /* a name the server refuses is RFC 5530 SS3 NO [CANNOT], not BAD */ + /* a refused name is RFC 5530 section 3 NO [CANNOT], not BAD */ if (!mailbox_name_is_inbox(mailbox) && !listener_mailbox_name_valid(mailbox)) { session_reply(s, tag, "NO", "[CANNOT] invalid mailbox name"); @@ -463,7 +451,7 @@ copy_move_dispatch(struct session *s, const char *tag, } if (is_move && s->mbox_readonly) { - /* RFC 9051 SS6.3.3: MOVE removes messages; COPY may proceed */ + /* RFC 9051 section 6.3.3: MOVE would remove; COPY is fine */ session_reply(s, tag, "NO", "[CANNOT] Mailbox is read-only " "(selected via EXAMINE)"); return (1); @@ -490,9 +478,9 @@ copy_move_dispatch(struct session *s, const char *tag, s->cmd_is_move = is_move; s->state = SESSION_COPYING; - if (!send_mbox_request(s, is_move ? IMSG_MBOX_MOVE : IMSG_MBOX_COPY, - cmdname, cmdname, &req, sizeof(req), ranges, nranges, - sizeof(struct seq_range))) { + if (send_mbox_request(s, is_move ? IMSG_MBOX_MOVE : IMSG_MBOX_COPY, + is_move ? "IMSG_MBOX_MOVE" : "IMSG_MBOX_COPY", &req, sizeof(req), + ranges, nranges, sizeof(struct seq_range)) == -1) { session_reply(s, tag, "NO", "[SERVERBUG] internal error"); s->state = SESSION_SELECTED; return (1); @@ -501,21 +489,19 @@ copy_move_dispatch(struct session *s, const char *tag, return (1); } - int cmd_copy(struct session *s, const char *tag, char *args) { - return copy_move_dispatch(s, tag, args, 0, 0); + return (copy_move_dispatch(s, tag, args, 0, 0)); } - int cmd_move(struct session *s, const char *tag, char *args) { - return copy_move_dispatch(s, tag, args, 0, 1); + return (copy_move_dispatch(s, tag, args, 0, 1)); } -/* RFC 9051 SS6.4.9 UID */ +/* RFC 9051 section 6.4.9 UID */ int cmd_uid(struct session *s, const char *tag, char *args) { @@ -538,24 +524,24 @@ cmd_uid(struct session *s, const char *tag, char *args subargs = NULL; if (strcasecmp(sub, "FETCH") == 0) - return fetch_dispatch(s, tag, subargs, 1); + return (fetch_dispatch(s, tag, subargs, 1)); if (strcasecmp(sub, "STORE") == 0) - return store_do(s, tag, subargs, 1); + return (store_do(s, tag, subargs, 1)); if (strcasecmp(sub, "SEARCH") == 0) - return search_dispatch(s, tag, subargs, 1); + return (search_dispatch(s, tag, subargs, 1)); if (strcasecmp(sub, "EXPUNGE") == 0) - return uid_expunge_dispatch(s, tag, subargs); + return (uid_expunge_dispatch(s, tag, subargs)); if (strcasecmp(sub, "COPY") == 0) - return copy_move_dispatch(s, tag, subargs, 1, 0); + return (copy_move_dispatch(s, tag, subargs, 1, 0)); if (strcasecmp(sub, "MOVE") == 0) - return copy_move_dispatch(s, tag, subargs, 1, 1); + return (copy_move_dispatch(s, tag, subargs, 1, 1)); session_reply(s, tag, "BAD", "UID sub-command must be COPY, FETCH, MOVE, SEARCH, or STORE"); return (1); } -/* RFC 9051 SS7.5.1 EXPUNGE, or RFC 7162 SS3.2.10.2 VANISHED */ +/* RFC 9051 section 7.5.1 EXPUNGE, or RFC 7162 section 3.2.10.2 VANISHED */ void session_send_expunge_response(struct session *s, const struct imsg_mbox_expunged *exp) @@ -569,7 +555,7 @@ session_send_expunge_response(struct session *s, session_untagged(s, buf); } -/* RFC 7162 SS3.2.6 VANISHED (EARLIER) */ +/* RFC 7162 section 3.2.6 VANISHED (EARLIER) */ void session_handle_fetch_vanished(struct session *s, const struct imsg_mbox_select_vanished *v) @@ -595,12 +581,12 @@ session_request_expunge(struct session *s, const char if (s->mbox_readonly) { if (is_close) { - /* RFC 9051 SS6.4.1: read-only, so just deselect */ + /* RFC 9051 section 6.4.1: read-only, just deselect */ s->state = SESSION_AUTHENTICATED; session_reply(s, tag, "OK", "CLOSE completed"); return (1); } - /* RFC 9051 SS6.3.3: no read-only exception for EXPUNGE */ + /* RFC 9051 section 6.3.3: no read-only exception for EXPUNGE */ session_reply(s, tag, "NO", "[CANNOT] Mailbox is read-only " "(selected via EXAMINE)"); return (1); @@ -627,9 +613,9 @@ session_request_expunge(struct session *s, const char s->cmd_by_uid = by_uid; s->state = SESSION_EXPUNGING; - if (!send_mbox_request(s, IMSG_MBOX_EXPUNGE, cmdname, + if (send_mbox_request(s, IMSG_MBOX_EXPUNGE, "IMSG_MBOX_EXPUNGE", &req, sizeof(req), ranges, nranges, - sizeof(struct seq_range))) { + sizeof(struct seq_range)) == -1) { session_reply(s, tag, "NO", "[SERVERBUG] internal error"); s->state = SESSION_SELECTED; return (1); @@ -655,10 +641,10 @@ uid_expunge_dispatch(struct session *s, const char *ta return (1); } - return session_request_expunge(s, tag, 0, 1, ranges, nranges); + return (session_request_expunge(s, tag, 0, 1, ranges, nranges)); } -/* RFC 7162 SS3.1.3 MODIFIED entry */ +/* RFC 7162 section 3.1.3 MODIFIED entry */ void session_handle_store_modified(struct session *s, struct imsg_mbox_store_modified *m) @@ -669,9 +655,9 @@ session_handle_store_modified(struct session *s, if (s->store_modified_n == s->store_modified_cap) { uint32_t newcap = s->store_modified_cap ? s->store_modified_cap * 2 : 16; - uint32_t *n = reallocarray(s->store_modified, newcap, - sizeof(*n)); + uint32_t *n; + n = reallocarray(s->store_modified, newcap, sizeof(*n)); if (n == NULL) { log_warn("session %u: realloc STORE MODIFIED array", s->id); @@ -687,8 +673,9 @@ session_handle_store_modified(struct session *s, } /* refuses rather than truncates when it would not fit */ -static size_t -append_range_token(char *buf, size_t bufsize, size_t written, int *first, +/* appends "lo" or "lo:hi" after a comma; -1 when it would not fit */ +static int +append_range_token(char *buf, size_t bufsize, size_t *written, int *first, uint32_t lo, uint32_t hi, int *truncated) { char tok[24]; @@ -699,23 +686,23 @@ append_range_token(char *buf, size_t bufsize, size_t w else toklen = snprintf(tok, sizeof(tok), "%u:%u", lo, hi); if (toklen < 0) - return ((size_t)-1); + return (-1); - if (written + (size_t)toklen + (*first ? 0 : 1) >= bufsize) { + if (*written + (size_t)toklen + (*first ? 0 : 1) >= bufsize) { *truncated = 1; - return ((size_t)-1); + return (-1); } if (!*first) - buf[written++] = ','; - memcpy(buf + written, tok, (size_t)toklen); - written += (size_t)toklen; - buf[written] = '\0'; + buf[(*written)++] = ','; + memcpy(buf + *written, tok, (size_t)toklen); + *written += (size_t)toklen; + buf[*written] = '\0'; *first = 0; - return (written); + return (0); } -/* RFC 9051 SS7.3.4 sequence list; input sorted */ +/* RFC 9051 section 7.3.4 sequence list; input sorted */ size_t format_seq_list(char *buf, size_t bufsize, const uint32_t *nums, uint32_t n, int *truncated) @@ -731,18 +718,15 @@ format_seq_list(char *buf, size_t bufsize, const uint3 uint32_t start = nums[i]; uint32_t end = start; uint32_t j = i + 1; - size_t w; while (j < n && nums[j] == end + 1) { end = nums[j]; j++; } - w = append_range_token(buf, bufsize, written, &first, start, - end, truncated); - if (w == (size_t)-1) + if (append_range_token(buf, bufsize, &written, &first, start, + end, truncated) == -1) break; - written = w; i = j; } @@ -763,13 +747,9 @@ format_range_list(char *buf, size_t bufsize, buf[0] = '\0'; for (i = 0; i < n; i++) { - size_t w; - - w = append_range_token(buf, bufsize, written, &first, - ranges[i].lo, ranges[i].hi, truncated); - if (w == (size_t)-1) + if (append_range_token(buf, bufsize, &written, &first, + ranges[i].lo, ranges[i].hi, truncated) == -1) break; - written = w; } return (written); @@ -778,7 +758,7 @@ format_range_list(char *buf, size_t bufsize, #define COPYUID_PER_ENTRY 11 #define COPYUID_WRAPPER_MAX 160 -/* RFC 9051 SS6.4.7/SS6.4.8 terminal reply */ +/* RFC 9051 section 6.4.7/section 6.4.8 terminal reply */ void session_finish_copy_or_move(struct session *s, const struct imsg_mbox_result *res) @@ -793,22 +773,21 @@ session_finish_copy_or_move(struct session *s, if (res->error != MBOX_OP_OK || s->copy_alloc_failed) { char text[48]; - /* RFC 9051 SS6.4.7: TRYCREATE */ + /* RFC 9051 section 6.4.7: TRYCREATE */ if (!s->copy_alloc_failed && res->error == MBOX_OP_ERR_NO_SUCH_MAILBOX) snprintf(text, sizeof(text), "[TRYCREATE] no such " "mailbox"); else if (!s->copy_alloc_failed && res->error == MBOX_OP_ERR_BUSY) - snprintf(text, sizeof(text), "%s", IMAP_BUSY_TEXT); + strlcpy(text, IMAP_BUSY_TEXT, sizeof(text)); else if (!s->copy_alloc_failed && res->error == MBOX_OP_ERR_LIMIT) snprintf(text, sizeof(text), "[LIMIT] %s failed", cmdname); else if (!s->copy_alloc_failed && res->error == MBOX_OP_ERR_OVERQUOTA) - snprintf(text, sizeof(text), "%s", - IMAP_OVERQUOTA_TEXT); + strlcpy(text, IMAP_OVERQUOTA_TEXT, sizeof(text)); else snprintf(text, sizeof(text), "%s failed", cmdname); session_reply(s, s->pending_tag, "NO", text); @@ -817,7 +796,6 @@ session_finish_copy_or_move(struct session *s, s->mbox_highestmodseq = res->highestmodseq; - if (s->copy_n > 0) { char *srcbuf = NULL, *destbuf = NULL, *text = NULL; size_t listsize, textsize; @@ -871,7 +849,7 @@ session_finish_copy_or_move(struct session *s, session_reply(s, s->pending_tag, "OK", "Done"); } else if (!sent) { - /* RFC 9051 SS6.4.7: a SHOULD; never truncated */ + /* RFC 9051 section 6.4.7: a SHOULD; never truncated */ char fallback[64]; snprintf(fallback, sizeof(fallback), "%s completed", @@ -879,7 +857,7 @@ session_finish_copy_or_move(struct session *s, session_reply(s, s->pending_tag, "OK", fallback); } } else { - /* RFC 9051 SS6.4.7's own worked example, verbatim. */ + /* RFC 9051 section 6.4.7's own worked example, verbatim. */ session_reply(s, s->pending_tag, "OK", "No matching messages, so nothing copied"); } blob - af036bd14d59347e1db3ab7d2c57f9914359fe22 blob + 2ee04d07aa22ba325d4b7df0403cafa80a16c44c --- src/store_internal.h +++ src/store_internal.h @@ -22,10 +22,16 @@ #include #include +#include #include #include "mboxname.h" +/* + * Returns: handle_mbox_*() 0 once answered, 1 to defer on a busy index + * lock; the rest 0/-1 or 1/0, except where a definition says otherwise. + */ + struct mbox_index { uint32_t uidvalidity; uint32_t uidnext; @@ -75,7 +81,7 @@ struct store_fetch_walk { uint64_t seen_modseq; }; -/* RFC 9051 SS6.4.4 SEARCH, yielding to the account's other sessions */ +/* RFC 9051 section 6.4.4 SEARCH, yielding to the account's other sessions */ struct store_search_walk { int active; int wait_parser; /* paused until one comes */ @@ -121,7 +127,7 @@ struct store_idle_baseline { int valid; }; -/* RFC 9051 SS6.3.12 APPEND; a failure is held until END */ +/* RFC 9051 section 6.3.12 APPEND; a failure is held until END */ struct store_append { int active; int failed; @@ -142,6 +148,22 @@ struct cur_snapshot { size_t n; }; +union mbox_request { + struct imsg_mbox_select select; + struct imsg_mbox_fetch fetch; + struct imsg_mbox_store store; + struct imsg_mbox_expunge expunge; + struct imsg_mbox_search search; + struct imsg_mbox_status status; + struct imsg_mbox_copy copy; + struct imsg_mbox_append append; + struct imsg_mbox_list list; + struct imsg_mbox_create create; + struct imsg_mbox_delete delete; + struct imsg_mbox_rename rename; + struct imsg_mbox_subscribe subscribe; +}; + /* one command waiting on the index lock; re-run from the top */ struct store_deferred { int active; @@ -149,16 +171,8 @@ struct store_deferred { struct event ev; struct timespec give_up_at; unsigned int wait_ms; + union mbox_request req; union { - struct imsg_mbox_store store; - struct imsg_mbox_expunge expunge; - struct imsg_mbox_fetch fetch; - struct imsg_mbox_search search; - struct imsg_mbox_select select; - struct imsg_mbox_status status; - struct imsg_mbox_copy copy; - } req; - union { struct seq_range ranges[SEQSET_MAX_RANGES]; struct search_node nodes[SEARCH_PROGRAM_MAX_NODES]; } elts; @@ -177,6 +191,15 @@ struct address { size_t hostlen; }; +/* RFC 2045 section 5.1 Content-Type; RFC 2046 section 5.1.1: boundary <= 70 */ +struct content_type { + char type[64]; + char subtype[64]; + char params_fmt[600]; + char boundary[70 + 1]; + int has_boundary; +}; + #define ADDR_MAILBOX 1 #define ADDR_GROUP 2 #define ADDR_GROUP_END 3 @@ -186,7 +209,7 @@ struct store_session { TAILQ_ENTRY(store_session) entry; struct imsgev iev; - /* a failed SELECT leaves nothing selected (RFC 9051 SS6.3.2) */ + /* a failed SELECT leaves nothing selected (RFC 9051 section 6.3.2) */ int mailbox_selected; /* "" for INBOX */ @@ -203,7 +226,7 @@ struct store_session { struct store_idle_baseline idle_baseline; struct store_append append; - /* RFC 9051 SS6.4.4.1 "$", as UID ranges */ + /* RFC 9051 section 6.4.4.1 "$", as UID ranges */ struct seq_range saved[SEQSET_MAX_RANGES]; uint32_t nsaved; }; @@ -253,6 +276,7 @@ void handle_mbox_unsubscribe(struct imsg_mbox_subscri struct imsgev *); int mailbox_name_valid(const char *); int mailbox_open_dir(const char *); +DIR *opendirat(int, const char *); void cur_snapshot_discard(struct cur_snapshot *); int fetch_walk_paused(struct store_session *); void fetch_walk_resume(struct store_session *); @@ -308,8 +332,7 @@ int mime_is_tspecial(char); int mime_read_token_or_qstring(const char *, size_t, size_t *, char *, size_t); void mime_str_upper(char *); -int parse_content_type(const char *, size_t, char *, size_t, - char *, size_t, char *, size_t, char *, size_t, int *); +int parse_content_type(const char *, size_t, struct content_type *); int split_multipart(const char *, size_t, const char *, size_t *, size_t *, int *, int); int build_body_structure(int, int *, const char *, size_t, @@ -361,13 +384,13 @@ void index_free(struct mbox_index *); int index_parse_line(const char *, struct index_rec *); int index_field_valid(const char *); /* no ':', CR or LF */ int index_basename_valid(const char *); /* safe in "new/%s" */ -uint32_t index_max_uid(struct mbox_index *); +uint32_t index_max_uid(struct mbox_index *); void send_vanished_range(const struct mbox_index *, uint32_t, uint32_t, struct imsgev *); -uint32_t seqset_resolve(const struct seq_range *, uint32_t, uint32_t, - int, struct seq_range[SEQSET_MAX_RANGES]); +uint32_t seqset_resolve(const struct seq_range *, uint32_t, uint32_t, int, + struct seq_range[SEQSET_MAX_RANGES]); int seqset_contains(const struct seq_range *, uint32_t, uint32_t); -uint32_t seqset_max_hi(const struct seq_range *, uint32_t); +uint32_t seqset_max_hi(const struct seq_range *, uint32_t); /* PAST_END holds only because the scans walk in ascending order */ enum seqset_pos { blob - fb6781900013f0b414aa6a72d370df8ce6e14ddc blob + 16c3448a2f38c87298731a90db0f7964928411b7 --- src/store_ipc.c +++ src/store_ipc.c @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - #include #include #include @@ -53,7 +52,6 @@ fetch_part_recv(struct session *s, struct imsg *imsg, *foundp = 0; if (!found) - /* store.c found/built nothing for this message */ return; wirelen = imsg_get_len(imsg); @@ -81,12 +79,86 @@ fetch_part_recv(struct session *s, struct imsg *imsg, *foundp = 1; } +union mbox_reply { + struct imsg_mbox_selected selected; + struct imsg_mbox_status_result status; + struct imsg_mbox_fetch_header header; + struct imsg_mbox_fetch_envelope envelope; + struct imsg_mbox_fetch_bodystructure bodystructure; + struct imsg_mbox_fetch_meta meta; + struct imsg_mbox_expunged expunged; + struct imsg_mbox_copy_mapping mapping; + struct imsg_mbox_search_match match; + struct imsg_mbox_select_vanished vanished; + struct imsg_mbox_store_modified modified; + struct imsg_mbox_view_exists exists; + struct imsg_mbox_idle_refreshed refreshed; + struct imsg_mbox_result result; + struct imsg_mbox_list_item item; + struct imsg_mbox_appended appended; +}; + +static void +session_recv_fetch_body(struct session *s, struct imsg *imsg) +{ + struct imsg_mbox_fetch_body bodyhdr; + int bodyfd; + + bodyfd = imsg_get_fd(imsg); + if (s->pending_body_fd != -1) + close(s->pending_body_fd); + s->pending_body_fd = -1; + s->pending_body_off = 0; + s->pending_body_len = 0; + s->pending_body_found = 0; + if (!recv_fixed(imsg, &bodyhdr, sizeof(bodyhdr), + "IMSG_MBOX_FETCH_BODY")) { + if (bodyfd != -1) + close(bodyfd); + return; + } + if (!bodyhdr.found || (bodyhdr.length > 0 && bodyfd == -1)) { + if (bodyhdr.found) + log_warnx("session %u: IMSG_MBOX_FETCH_BODY without " + "its descriptor", s->id); + if (bodyfd != -1) + close(bodyfd); + return; + } + s->pending_body_fd = bodyfd; + s->pending_body_off = bodyhdr.offset; + s->pending_body_len = bodyhdr.length; + s->pending_body_found = 1; +} + +/* a MOVE's EXPUNGEs wait for COPYUID, RFC 9051 section 6.4.8 */ +static void +session_hold_expunged(struct session *s, const struct imsg_mbox_expunged *exp) +{ + struct imsg_mbox_expunged *enew; + uint32_t newcap; + + if (s->move_expunged_n == s->move_expunged_cap) { + newcap = s->move_expunged_cap ? s->move_expunged_cap * 2 : 16; + enew = reallocarray(s->move_expunged, newcap, sizeof(*enew)); + if (enew == NULL) { + log_warn("session %u: realloc move_expunged", s->id); + return; + } + s->move_expunged = enew; + s->move_expunged_cap = newcap; + } + s->move_expunged[s->move_expunged_n++] = *exp; +} + void session_store_dispatch(int fd, short event, void *arg) { - struct session *s = arg; - struct imsg imsg; - ssize_t n; + struct session *s = arg; + struct imsg imsg; + union mbox_reply u; + char buf[32]; + ssize_t n; if (event & EV_WRITE) { if (imsgbuf_write(&s->store_iev->ibuf) == -1) { @@ -119,274 +191,138 @@ session_store_dispatch(int fd, short event, void *arg) break; switch (imsg_get_type(&imsg)) { - case IMSG_MBOX_SELECTED: { - struct imsg_mbox_selected res; - - if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) { - log_warnx("bad IMSG_MBOX_SELECTED"); - break; - } - session_handle_mbox_selected(s, &res); + case IMSG_MBOX_SELECTED: + if (recv_fixed(&imsg, &u.selected, sizeof(u.selected), + "IMSG_MBOX_SELECTED")) + session_handle_mbox_selected(s, &u.selected); break; - } - case IMSG_MBOX_STATUS_RESULT: { - struct imsg_mbox_status_result res; - - if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) { - log_warnx("bad IMSG_MBOX_STATUS_RESULT"); - break; - } - session_handle_mbox_status_result(s, &res); + case IMSG_MBOX_STATUS_RESULT: + if (recv_fixed(&imsg, &u.status, sizeof(u.status), + "IMSG_MBOX_STATUS_RESULT")) + session_handle_mbox_status_result(s, &u.status); break; - } - case IMSG_MBOX_FETCH_HEADER: { - struct imsg_mbox_fetch_header hdr; - - if (imsg_get_buf(&imsg, &hdr, sizeof(hdr)) == -1) { - log_warnx("bad IMSG_MBOX_FETCH_HEADER " - "(header)"); - break; - } - fetch_part_recv(s, &imsg, "IMSG_MBOX_FETCH_HEADER", - hdr.found, hdr.hdrlen, &s->pending_header_buf, - &s->pending_header_len, &s->pending_header_found); + case IMSG_MBOX_FETCH_HEADER: + if (recv_header(&imsg, &u.header, sizeof(u.header), + "IMSG_MBOX_FETCH_HEADER")) + fetch_part_recv(s, &imsg, + "IMSG_MBOX_FETCH_HEADER", u.header.found, + u.header.hdrlen, &s->pending_header_buf, + &s->pending_header_len, + &s->pending_header_found); break; - } - case IMSG_MBOX_FETCH_BODY: { - struct imsg_mbox_fetch_body bodyhdr; - int bodyfd; - - bodyfd = imsg_get_fd(&imsg); - if (s->pending_body_fd != -1) - close(s->pending_body_fd); - s->pending_body_fd = -1; - s->pending_body_off = 0; - s->pending_body_len = 0; - s->pending_body_found = 0; - if (imsg_get_data(&imsg, &bodyhdr, - sizeof(bodyhdr)) == -1) { - log_warnx("bad IMSG_MBOX_FETCH_BODY"); - if (bodyfd != -1) - close(bodyfd); - break; - } - if (!bodyhdr.found || - (bodyhdr.length > 0 && bodyfd == -1)) { - if (bodyhdr.found) - log_warnx("session %u: " - "IMSG_MBOX_FETCH_BODY without its " - "descriptor", s->id); - if (bodyfd != -1) - close(bodyfd); - break; - } - s->pending_body_fd = bodyfd; - s->pending_body_off = bodyhdr.offset; - s->pending_body_len = bodyhdr.length; - s->pending_body_found = 1; + case IMSG_MBOX_FETCH_BODY: + session_recv_fetch_body(s, &imsg); break; - } - case IMSG_MBOX_FETCH_ENVELOPE: { - struct imsg_mbox_fetch_envelope envhdr; - - if (imsg_get_buf(&imsg, &envhdr, sizeof(envhdr)) == - -1) { - log_warnx("bad IMSG_MBOX_FETCH_ENVELOPE " - "(header)"); - break; - } - fetch_part_recv(s, &imsg, "IMSG_MBOX_FETCH_ENVELOPE", - envhdr.found, envhdr.envlen, - &s->pending_envelope_buf, &s->pending_envelope_len, - &s->pending_envelope_found); + case IMSG_MBOX_FETCH_ENVELOPE: + if (recv_header(&imsg, &u.envelope, sizeof(u.envelope), + "IMSG_MBOX_FETCH_ENVELOPE")) + fetch_part_recv(s, &imsg, + "IMSG_MBOX_FETCH_ENVELOPE", + u.envelope.found, u.envelope.envlen, + &s->pending_envelope_buf, + &s->pending_envelope_len, + &s->pending_envelope_found); break; - } - case IMSG_MBOX_FETCH_BODYSTRUCTURE: { - struct imsg_mbox_fetch_bodystructure bshdr; - - if (imsg_get_buf(&imsg, &bshdr, sizeof(bshdr)) == - -1) { - log_warnx("bad IMSG_MBOX_FETCH_BODYSTRUCTURE " - "(header)"); - break; - } - fetch_part_recv(s, &imsg, - "IMSG_MBOX_FETCH_BODYSTRUCTURE", - bshdr.found, bshdr.bslen, - &s->pending_bodystructure_buf, - &s->pending_bodystructure_len, - &s->pending_bodystructure_found); + case IMSG_MBOX_FETCH_BODYSTRUCTURE: + if (recv_header(&imsg, &u.bodystructure, + sizeof(u.bodystructure), + "IMSG_MBOX_FETCH_BODYSTRUCTURE")) + fetch_part_recv(s, &imsg, + "IMSG_MBOX_FETCH_BODYSTRUCTURE", + u.bodystructure.found, + u.bodystructure.bslen, + &s->pending_bodystructure_buf, + &s->pending_bodystructure_len, + &s->pending_bodystructure_found); break; - } - case IMSG_MBOX_FETCH_META: { - struct imsg_mbox_fetch_meta meta; - - if (imsg_get_data(&imsg, &meta, sizeof(meta)) == -1) { - log_warnx("bad IMSG_MBOX_FETCH_META"); + case IMSG_MBOX_FETCH_META: + if (!recv_fixed(&imsg, &u.meta, sizeof(u.meta), + "IMSG_MBOX_FETCH_META")) break; - } - /* imsg_get_data() does not NUL-terminate */ - meta.flags[sizeof(meta.flags) - 1] = '\0'; - /* SELECTING buffers, for RFC 7162 SS3.2.6 order */ + u.meta.flags[sizeof(u.meta.flags) - 1] = '\0'; + /* SELECTING buffers: RFC 7162 section 3.2.6 order */ if (s->state == SESSION_SELECTING) - session_handle_select_fetch(s, &meta); + session_handle_select_fetch(s, &u.meta); else if (s->state == SESSION_STORING) - session_send_store_fetch_response(s, &meta); + session_send_store_fetch_response(s, &u.meta); else - session_send_fetch_response(s, &meta); + session_send_fetch_response(s, &u.meta); break; - } - case IMSG_MBOX_EXPUNGED: { - struct imsg_mbox_expunged exp; - - if (imsg_get_data(&imsg, &exp, sizeof(exp)) == -1) { - log_warnx("bad IMSG_MBOX_EXPUNGED"); + case IMSG_MBOX_EXPUNGED: + if (!recv_fixed(&imsg, &u.expunged, sizeof(u.expunged), + "IMSG_MBOX_EXPUNGED")) break; - } - /* buffered until after COPYUID (RFC 9051 SS6.4.8) */ - if (s->state == SESSION_COPYING) { - if (s->move_expunged_n == - s->move_expunged_cap) { - uint32_t newcap = - s->move_expunged_cap ? - s->move_expunged_cap * 2 : 16; - struct imsg_mbox_expunged *enew = - reallocarray(s->move_expunged, - newcap, sizeof(*enew)); - - if (enew == NULL) { - log_warn("session %u: " - "realloc move_expunged", - s->id); - break; - } - s->move_expunged = enew; - s->move_expunged_cap = newcap; - } - s->move_expunged[s->move_expunged_n++] = exp; - } else - session_send_expunge_response(s, &exp); + if (s->state == SESSION_COPYING) + session_hold_expunged(s, &u.expunged); + else + session_send_expunge_response(s, &u.expunged); break; - } - case IMSG_MBOX_COPY_MAPPING: { - struct imsg_mbox_copy_mapping m; - - if (imsg_get_data(&imsg, &m, sizeof(m)) == -1) { - log_warnx("bad IMSG_MBOX_COPY_MAPPING"); - break; - } - session_handle_mbox_copy_mapping(s, &m); + case IMSG_MBOX_COPY_MAPPING: + if (recv_fixed(&imsg, &u.mapping, sizeof(u.mapping), + "IMSG_MBOX_COPY_MAPPING")) + session_handle_mbox_copy_mapping(s, &u.mapping); break; - } - case IMSG_MBOX_SEARCH_MATCH: { - struct imsg_mbox_search_match m; - - if (imsg_get_data(&imsg, &m, sizeof(m)) == -1) { - log_warnx("bad IMSG_MBOX_SEARCH_MATCH"); - break; - } - session_handle_mbox_search_match(s, &m); + case IMSG_MBOX_SEARCH_MATCH: + if (recv_fixed(&imsg, &u.match, sizeof(u.match), + "IMSG_MBOX_SEARCH_MATCH")) + session_handle_mbox_search_match(s, &u.match); break; - } - case IMSG_MBOX_SELECT_VANISHED: { - struct imsg_mbox_select_vanished v; - - if (imsg_get_data(&imsg, &v, sizeof(v)) == -1) { - log_warnx("bad IMSG_MBOX_SELECT_VANISHED"); + case IMSG_MBOX_SELECT_VANISHED: + if (!recv_fixed(&imsg, &u.vanished, sizeof(u.vanished), + "IMSG_MBOX_SELECT_VANISHED")) break; - } if (s->state == SESSION_SELECTING) - session_handle_select_vanished(s, &v); + session_handle_select_vanished(s, &u.vanished); else - session_handle_fetch_vanished(s, &v); + session_handle_fetch_vanished(s, &u.vanished); break; - } - case IMSG_MBOX_STORE_MODIFIED: { - struct imsg_mbox_store_modified m; - - if (imsg_get_data(&imsg, &m, sizeof(m)) == -1) { - log_warnx("bad IMSG_MBOX_STORE_MODIFIED"); - break; - } - session_handle_store_modified(s, &m); + case IMSG_MBOX_STORE_MODIFIED: + if (recv_fixed(&imsg, &u.modified, sizeof(u.modified), + "IMSG_MBOX_STORE_MODIFIED")) + session_handle_store_modified(s, &u.modified); break; - } - case IMSG_MBOX_VIEW_EXPUNGE: { - struct imsg_mbox_expunged item; - - if (imsg_get_data(&imsg, &item, sizeof(item)) == -1) { - log_warnx("bad IMSG_MBOX_VIEW_EXPUNGE"); - break; - } - session_send_expunge_response(s, &item); + case IMSG_MBOX_VIEW_EXPUNGE: + if (recv_fixed(&imsg, &u.expunged, sizeof(u.expunged), + "IMSG_MBOX_VIEW_EXPUNGE")) + session_send_expunge_response(s, &u.expunged); break; - } - case IMSG_MBOX_VIEW_FETCH: { - struct imsg_mbox_fetch_meta meta; - - if (imsg_get_data(&imsg, &meta, sizeof(meta)) == -1) { - log_warnx("bad IMSG_MBOX_VIEW_FETCH"); + case IMSG_MBOX_VIEW_FETCH: + if (!recv_fixed(&imsg, &u.meta, sizeof(u.meta), + "IMSG_MBOX_VIEW_FETCH")) break; - } - /* same risk as IMSG_MBOX_FETCH_META's flags above */ - meta.flags[sizeof(meta.flags) - 1] = '\0'; - session_handle_view_fetch(s, &meta); + u.meta.flags[sizeof(u.meta.flags) - 1] = '\0'; + session_handle_view_fetch(s, &u.meta); break; - } - case IMSG_MBOX_VIEW_EXISTS: { - struct imsg_mbox_view_exists ve; - char buf[32]; - - if (imsg_get_data(&imsg, &ve, sizeof(ve)) == -1) { - log_warnx("bad IMSG_MBOX_VIEW_EXISTS"); + case IMSG_MBOX_VIEW_EXISTS: + if (!recv_fixed(&imsg, &u.exists, sizeof(u.exists), + "IMSG_MBOX_VIEW_EXISTS")) break; - } - snprintf(buf, sizeof(buf), "%u EXISTS", ve.exists); + snprintf(buf, sizeof(buf), "%u EXISTS", + u.exists.exists); session_untagged(s, buf); break; - } - case IMSG_MBOX_IDLE_REFRESHED: { - struct imsg_mbox_idle_refreshed res; - - if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) { - log_warnx("bad IMSG_MBOX_IDLE_REFRESHED"); - break; - } - session_handle_idle_refreshed(s, &res); + case IMSG_MBOX_IDLE_REFRESHED: + if (recv_fixed(&imsg, &u.refreshed, sizeof(u.refreshed), + "IMSG_MBOX_IDLE_REFRESHED")) + session_handle_idle_refreshed(s, &u.refreshed); break; - } - case IMSG_MBOX_RESULT: { - struct imsg_mbox_result res; - - if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) { - log_warnx("bad IMSG_MBOX_RESULT"); - break; - } - session_handle_mbox_result(s, &res); + case IMSG_MBOX_RESULT: + if (recv_fixed(&imsg, &u.result, sizeof(u.result), + "IMSG_MBOX_RESULT")) + session_handle_mbox_result(s, &u.result); break; - } - case IMSG_MBOX_LIST_ITEM: { - struct imsg_mbox_list_item item; - - if (imsg_get_data(&imsg, &item, sizeof(item)) == -1) { - log_warnx("bad IMSG_MBOX_LIST_ITEM"); + case IMSG_MBOX_LIST_ITEM: + if (!recv_fixed(&imsg, &u.item, sizeof(u.item), + "IMSG_MBOX_LIST_ITEM")) break; - } - /* not NUL-terminated by imsg_get_data() */ - item.mailbox[sizeof(item.mailbox) - 1] = '\0'; - session_handle_mbox_list_item(s, &item); + u.item.mailbox[sizeof(u.item.mailbox) - 1] = '\0'; + session_handle_mbox_list_item(s, &u.item); break; - } - case IMSG_MBOX_APPENDED: { - struct imsg_mbox_appended res; - - if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) { - log_warnx("bad IMSG_MBOX_APPENDED"); - break; - } - session_handle_mbox_appended(s, &res); + case IMSG_MBOX_APPENDED: + if (recv_fixed(&imsg, &u.appended, sizeof(u.appended), + "IMSG_MBOX_APPENDED")) + session_handle_mbox_appended(s, &u.appended); break; - } default: log_debug("session %u: store channel: unhandled %d", s->id, imsg_get_type(&imsg)); @@ -395,18 +331,21 @@ session_store_dispatch(int fd, short event, void *arg) imsg_free(&imsg); } imsgev_rearm_read(s->store_iev); - (void)fd; if (!session_dequeue_next(s)) return; /* s was torn down by a queued LOGOUT, do not touch */ } -/* RFC 9051 SS6.3.2; for QRESYNC: VANISHED, FETCH, tagged OK */ +/* RFC 9051 section 6.3.2; for QRESYNC: VANISHED, FETCH, tagged OK */ void session_handle_mbox_selected(struct session *s, const struct imsg_mbox_selected *res) { - char buf[128]; + char buf[128]; + char qname[MBOX_QUOTED_MAX]; + char listbuf[MBOX_QUOTED_MAX + 64]; + uint32_t i; + int qlen; if (res->error != MBOX_OP_OK || s->qresync_alloc_failed) { /* RFC 5530 NONEXISTENT only when there is no such mailbox */ @@ -452,7 +391,7 @@ session_handle_mbox_selected(struct session *s, session_untagged(s, buf); } - /* RFC 9051 SS6.3.3: EXAMINE gets no PERMANENTFLAGS */ + /* RFC 9051 section 6.3.3: EXAMINE gets no PERMANENTFLAGS */ session_untagged(s, "FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)"); if (s->mbox_readonly) @@ -463,24 +402,17 @@ session_handle_mbox_selected(struct session *s, "OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen " "\\Draft \\*)] System flags and keywords allowed"); - { - char qname[MBOX_QUOTED_MAX]; - char listbuf[MBOX_QUOTED_MAX + 64]; - int qlen; + if (quote_mailbox(qname, sizeof(qname), s->selected_mailbox) == -1) + log_warnx("session %u: SELECT LIST mailbox name truncated", + s->id); + qlen = snprintf(listbuf, sizeof(listbuf), "* LIST () \"/\" %s\r\n", + qname); + if (qlen > 0 && (size_t)qlen < sizeof(listbuf)) + session_write(s, listbuf, (size_t)qlen); + else + log_warnx("session %u: SELECT LIST response did not fit", + s->id); - if (quote_mailbox(qname, sizeof(qname), - s->selected_mailbox) == -1) - log_warnx("session %u: SELECT LIST mailbox name " - "truncated", s->id); - qlen = snprintf(listbuf, sizeof(listbuf), - "* LIST () \"/\" %s\r\n", qname); - if (qlen > 0 && (size_t)qlen < sizeof(listbuf)) - session_write(s, listbuf, (size_t)qlen); - else - log_warnx("session %u: SELECT LIST response did not " - "fit", s->id); - } - if (s->vanished_nranges > 0) { char vbuf[8192]; char full[8192 + 32]; @@ -506,13 +438,8 @@ session_handle_mbox_selected(struct session *s, s->vanished_nranges = 0; s->vanished_cap = 0; - { - uint32_t i; - - for (i = 0; i < s->qresync_nfetches; i++) - session_send_qresync_fetch_response(s, - &s->qresync_fetches[i]); - } + for (i = 0; i < s->qresync_nfetches; i++) + session_send_qresync_fetch_response(s, &s->qresync_fetches[i]); free(s->qresync_fetches); s->qresync_fetches = NULL; s->qresync_nfetches = 0; @@ -527,13 +454,13 @@ session_handle_mbox_selected(struct session *s, "[READ-WRITE] SELECT completed"); } -/* RFC 9051 SS6.3.11; attributes in a fixed order */ +/* RFC 9051 section 6.3.11; attributes in a fixed order */ void session_handle_mbox_status_result(struct session *s, const struct imsg_mbox_status_result *res) { char qname[MBOX_QUOTED_MAX]; - /* RFC 9051 SS4.3-escaped name plus attributes */ + /* RFC 9051 section 4.3-escaped name plus attributes */ char buf[MBOX_QUOTED_MAX + 384]; size_t len; int n, first = 1; @@ -568,7 +495,7 @@ session_handle_mbox_status_result(struct session *s, if (s->status_attrs & STATUS_ATT_MESSAGES) STATUS_APPEND("MESSAGES %u", res->messages); if (s->status_attrs & STATUS_ATT_RECENT) - /* RFC 9051 SS2.3.2 dropped \Recent; always 0 */ + /* RFC 9051 section 2.3.2 dropped \Recent; always 0 */ STATUS_APPEND("RECENT %u", 0U); if (s->status_attrs & STATUS_ATT_UIDNEXT) STATUS_APPEND("UIDNEXT %u", res->uidnext); @@ -624,12 +551,12 @@ session_finish_mbox_op(struct session *s, const struct case MBOX_OP_OK: break; case MBOX_OP_ERR_NO_SUCH_MAILBOX: - /* RFC 5530 SS3 NONEXISTENT */ + /* RFC 5530 section 3 NONEXISTENT */ session_reply(s, s->pending_tag, "NO", "[NONEXISTENT] no such mailbox"); return; case MBOX_OP_ERR_ALREADY_EXISTS: - /* RFC 5530 SS3 ALREADYEXISTS */ + /* RFC 5530 section 3 ALREADYEXISTS */ session_reply(s, s->pending_tag, "NO", "[ALREADYEXISTS] mailbox already exists"); return; @@ -646,12 +573,8 @@ session_finish_mbox_op(struct session *s, const struct if (s->state == SESSION_SELECTED && strcmp(s->selected_mailbox, s->mbox_op_name) == 0) { if (strcmp(cmdname, "RENAME") == 0) { - if (strlcpy(s->selected_mailbox, s->rename_newname, - sizeof(s->selected_mailbox)) >= - sizeof(s->selected_mailbox)) - log_warnx("session %u: selected_mailbox " - "truncated after RENAME, can't happen " - "(both same size)", s->id); + strlcpy(s->selected_mailbox, s->rename_newname, + sizeof(s->selected_mailbox)); } else if (strcmp(cmdname, "DELETE") == 0) { /* the mailbox is gone: deselect, as CLOSE does */ s->state = SESSION_AUTHENTICATED; @@ -676,7 +599,7 @@ session_handle_mbox_list_item(struct session *s, if (!list_pattern_match(s->list_pattern, item->mailbox, 0)) return; - /* RFC 9051 SS7.3.1 attributes */ + /* RFC 9051 section 7.3.1 attributes */ if (!s->list_subscribed_only) attrs = ""; else if (s->list_is_lsub) @@ -722,10 +645,11 @@ session_handle_mbox_copy_mapping(struct session *s, if (s->copy_n == s->copy_cap) { uint32_t newcap = s->copy_cap ? s->copy_cap * 2 : 16; - uint32_t *newsrc = reallocarray(s->copy_src_uids, newcap, - sizeof(uint32_t)); + uint32_t *newsrc; uint32_t *newdest; + newsrc = reallocarray(s->copy_src_uids, newcap, + sizeof(uint32_t)); if (newsrc == NULL) { log_warn("session %u: realloc COPY src array", s->id); s->copy_alloc_failed = 1; @@ -757,9 +681,9 @@ session_handle_select_vanished(struct session *s, if (s->vanished_nranges == s->vanished_cap) { uint32_t newcap = s->vanished_cap ? s->vanished_cap * 2 : 16; - struct vanished_range *n = reallocarray(s->vanished_ranges, - newcap, sizeof(*n)); + struct vanished_range *n; + n = reallocarray(s->vanished_ranges, newcap, sizeof(*n)); if (n == NULL) { log_warn("session %u: realloc VANISHED range array", s->id); @@ -783,9 +707,9 @@ session_handle_select_fetch(struct session *s, uint32_t newcap = s->qresync_fetches_cap ? s->qresync_fetches_cap * 2 : 16; - struct imsg_mbox_fetch_meta *n = reallocarray( - s->qresync_fetches, newcap, sizeof(*n)); + struct imsg_mbox_fetch_meta *n; + n = reallocarray(s->qresync_fetches, newcap, sizeof(*n)); if (n == NULL) { log_warn("session %u: realloc QRESYNC fetch array", s->id); @@ -799,7 +723,7 @@ session_handle_select_fetch(struct session *s, s->qresync_fetches[s->qresync_nfetches++] = *m; } -/* RFC 9051 SS6.3.13: an unsolicited FETCH carries UID */ +/* RFC 9051 section 6.3.13: an unsolicited FETCH carries UID */ void session_handle_view_fetch(struct session *s, const struct imsg_mbox_fetch_meta *meta) @@ -864,13 +788,9 @@ session_idle_poll(int fd, short event, void *arg) { struct session *s = arg; - (void)fd; - (void)event; - if (!s->idling || s->state != SESSION_SELECTED) { - log_debug("session %u: idle poll fired while not idling " - "(idling=%d state=%d), ignored", s->id, s->idling, - (int)s->state); + log_debug("session %u: idle poll while not idling (idling=%d " + "state=%d)", s->id, s->idling, (int)s->state); return; } @@ -895,8 +815,7 @@ session_idle_poll_arm(struct session *s) tv.tv_sec = (time_t)listener_idle_poll_secs; tv.tv_usec = 0; if (evtimer_add(&s->idle_ev, &tv) == -1) - log_warnx("session %u: evtimer_add (idle poll); this IDLE " - "will not be woken until the client sends DONE", s->id); + log_warnx("session %u: evtimer_add (idle poll)", s->id); } void @@ -905,7 +824,7 @@ session_idle_poll_disarm(struct session *s) evtimer_del(&s->idle_ev); } -/* RFC 7162 SS3.2.5.1: always UID, FLAGS and MODSEQ */ +/* RFC 7162 section 3.2.5.1: always UID, FLAGS and MODSEQ */ void session_send_qresync_fetch_response(struct session *s, const struct imsg_mbox_fetch_meta *meta) @@ -918,15 +837,16 @@ session_send_qresync_fetch_response(struct session *s, session_untagged(s, buf); } -/* RFC 7162 SS3.1.3 MODIFIED, worst case per entry */ +/* RFC 7162 section 3.1.3 MODIFIED, worst case per entry */ #define MODIFIED_PER_ENTRY 11 #define MODIFIED_WRAPPER_MAX 160 void session_handle_mbox_result(struct session *s, struct imsg_mbox_result *res) { + const char *cmdname; + char reply[96]; int was_close = 0, was_storing = 0, was_expunging = 0; - const char *cmdname; if (s->state == SESSION_SEARCHING) { session_finish_search(s, res); @@ -947,7 +867,7 @@ session_handle_mbox_result(struct session *s, struct i return; } - /* RFC 9051 SS6.4.9: "UID " */ + /* RFC 9051 section 6.4.9: "UID " */ if (s->state == SESSION_STORING) { cmdname = s->cmd_by_uid ? "UID STORE" : "STORE"; was_storing = 1; @@ -963,13 +883,11 @@ session_handle_mbox_result(struct session *s, struct i s->id, cmdname, res->error == MBOX_OP_OK ? "OK" : "ERROR", res->count); - /* RFC 9051 SS6.4.1: only a successful CLOSE deselects */ + /* RFC 9051 section 6.4.1: only a successful CLOSE deselects */ s->state = (was_close && res->error == MBOX_OP_OK) ? SESSION_AUTHENTICATED : SESSION_SELECTED; if (res->error != MBOX_OP_OK) { - char text[64]; - free(s->store_modified); s->store_modified = NULL; s->store_modified_n = 0; @@ -981,18 +899,18 @@ session_handle_mbox_result(struct session *s, struct i IMAP_BUSY_TEXT); return; } - snprintf(text, sizeof(text), "%s%s failed", + snprintf(reply, sizeof(reply), "%s%s failed", res->error == MBOX_OP_ERR_EXPUNGEISSUED ? "[EXPUNGEISSUED] " : res->error == MBOX_OP_ERR_LIMIT ? "[LIMIT] " : "", cmdname); - session_reply(s, s->pending_tag, "NO", text); + session_reply(s, s->pending_tag, "NO", reply); return; } if (was_storing || was_expunging) s->mbox_highestmodseq = res->highestmodseq; - /* RFC 7162 SS3.1.3 MODIFIED */ + /* RFC 7162 section 3.1.3 MODIFIED */ if (was_storing && (s->store_modified_n > 0 || s->store_modified_alloc_failed)) { char *rbuf = NULL, *text = NULL; @@ -1038,7 +956,7 @@ session_handle_mbox_result(struct session *s, struct i free(rbuf); free(text); - /* RFC 7162 SS3.1.3: an incomplete MODIFIED is refused */ + /* RFC 7162 section 3.1.3: an incomplete MODIFIED is refused */ if (incomplete) { char fail[64]; @@ -1060,35 +978,26 @@ session_handle_mbox_result(struct session *s, struct i s->store_modified_cap = 0; s->store_modified_alloc_failed = 0; - - /* RFC 7162 SS3.2.7, but never on CLOSE (SS3.2.8) */ + /* RFC 7162 section 3.2.7, but never on CLOSE (section 3.2.8) */ if (was_expunging && !was_close && s->condstore_enabled && res->count > 0) { - char text[64]; - - snprintf(text, sizeof(text), "[HIGHESTMODSEQ %llu] %s " + snprintf(reply, sizeof(reply), "[HIGHESTMODSEQ %llu] %s " "completed", (unsigned long long)res->highestmodseq, cmdname); - session_reply(s, s->pending_tag, "OK", text); + session_reply(s, s->pending_tag, "OK", reply); return; } - /* RFC 9051 SS6.4.5 */ + /* RFC 9051 section 6.4.5 */ if (s->fetch_incomplete) { - char text[96]; - s->fetch_incomplete = 0; - snprintf(text, sizeof(text), + snprintf(reply, sizeof(reply), "%s failed, some requested items could not be returned", cmdname); - session_reply(s, s->pending_tag, "NO", text); + session_reply(s, s->pending_tag, "NO", reply); return; } - { - char text[32]; - - snprintf(text, sizeof(text), "%s completed", cmdname); - session_reply(s, s->pending_tag, "OK", text); - } + snprintf(reply, sizeof(reply), "%s completed", cmdname); + session_reply(s, s->pending_tag, "OK", reply); } blob - 591d448412ee231a8ee8bc46eda9c51afb1b6fdf blob + 0e316e857ef102f15f9640ef5cc55c55eefb6c21 --- src/utf8.c +++ src/utf8.c @@ -16,7 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -/* RFC 5198 SS2: UTF-8, no C1 controls, no BOM */ +/* RFC 5198 section 2: UTF-8, no C1 controls, no BOM */ #include "utf8.h" @@ -33,7 +33,7 @@ utf8_mailbox_ok(const char *name) continue; } - /* RFC 3629 SS4's table, one branch per lead-byte class. */ + /* RFC 3629 section 4's table, one branch per lead byte */ if (p[0] >= 0xc2 && p[0] <= 0xdf) { need = 1; lo = 0x80; hi = 0xbf; } else if (p[0] == 0xe0) { @@ -59,11 +59,11 @@ utf8_mailbox_ok(const char *name) if (p[i] < 0x80 || p[i] > 0xbf) return (0); - /* RFC 5198 SS2 item 3: the C1 controls MUST NOT appear. */ + /* RFC 5198 section 2 item 3: no C1 controls */ if (p[0] == 0xc2 && p[1] <= 0x9f) return (0); - /* RFC 5198 SS2 item 5 bans a leading BOM; refused anywhere */ + /* RFC 5198 section 2 item 5 bans a leading BOM; none at all */ if (p[0] == 0xef && p[1] == 0xbb && p[2] == 0xbf) return (0);