commit be72be56ebed8f5a27d7258f8e2c6b5ba106083e from: David Williams date: Wed Sep 30 02:48:12 2026 UTC Unstall accounts, find renamed mail, quieter STATUS, join address fields Change 1 of 6: Find a message renamed while a FETCH or SEARCH walk was paused The store reads cur/ once per command into a sorted snapshot and keeps it while a FETCH or SEARCH walk is paused. A walk pauses so that the account's other sessions can run, and if one of them changed a message's flags meanwhile, the file was renamed and the snapshot still named the old one. The lookup failed, the walk logged "indexed but missing on disk" and left the message out: a SEARCH ALL answered one short, a FETCH skipped a message the client asked for. RFC 9051 section 6.4.4 defines ALL as every message in the mailbox, and RFC 2180 section 4.3 allows a server to omit only messages that have been expunged. locate_message_file() and open_message_file() now treat a snapshot entry whose file has gone as stale: they read cur/ again, once, and look again. A message that really has gone still fails as before. Change 2 of 6: Plan STORE once, and stat only what is read STORE planned every message twice, once to check the whole command could be done before any rename, and again while renaming. It now plans once into the array it already kept, then renames from it; a rename that fails still undoes the ones before it. Each plan looked the file up with two fstatat(2) calls, one for new/ that almost always failed and one for a size that STORE and EXPUNGE never read. locate_message_file() now tries cur/'s snapshot first and new/ only on a miss, and a caller that passes no size gets no stat of the cur/ file. STORE and EXPUNGE read cur/ while holding the exclusive index lock, so nothing in imapd can rename behind them. Change 3 of 6: Let a SEARCH give way to the account's other sessions One process serves every session of an account and runs one command at a time. A SEARCH on keys the index answers, such as flags, never paused, so on a 100,000 message mailbox it held every other session of the account for its whole walk, about 6 seconds. It now yields every 1,000 messages, as it already did every 64 parser requests. Change 1 is what makes the pauses safe. Change 4 of 6: imapd.8: say that a long command delays the account's other sessions STORE, EXPUNGE, COPY and MOVE still run to completion. Before this change a STORE 1:* over 100,000 messages held the account for about 47 seconds, 34 of them in the renames and the index save that keeping flags in the filename requires. Change 5 of 6: Have STATUS write the index only when mail has arrived STATUS registered mail in new/ with its own copy of the scan, then rewrote the whole index, with an fsync(2) and a rename into the mailbox directory, on every call. Clients poll folders with STATUS, so every poll wrote the index, and the rename changed the directory the IDLE refresh watches, so each idling session on that mailbox read the whole index again. The copied scan also lacked the check that skips a new/ name containing ':' or a newline. index_append() refuses such a name, so one oddly named file made STATUS answer NO for a mailbox that SELECT opens; RFC 9051 section 6.3.11 keeps NO for "no status for that name". STATUS now calls refresh_index(), as SELECT does, which saves only when a message was added or the index is new. An error reading new/ now fails STATUS as it fails SELECT. On a 100,000 message mailbox a STATUS took 0.24 seconds before this change and 0.18 after. Change 6 of 6: Join repeated address fields in ENVELOPE, as SEARCH already read them SEARCH FROM, TO, CC and BCC matched an address in any occurrence of the field, but ENVELOPE showed only the first, and RFC 9051 section 6.4.4 defines those keys against the envelope. RFC 5322 section 4.5.3 says repeated To:, Cc: and Bcc: fields SHOULD be read as one list. ENVELOPE now joins every occurrence of each of its six address fields, each occurrence closing its own group, so an empty first Cc: no longer hides a second. RFC 5322 leaves repeated From:, Sender: and Reply-To: unspecified; they are joined too, so no address field has a rule of its own. SEARCH SUBJECT now matches only the first Subject:, the one ENVELOPE shows, as the SENT keys already use only the first Date:. commit - 65c7cf929f78f3280baaf04805e06a3d59b8b4f2 commit + be72be56ebed8f5a27d7258f8e2c6b5ba106083e blob - 5705b36cfc88caba91ffb269193d7c04ba72f507 blob + c59aa80793e00780ac0a04bc9ce5f96d57d19f3a --- 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.1.7 Pre-release, actively developed. Not a port. See [Getting the source](#getting-the-source) below for the repository. +**Status:** 0.1.9 Pre-release, actively developed. Not a port. See [Getting the source](#getting-the-source) below for the repository. ## What it is blob - cd483dbb205734c6ecdc3591bf43d05bdd60f354 blob + 291af09dff0964c50e04f656d1205a7a788e523e --- contrib/imapduser.8 +++ contrib/imapduser.8 @@ -2,7 +2,7 @@ .\" .\" Written for the OpenIMAPD project. Public domain / no rights reserved. .\" -.Dd $Mdocdate: September 28 2026 $ +.Dd $Mdocdate: September 29 2026 $ .Dt IMAPDUSER 8 .Os .Sh NAME blob - 3156bf94c2560eef628e87b9365638328a43164e blob + 8e8b857481d199891feabf48f917a2758c8c1a62 --- src/envelope.c +++ src/envelope.c @@ -39,6 +39,10 @@ static size_t quoted_end(const char *, size_t, size_t); static size_t unquoted_find(const char *, size_t, size_t, const char *); +static int address_list_add(char *, size_t, size_t *, char *, size_t, + int *); +static int envbuf_append_address_fields(char *, size_t, size_t *, + const char *, size_t, const char *, int *); static int envelope_from_header(const char *, size_t, char *, size_t, size_t *); @@ -91,7 +95,6 @@ envbuf_append_nstring(char *buf, size_t bufsize, size_ return (0); fail: - /* all or nothing */ *outlen = save; return (-1); } @@ -292,20 +295,16 @@ address_list_next(char *val, size_t vallen, size_t *po return (ADDR_MAILBOX); } -/* RFC 9051 SS7.5.2 address list, or NIL; val is rewritten in place */ -int -envbuf_append_address_list(char *buf, size_t bufsize, size_t *outlen, - char *val, size_t vallen) +static int +address_list_add(char *buf, size_t bufsize, size_t *outlen, char *val, + size_t vallen, int *any) { struct address a; char *tok; - size_t save = *outlen, pos = 0, toklen; - int any = 0, ingroup = 0, kind; + size_t pos = 0, toklen; + int ingroup = 0, kind; vallen = address_uncomment(val, vallen); - if (envbuf_append(buf, bufsize, outlen, "(", 1) == -1) - return (-1); - while ((kind = address_list_next(val, vallen, &pos, &ingroup, &tok, &toklen)) != 0) { memset(&a, 0, sizeof(a)); @@ -315,16 +314,49 @@ envbuf_append_address_list(char *buf, size_t bufsize, } else if (kind == ADDR_MAILBOX && address_split(tok, toklen, &a) == -1) continue; + if (*any == 0 && + envbuf_append(buf, bufsize, outlen, "(", 1) == -1) + return (-1); + *any = 1; if (envbuf_append_one_address(buf, bufsize, outlen, &a) == -1) return (-1); - any = 1; } + return (0); +} - if (!any) { - *outlen = save; - return (envbuf_append_str(buf, bufsize, outlen, "NIL")); +/* RFC 5322 SS4.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 ulen, i; + int any = 0, rc; + + *seen = 0; + while (header_next_field(hdr, hdrlen, &off, &name, &namelen, &val, + &vallen) == 1) { + if (namelen != flen || strncasecmp(name, field, flen) != 0) + continue; + if ((unf = malloc(vallen + 1)) == NULL) + return (-1); + for (i = ulen = 0; i < vallen; i++) { + if (val[i] == '\r' || val[i] == '\n') + continue; + if (val[i] != ' ' && val[i] != '\t') + *seen = 1; + unf[ulen++] = val[i]; + } + rc = address_list_add(buf, bufsize, outlen, unf, ulen, &any); + free(unf); + if (rc == -1) + return (-1); } - return (envbuf_append(buf, bufsize, outlen, ")", 1)); + if (any) + return (envbuf_append(buf, bufsize, outlen, ")", 1)); + return (envbuf_append_str(buf, bufsize, outlen, "NIL")); } int @@ -349,8 +381,11 @@ static int envelope_from_header(const char *hdr, size_t hdrlen, char *out, size_t outsize, size_t *outlen) { + static const char *const fallback_fields[] = { "Sender", "Reply-To" }; + static const char *const addr_fields[] = { "To", "Cc", "Bcc" }; char from_formatted[ENVELOPE_MAX]; - size_t from_len = 0; + size_t from_len = 0, fi, save; + int seen; if (envbuf_append(out, outsize, outlen, "(", 1) == -1) return (-1); @@ -366,86 +401,37 @@ envelope_from_header(const char *hdr, size_t hdrlen, c if (envbuf_append(out, outsize, outlen, " ", 1) == -1) return (-1); - { - char *val; - size_t vallen; - - if (extract_header_field(hdr, hdrlen, "From", &val, - &vallen) == 0) { - int rc = envbuf_append_address_list(from_formatted, - sizeof(from_formatted), &from_len, val, vallen); - free(val); - if (rc == -1) - return (-1); - } else { - if (envbuf_append_str(from_formatted, - sizeof(from_formatted), &from_len, "NIL") == -1) - return (-1); - } - } + if (envbuf_append_address_fields(from_formatted, + sizeof(from_formatted), &from_len, hdr, hdrlen, "From", + &seen) == -1) + return (-1); if (envbuf_append(out, outsize, outlen, from_formatted, from_len) == -1) return (-1); if (envbuf_append(out, outsize, outlen, " ", 1) == -1) return (-1); - { - static const char *const fallback_fields[] = - { "Sender", "Reply-To" }; - size_t fi; - - for (fi = 0; fi < 2; fi++) { - char *val; - size_t vallen; - int used_value = 0; - - if (extract_header_field(hdr, hdrlen, - fallback_fields[fi], &val, &vallen) == 0) { - if (vallen > 0) { - int rc = envbuf_append_address_list( - out, outsize, outlen, val, - vallen); - used_value = 1; - free(val); - if (rc == -1) - return (-1); - } else - free(val); - } - if (!used_value && - envbuf_append(out, outsize, outlen, + for (fi = 0; fi < 2; fi++) { + save = *outlen; + if (envbuf_append_address_fields(out, outsize, outlen, hdr, + hdrlen, fallback_fields[fi], &seen) == -1) + return (-1); + if (!seen) { + *outlen = save; + if (envbuf_append(out, outsize, outlen, from_formatted, from_len) == -1) return (-1); - if (envbuf_append(out, outsize, outlen, - " ", 1) == -1) - return (-1); } + if (envbuf_append(out, outsize, outlen, " ", 1) == -1) + return (-1); } - { - static const char *const addr_fields[] = { "To", "Cc", "Bcc" }; - size_t fi; - - for (fi = 0; fi < 3; fi++) { - char *val; - size_t vallen; - - if (extract_header_field(hdr, hdrlen, - addr_fields[fi], &val, &vallen) == 0) { - int rc = envbuf_append_address_list(out, - outsize, outlen, val, vallen); - free(val); - if (rc == -1) - return (-1); - } else { - if (envbuf_append_str(out, outsize, outlen, - "NIL") == -1) - return (-1); - } - if (envbuf_append(out, outsize, outlen, - " ", 1) == -1) - return (-1); - } + for (fi = 0; fi < 3; fi++) { + if (envbuf_append_address_fields(out, outsize, outlen, hdr, + hdrlen, addr_fields[fi], &seen) == -1) + return (-1); + if (envbuf_append(out, outsize, outlen, " ", 1) == -1) + return (-1); } if (append_field_nstring(out, outsize, outlen, hdr, hdrlen, blob - 89cc5d2c5094633ba2e50057c592a262ca6ef0ec blob + 53289376fe16e21ab8f9917fe0f74f8ccbb29f0b --- 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: September 28 2026 $ +.Dd $Mdocdate: September 29 2026 $ .Dt IMAPD 8 .Os .Sh NAME @@ -972,3 +972,18 @@ afterwards is refused until it selects a mailbox again served .Li INBOX under the deleted mailbox's name. +.Pp +All sessions of one account are served by one process, which runs one +command at a time. +.Li FETCH +and +.Li SEARCH +give way to the account's other sessions as they go; +.Li STORE , +.Li EXPUNGE , +.Li COPY +and +.Li MOVE +do not. +One of these over a very large mailbox therefore delays every other +session of the same account until it completes. blob - 6eb1a0411a611331b59b79c7139120c4135ecfc8 blob + 39657eccd76572179da9fd1272ca3f9e4935ea00 --- src/imapd.h +++ src/imapd.h @@ -28,7 +28,7 @@ #include #include -#define IMAPD_VERSION "0.1.8" +#define IMAPD_VERSION "0.1.9" #define IMAPD_USER "_imapd" #define IMAPD_AUTH_USER "_imapauth" blob - 3088dce4bc163aa0100848cfe302e99edd45c2d5 blob + 24b638d4b47dfecd0463e58273e3821e1b60219a --- src/mbox_fetch.c +++ src/mbox_fetch.c @@ -20,7 +20,6 @@ #include #include -#include #include #include #include @@ -608,8 +607,6 @@ handle_mbox_status(struct imsg_mbox_status *req, struc struct mbox_index idx; struct imsg_mbox_status_result reply; struct index_lock il = INDEX_LOCK_INIT; - DIR *dp; - struct dirent *de; const char *target; int tfd = -1, locked; @@ -642,55 +639,12 @@ handle_mbox_status(struct imsg_mbox_status *req, struc goto send; } - if (index_load(il.fd, &idx) == -1) { + if (refresh_index(tfd, &idx, il.fd) == -1) { index_lock_release(&il); reply.error = MBOX_OP_ERR_GENERIC; goto send; } - /* STATUS may be first to touch a mailbox: persist a fresh header */ - if (idx.fresh && index_save(tfd, &idx) == -1) - log_warnx("session %u: STATUS: could not persist the new " - "index header", session_id); - - { - int newfd; - - dp = NULL; - newfd = openat(tfd, "new", - O_RDONLY | O_DIRECTORY); - if (newfd != -1 && (dp = fdopendir(newfd)) == NULL) - close(newfd); - } - if (dp == NULL) { - if (errno != ENOENT) - log_warn("session %u: opendir new", session_id); - } else { - while ((de = readdir(dp)) != NULL) { - if (de->d_name[0] == '.') - continue; - if (index_has_basename(&idx, de->d_name)) - continue; - if (index_append(&idx, idx.uidnext, de->d_name) - == -1) { - closedir(dp); - index_free(&idx); - index_lock_release(&il); - reply.error = MBOX_OP_ERR_GENERIC; - goto send; - } - idx.uidnext++; - } - closedir(dp); - } - - if (index_save(tfd, &idx) == -1) { - index_free(&idx); - index_lock_release(&il); - reply.error = MBOX_OP_ERR_GENERIC; - goto send; - } - reply.error = MBOX_OP_OK; reply.messages = (uint32_t)idx.nlines; reply.uidnext = idx.uidnext; blob - fb8ba5603a5446fd95335c1e1ef8654a5f73daf2 blob + 40b33bfac7582be921b04b3c91cd3267464ec215 --- src/mbox_search.c +++ src/mbox_search.c @@ -140,8 +140,9 @@ merge_keywords(int mode, const char *old_kws, const ch /* A content key's value before the parser-worker has answered */ #define SEARCH_UNKNOWN 2 -/* Parser requests a SEARCH makes before it lets other sessions run */ +/* Parser requests, or messages, a SEARCH takes before others run */ #define SEARCH_YIELD_REQUESTS 64 +#define SEARCH_YIELD_MESSAGES 1000 static void search_walk_step(struct store_session *); static void search_walk_finish(struct store_session *, int); @@ -427,6 +428,14 @@ search_walk_step(struct store_session *ss) char suffix[64]; int r; + if (++sw->walked > SEARCH_YIELD_MESSAGES) { + sw->walked = 0; + sw->next = i; + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_add(&sw->yield_ev, &tv); + return; + } if (index_parse_line(sw->idx.lines[i - 1], &rec) == -1) continue; blob - 4061bcf4257fbef8e964f7f25980a2576a5360c8 blob + 4df03f1fb49ab72829be8f927238bad22709c4f4 --- src/mbox_store.c +++ src/mbox_store.c @@ -45,6 +45,7 @@ struct store_step { int in_new; char oldsuffix[64]; char letters[8]; + char *newline; }; static int @@ -54,12 +55,11 @@ store_plan(struct store_session *ss, const struct imsg { char suffix[64], newkeywords[MBOX_FLAGS_MAX]; const char *lp; - off_t size; uint32_t old_sysflags, new_sysflags; int len; if (locate_message_file(&ss->cur_snap, ss->mailbox_dir_fd, - rec->basename, &size, suffix, sizeof(suffix)) == -1) { + rec->basename, NULL, suffix, sizeof(suffix)) == -1) { log_warnx("session %u: message %s (uid %u) indexed but missing " "on disk, skipped", session_id, rec->basename, rec->uid); return (0); @@ -158,92 +158,105 @@ store_apply(struct store_session *ss, const struct ims struct store_step **stepsp, size_t *nstepsp) { struct store_step *steps = NULL, *grown; - size_t nsteps = 0, maxsteps = 0; + size_t nsteps = 0, maxsteps = 0, applied, k; uint32_t i, max_hi; uint64_t new_modseq = idx->highestmodseq + 1; - int ok = 1, changed = 0, pass; + int ok = 1, changed = 0; max_hi = seqset_max_hi(resolved, nresolved); - for (pass = 0; pass < 2 && ok; pass++) { - for (i = 1; i <= (uint32_t)idx->nlines; i++) { - struct index_rec rec; - struct store_step st; - enum seqset_pos pos; - char newline[STORE_INDEX_LINE_MAX]; - char oldpath[600], newpath[600]; - char *dup; - int rc; + for (i = 1; i <= (uint32_t)idx->nlines; i++) { + struct index_rec rec; + struct store_step st; + enum seqset_pos pos; + char newline[STORE_INDEX_LINE_MAX]; + char oldpath[600], newpath[600]; + int rc; - if (index_parse_line(idx->lines[i - 1], &rec) == -1) + if (index_parse_line(idx->lines[i - 1], &rec) == -1) + continue; + pos = seqset_position(resolved, nresolved, max_hi, by_uid, + rec.uid, i); + if (pos == SEQSET_PAST_END) + break; + if (pos == SEQSET_SKIP || + (since != NULL && rec.modseq <= *since)) + continue; + + memset(&st, 0, sizeof(st)); + st.seqno = i; + if (req->has_unchangedsince && + rec.modseq > req->unchangedsince) { + st.modified = 1; + st.modseq = rec.modseq; + } else { + rc = store_plan(ss, req, &rec, new_modseq, &st, + newline, sizeof(newline)); + if (rc == 0) continue; - pos = seqset_position(resolved, nresolved, max_hi, - by_uid, rec.uid, i); - if (pos == SEQSET_PAST_END) + if (rc == -1 || (st.rename && store_paths(rec.basename, + &st, oldpath, sizeof(oldpath), newpath, + sizeof(newpath)) == -1)) { + ok = 0; break; - if (pos == SEQSET_SKIP || - (since != NULL && rec.modseq <= *since)) - continue; - - memset(&st, 0, sizeof(st)); - st.seqno = i; - if (req->has_unchangedsince && - rec.modseq > req->unchangedsince) { - st.modified = 1; - st.modseq = rec.modseq; - } else { - rc = store_plan(ss, req, &rec, new_modseq, &st, - newline, sizeof(newline)); - if (rc == 0) - continue; - if (rc == -1 || (st.rename && - store_paths(rec.basename, &st, oldpath, - sizeof(oldpath), newpath, - sizeof(newpath)) == -1)) { - ok = 0; - break; - } } - if (pass == 0) - continue; - - if (nsteps == maxsteps) { - size_t newmax = maxsteps ? maxsteps * 2 : 64; - - if ((grown = reallocarray(steps, newmax, - sizeof(*steps))) == NULL) { - log_warn("session %u: STORE: " - "reallocarray", session_id); - ok = 0; - break; - } - steps = grown; - maxsteps = newmax; - } - if (st.modified) { - steps[nsteps++] = st; - continue; - } - if ((dup = strdup(newline)) == NULL) { + if ((st.newline = strdup(newline)) == NULL) { log_warn("session %u: strdup index line", session_id); ok = 0; break; } - if (st.rename && renameat(ss->mailbox_dir_fd, oldpath, + } + + if (nsteps == maxsteps) { + size_t newmax = maxsteps ? maxsteps * 2 : 64; + + if ((grown = reallocarray(steps, newmax, + sizeof(*steps))) == NULL) { + log_warn("session %u: STORE: reallocarray", + session_id); + free(st.newline); + ok = 0; + break; + } + steps = grown; + maxsteps = newmax; + } + steps[nsteps++] = st; + } + + for (applied = 0; ok && applied < nsteps; applied++) { + struct store_step *st = &steps[applied]; + struct index_rec rec; + char oldpath[600], newpath[600]; + + if (st->modified) + continue; + if (st->rename) { + if (index_parse_line(idx->lines[st->seqno - 1], + &rec) == -1 || store_paths(rec.basename, st, + oldpath, sizeof(oldpath), newpath, + sizeof(newpath)) == -1) { + ok = 0; + break; + } + if (renameat(ss->mailbox_dir_fd, oldpath, ss->mailbox_dir_fd, newpath) == -1) { log_warn("session %u: rename %s -> %s", session_id, oldpath, newpath); - free(dup); ok = 0; break; } - steps[nsteps++] = st; - free(idx->lines[i - 1]); - idx->lines[i - 1] = dup; - if (st.changed) - changed = 1; } + free(idx->lines[st->seqno - 1]); + idx->lines[st->seqno - 1] = st->newline; + st->newline = NULL; + if (st->changed) + changed = 1; } + for (k = 0; k < nsteps; k++) { + free(steps[k].newline); + steps[k].newline = NULL; + } if (ok && changed) { idx->highestmodseq = new_modseq; @@ -251,7 +264,7 @@ store_apply(struct store_session *ss, const struct ims ok = 0; } if (!ok) - store_undo(ss, idx, steps, nsteps); + store_undo(ss, idx, steps, applied); else if (synced && changed && new_modseq > ss->idle_baseline.modseq) /* LOCK_EX held since the sync: new_modseq is ours alone */ ss->idle_baseline.modseq = new_modseq; @@ -426,14 +439,13 @@ handle_mbox_expunge(struct imsg_mbox_expunge *req, const char *lp; uint32_t sysflags; char suffix[64]; - off_t size; if (index_parse_line(idx.lines[in], &rec) == -1) { idx.lines[out++] = idx.lines[in]; continue; } if (locate_message_file(&ss->cur_snap, ss->mailbox_dir_fd, - rec.basename, &size, suffix, sizeof(suffix)) == -1) { + 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); blob - 0efcbefdf39129fc1141c56af54fa50edb9a656c blob + a7f71c2b4615291f9471cd67ab552d19672ed753 --- src/mime.c +++ src/mime.c @@ -195,10 +195,30 @@ locate_message_file(struct cur_snapshot *snap, int dfd off_t *size_out, char *suffix_out, size_t suffix_out_size) { struct stat st; + const char *hit; char path[600]; + size_t baselen = strlen(basename); suffix_out[0] = '\0'; + if ((hit = cur_snapshot_find(snap, dfd, basename, baselen)) != NULL) { + if (cur_entry_take(hit, baselen, path, sizeof(path), + suffix_out, suffix_out_size) == -1) + return (-1); + /* no size wanted: STORE and EXPUNGE read cur/ under LOCK_EX */ + if (size_out == NULL) + return (0); + if (fstatat(dfd, path, &st, 0) == 0) { + *size_out = st.st_size; + return (0); + } + if (errno != ENOENT) + return (-1); + /* renamed since cur/ was read: read it again */ + cur_snapshot_discard(snap); + suffix_out[0] = '\0'; + } + if (snprintf(path, sizeof(path), "new/%s", basename) >= (int)sizeof(path)) { log_warnx("session %u: basename too long: %s", session_id, @@ -206,7 +226,8 @@ locate_message_file(struct cur_snapshot *snap, int dfd return (-1); } if (fstatat(dfd, path, &st, 0) == 0) { - *size_out = st.st_size; + if (size_out != NULL) + *size_out = st.st_size; return (0); } if (errno != ENOENT) { @@ -217,6 +238,8 @@ locate_message_file(struct cur_snapshot *snap, int dfd if (scan_cur_for_basename(snap, dfd, basename, path, sizeof(path), suffix_out, suffix_out_size) == -1) return (-1); + if (size_out == NULL) + return (0); if (fstatat(dfd, path, &st, 0) == -1) return (-1); *size_out = st.st_size; @@ -245,6 +268,12 @@ open_message_file(struct cur_snapshot *snap, int dfd, if (scan_cur_for_basename(snap, dfd, basename, path, sizeof(path), NULL, 0) == -1) return (-1); + if ((fd = openat(dfd, path, O_RDONLY)) != -1 || errno != ENOENT) + return (fd); + cur_snapshot_discard(snap); + if (scan_cur_for_basename(snap, dfd, basename, path, sizeof(path), + NULL, 0) == -1) + return (-1); return (openat(dfd, path, O_RDONLY)); } blob - 83080cc8b4803a2e5695bc813d3e09df2b69d5dd blob + 0fc76f71c80271d4856ee9038dcac60a2f275f4f --- src/search_match.c +++ src/search_match.c @@ -488,12 +488,12 @@ match_leaf(const char *hdr, size_t hdrlen, return (0); } + /* only the first Date: or Subject: counts, as ENVELOPE shows it */ while (!hit && header_next_field(hdr, hdrlen, &off, &name, &namelen, &val, &vallen) == 1) { if (namelen != wantlen || strncasecmp(name, want, wantlen) != 0) continue; if (date) { - /* the first Date: decides; RFC 5322 SS3.6 allows one */ if (date_field_day(val, vallen, &day) == -1) return (0); if (l->op == SEARCH_OP_SENTBEFORE) @@ -513,6 +513,8 @@ match_leaf(const char *hdr, size_t hdrlen, return (-1); hit = ci_contains(dec, dlen, pool + l->str_off, l->str_len); free(dec); + if (l->op == SEARCH_OP_SUBJECT) + break; } return (hit); } blob - 3c83389b2eb5b76d0b40c888d57458d13bb30233 blob + 48f76022adc5e6e524a657288cc45ed786e32ccd --- src/store_internal.h +++ src/store_internal.h @@ -74,7 +74,7 @@ struct store_fetch_walk { uint64_t seen_modseq; }; -/* RFC 9051 SS6.4.4 SEARCH, yielding every SEARCH_YIELD_REQUESTS */ +/* RFC 9051 SS6.4.4 SEARCH, yielding to the account's other sessions */ struct store_search_walk { int active; int wait_parser; /* paused until one comes */ @@ -92,6 +92,7 @@ struct store_search_walk { uint32_t next; /* index line to resume at */ uint32_t sent; uint32_t asked; + uint32_t walked; }; /* two stat(2) calls, no lock: "." and "new" */ @@ -284,8 +285,6 @@ int envbuf_append_nstring(char *, size_t, size_t *, c size_t); int envbuf_append_one_address(char *, size_t, size_t *, const struct address *); -int envbuf_append_address_list(char *, size_t, size_t *, char *, - size_t); int append_field_nstring(char *, size_t, size_t *, const char *, size_t, const char *); int build_envelope(int, const char *, char **, uint32_t *);