commit - f940a630e00423a0043b84fbe5affd05ea548d6d
commit + 65c7cf929f78f3280baaf04805e06a3d59b8b4f2
blob - 8c9c9862d382bfe7df46a4cc864956499b97ce3a
blob + 5705b36cfc88caba91ffb269193d7c04ba72f507
--- README.md
+++ README.md
Every directive is documented inline in the sample file; the full reference is in `imapd(8)`'s FILES section.
+## Creating the daemon accounts
+
+imapd drops privileges to three accounts of its own and will not start until all three exist. Create them once, before the first mailbox account, since `imapduser(8)` gives the credentials file to the `_imapauth` group:
+
+```
+doas useradd -c "IMAP Daemon" -d /var/empty -s /sbin/nologin _imapd
+doas useradd -c "IMAP Auth" -d /var/empty -s /sbin/nologin _imapauth
+doas useradd -c "IMAP Key Manager" -d /var/empty -s /sbin/nologin _imapkey
+```
+
+Each runs in a group of its own name, which `useradd(8)` creates by default unless `/etc/usermgmt.conf` sets `group` otherwise.
+
## Creating an account
imapd's users aren't real system accounts, `imapduser(8)` manages a bespoke credentials file (`username:passwordhash:uid:gid:maildir`, bcrypt via `crypt_checkpass(3)`) and the matching maildir ownership together, since no combination of `useradd(8)`/`userdel(8)` can safely keep both in sync:
blob - 3011ab2b68aa384025c3db089fbdca6bfd0a28ad
blob + 38f2e42d3962909ce6815417a407d690a860f0f2
--- contrib/imapd-teardown
+++ contrib/imapd-teardown
echo " system account $_acct ($_label)"
else
userdel "$_acct" 2>/dev/null || true
- # useradd's own default (no -g given) creates a same-
- # named group alongside the account, confirmed
- # against the pre-rename _openimap/_openimapd
- # accounts. userdel(8) itself makes no mention of
- # touching groups, so that group is cleaned up
- # separately here, guarded against already being gone.
+ # useradd(8) makes a same-named group by default and
+ # userdel(8) leaves a primary group behind.
groupdel "$_acct" 2>/dev/null || true
echo "${0##*/}: removed account $_acct (and its group, if any)" 1>&2
fi
do_rm "TLS private key" "$TLS_KEY" f
do_userdel "auth's privilege-drop account" "_imapauth"
do_userdel "listener's privilege-drop account" "_imapd"
+ do_userdel "keymgr's privilege-drop account" "_imapkey"
if [ "$WIPE_MAIL" -eq 1 ]; then
do_rm "mail spool -- REAL MAIL DATA" "$SPOOL_ROOT" r
fi
blob - 60bed43482a76e1603b8d438342657969d72b840
blob + cd483dbb205734c6ecdc3591bf43d05bdd60f354
--- contrib/imapduser.8
+++ contrib/imapduser.8
.\"
.\" Written for the OpenIMAPD project. Public domain / no rights reserved.
.\"
-.Dd $Mdocdate: September 27 2026 $
+.Dd $Mdocdate: September 28 2026 $
.Dt IMAPDUSER 8
.Os
.Sh NAME
blob - f7a13b2a677ece560279a205ca1d8a83d19722fd
blob + 7e0c578730502999d1136d4258eab53efa0c9d4b
--- src/append_cmd.c
+++ src/append_cmd.c
session_handle_mbox_appended(struct session *s,
const struct imsg_mbox_appended *res)
{
- int appended_to_selected;
-
s->state = s->append_prev_state;
if (res->error != MBOX_OP_OK) {
return;
}
- /* RFC 9051 SS5.1: INBOX is case-insensitive */
- if (mailbox_name_is_inbox(s->append_mailbox) &&
- mailbox_name_is_inbox(s->selected_mailbox))
- appended_to_selected = 1;
- else
- appended_to_selected =
- (strcmp(s->append_mailbox, s->selected_mailbox) == 0);
-
-
- if (s->append_prev_state == SESSION_SELECTED && appended_to_selected) {
- char buf[32];
-
- snprintf(buf, sizeof(buf), "%u EXISTS", res->exists);
- session_untagged(s, buf);
- }
-
{
char buf[96];
blob - 15fc87a7f400101a1cce03f1c440bc849107d673
blob + a9edf8444f61eb110fa9d62bde920efa5aa13ed5
--- src/auth.c
+++ src/auth.c
init.cred_file[sizeof(init.cred_file) - 1] = '\0';
imsg_free(&imsg);
- if ((pw = getpwnam("_imapauth")) == NULL)
- fatalx("getpwnam _imapauth: no such user "
- "(expected, not yet provisioned by an install script)");
+ if ((pw = getpwnam(IMAPD_AUTH_USER)) == NULL)
+ fatalx("unknown user %s", IMAPD_AUTH_USER);
if (strlcpy(chrootdir, init.cred_file, sizeof(chrootdir)) >=
sizeof(chrootdir))
blob - 9bdf08c3065723587c43f234b227be200861d536
blob + 295dbfe792a76217279f47edda320d56449332aa
--- src/auth_cmd.c
+++ src/auth_cmd.c
cmd_noop(struct session *s, const char *tag, char *args)
{
(void)args;
- session_reply(s, tag, "OK", "NOOP completed");
+
+ /* RFC 9051 SS6.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) {
+ session_reply(s, tag, "OK", "NOOP completed");
+ return (1);
+ }
+ s->refresh_status = "OK";
+ s->refresh_text = "NOOP completed";
return (1);
}
int
session_handle_idle_continuation(struct session *s, const char *line)
{
+ int done;
+
s->idling = 0;
+ s->idle_refresh_again = 0;
session_idle_poll_disarm(s);
- if (strcasecmp(line, "DONE") != 0) {
- session_reply(s, s->pending_tag, "BAD",
- "expected DONE");
- return (1);
+ 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 */
+ if (!s->idle_refresh_pending) {
+ session_reply(s, s->pending_tag, s->refresh_status,
+ s->refresh_text);
+ s->refresh_status = NULL;
}
-
- session_reply(s, s->pending_tag, "OK", "IDLE terminated");
return (1);
}
blob - a5728b7e6970463d7e16080c594c7fb8719cb862
blob + 3156bf94c2560eef628e87b9365638328a43164e
--- src/envelope.c
+++ src/envelope.c
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 envelope_from_header(const char *, size_t, char *, size_t,
+ size_t *);
int
envbuf_append(char *buf, size_t bufsize, size_t *outlen, const char *data,
int
append_field_nstring(char *out, size_t outsize, size_t *outlen,
- const char *hdrbuf, uint32_t hdrlen, const char *name)
+ const char *hdrbuf, size_t hdrlen, const char *name)
{
char *val;
size_t vallen;
}
/* RFC 9051 SS7.5.2 ENVELOPE; Sender and Reply-To default to From */
-int
-build_envelope(int fd, const char *basename, char **buf_out,
- uint32_t *len_out)
+static int
+envelope_from_header(const char *hdr, size_t hdrlen, char *out,
+ size_t outsize, size_t *outlen)
{
- char *hdrbuf = NULL;
- uint32_t hdrlen = 0;
- char out[ENVELOPE_MAX];
- size_t outlen = 0;
char from_formatted[ENVELOPE_MAX];
size_t from_len = 0;
- *buf_out = NULL;
- *len_out = 0;
-
- if (read_header_from_fd(fd, basename, &hdrbuf, &hdrlen) == -1)
+ if (envbuf_append(out, outsize, outlen, "(", 1) == -1)
return (-1);
- if (envbuf_append(out, sizeof(out), &outlen, "(", 1) == -1)
- goto fail;
-
- if (append_field_nstring(out, sizeof(out), &outlen, hdrbuf, hdrlen,
+ if (append_field_nstring(out, outsize, outlen, hdr, hdrlen,
"Date") == -1)
- goto fail;
- if (envbuf_append(out, sizeof(out), &outlen, " ", 1) == -1)
- goto fail;
- if (append_field_nstring(out, sizeof(out), &outlen, hdrbuf, hdrlen,
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
+ return (-1);
+ if (append_field_nstring(out, outsize, outlen, hdr, hdrlen,
"Subject") == -1)
- goto fail;
- if (envbuf_append(out, sizeof(out), &outlen, " ", 1) == -1)
- goto fail;
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
+ return (-1);
{
char *val;
size_t vallen;
- if (extract_header_field(hdrbuf, hdrlen, "From", &val,
+ 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)
- goto fail;
+ return (-1);
} else {
if (envbuf_append_str(from_formatted,
sizeof(from_formatted), &from_len, "NIL") == -1)
- goto fail;
+ return (-1);
}
}
- if (envbuf_append(out, sizeof(out), &outlen, from_formatted,
+ if (envbuf_append(out, outsize, outlen, from_formatted,
from_len) == -1)
- goto fail;
- if (envbuf_append(out, sizeof(out), &outlen, " ", 1) == -1)
- goto fail;
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
+ return (-1);
{
static const char *const fallback_fields[] =
size_t vallen;
int used_value = 0;
- if (extract_header_field(hdrbuf, hdrlen,
+ if (extract_header_field(hdr, hdrlen,
fallback_fields[fi], &val, &vallen) == 0) {
if (vallen > 0) {
int rc = envbuf_append_address_list(
- out, sizeof(out), &outlen, val,
+ out, outsize, outlen, val,
vallen);
used_value = 1;
free(val);
if (rc == -1)
- goto fail;
+ return (-1);
} else
free(val);
}
if (!used_value &&
- envbuf_append(out, sizeof(out), &outlen,
+ envbuf_append(out, outsize, outlen,
from_formatted, from_len) == -1)
- goto fail;
- if (envbuf_append(out, sizeof(out), &outlen,
+ return (-1);
+ if (envbuf_append(out, outsize, outlen,
" ", 1) == -1)
- goto fail;
+ return (-1);
}
}
char *val;
size_t vallen;
- if (extract_header_field(hdrbuf, hdrlen,
+ if (extract_header_field(hdr, hdrlen,
addr_fields[fi], &val, &vallen) == 0) {
int rc = envbuf_append_address_list(out,
- sizeof(out), &outlen, val, vallen);
+ outsize, outlen, val, vallen);
free(val);
if (rc == -1)
- goto fail;
+ return (-1);
} else {
- if (envbuf_append_str(out, sizeof(out),
- &outlen, "NIL") == -1)
- goto fail;
+ if (envbuf_append_str(out, outsize, outlen,
+ "NIL") == -1)
+ return (-1);
}
- if (envbuf_append(out, sizeof(out), &outlen,
+ if (envbuf_append(out, outsize, outlen,
" ", 1) == -1)
- goto fail;
+ return (-1);
}
}
- if (append_field_nstring(out, sizeof(out), &outlen, hdrbuf, hdrlen,
+ if (append_field_nstring(out, outsize, outlen, hdr, hdrlen,
"In-Reply-To") == -1)
- goto fail;
- if (envbuf_append(out, sizeof(out), &outlen, " ", 1) == -1)
- goto fail;
- if (append_field_nstring(out, sizeof(out), &outlen, hdrbuf, hdrlen,
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
+ return (-1);
+ if (append_field_nstring(out, outsize, outlen, hdr, hdrlen,
"Message-Id") == -1)
- goto fail;
+ return (-1);
- if (envbuf_append(out, sizeof(out), &outlen, ")", 1) == -1)
- goto fail;
+ return (envbuf_append(out, outsize, outlen, ")", 1));
+}
+int
+build_envelope(int fd, const char *basename, char **buf_out,
+ uint32_t *len_out)
+{
+ char *hdrbuf = NULL;
+ uint32_t hdrlen = 0;
+ char out[ENVELOPE_MAX];
+ size_t outlen = 0;
+
+ *buf_out = NULL;
+ *len_out = 0;
+
+ if (read_header_from_fd(fd, basename, &hdrbuf, &hdrlen) == -1)
+ 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);
+ free(hdrbuf);
+ return (-1);
+ }
free(hdrbuf);
if ((*buf_out = malloc(outlen)) == NULL) {
memcpy(*buf_out, out, outlen);
*len_out = (uint32_t)outlen;
return (0);
-
-fail:
- log_warnx("session %u: message %s: formatted ENVELOPE exceeds "
- "ENVELOPE_MAX, ENVELOPE skipped", session_id, basename);
- free(hdrbuf);
- return (-1);
}
/* RFC 9051 SS7.5.2 BODYSTRUCTURE; no extension data */
return (envbuf_append(out, outsize, outlen, ")", 1));
}
- if (strcasecmp(type, "MESSAGE") == 0 &&
- (strcasecmp(subtype, "RFC822") == 0 ||
- strcasecmp(subtype, "GLOBAL") == 0))
- /* MESSAGE/RFC822 and MESSAGE/GLOBAL are not supported */
- return (-1);
-
{
char *idval = NULL, *descval = NULL, *encval = NULL;
- size_t idlen = 0, desclen = 0, enclen = 0;
+ 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;
if (envbuf_append(out, outsize, outlen, "(", 1) == -1)
return (-1);
}
- if (is_text) {
+ /* 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];
blob - fa0815a7ea209ae6c3da6c3e18505aba39d017ca
blob + d2ffc23c6531d1c39bc449b58c991c54b69674d8
--- src/fetch_cmd.c
+++ src/fetch_cmd.c
}
static int
-parse_body_peek_section_tok(const char *tok, uint32_t *attrs_inout,
+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)
{
- const char *bracket_start = tok + strlen("BODY.PEEK[");
char *close;
char inner[SECTION_PART_MAX];
const char *suffix;
}
static int
-parse_body_peek_header_fields_tok(const char *tok, uint32_t *attrs_inout,
+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)
{
- size_t toklen = strlen(tok);
+ size_t len = strlen(sec);
char inner[HEADER_FIELDS_LABEL_MAX];
- if (toklen < strlen("BODY.PEEK[") + 1 || tok[toklen - 1] != ']') {
+ if (len < 1 || sec[len - 1] != ']') {
*errmsg = "malformed HEADER.FIELDS section";
return (-1);
}
/* already captured one, ignore any further duplicates */
return (0);
- if (toklen - strlen("BODY.PEEK[") - 1 >= sizeof(inner)) {
+ if (len - 1 >= sizeof(inner)) {
*errmsg = "HEADER.FIELDS section too long";
return (-1);
}
- memcpy(inner, tok + strlen("BODY.PEEK["),
- toklen - strlen("BODY.PEEK[") - 1);
- inner[toklen - strlen("BODY.PEEK[") - 1] = '\0';
+ 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) {
return (1);
}
+static const char *
+body_section(const char *tok, int *plain)
+{
+ *plain = strncasecmp(tok, "BODY[", 5) == 0;
+ if (*plain)
+ return (tok + 5);
+ if (strncasecmp(tok, "BODY.PEEK[", 10) == 0)
+ return (tok + 10);
+ return (NULL);
+}
+
/* RFC 9051 SS6.4.5 fetch-att; unsupported items are skipped */
int
parse_fetch_atts(char *spec, uint32_t *attrs_out, int *degraded_out,
uint32_t *partial_count_out, const char **errmsg)
{
char *p, *tok, *save;
+ const char *sec;
size_t len;
uint32_t attrs = 0;
- int degraded = 0;
+ int degraded = 0, plain, d;
int has_partial = 0;
uint32_t partial_start = 0, partial_count = 0;
} else if (strcasecmp(tok, "MODSEQ") == 0) {
/* RFC 7162 SS3.1.4.2 */
attrs |= MBOX_FETCH_MODSEQ;
- } else if (strcasecmp(tok, "BODY.PEEK[HEADER]") == 0) {
- /* exact BODY[...]; \Seen unimplemented */
- attrs |= MBOX_FETCH_BODY_HEADER;
- } else if (strncasecmp(tok, "BODY.PEEK[",
- strlen("BODY.PEEK[")) == 0 &&
- strncasecmp(tok, "BODY.PEEK[HEADER.FIELDS",
- strlen("BODY.PEEK[HEADER.FIELDS")) != 0) {
- if (parse_body_peek_section_tok(tok, &attrs,
+ } else if ((sec = body_section(tok, &plain)) != NULL) {
+ d = 0;
+ if (strcasecmp(sec, "HEADER]") == 0)
+ 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,
+ 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,
- °raded, errmsg) == -1)
+ &d, errmsg) == -1)
return (-1);
- } else if (strncasecmp(tok, "BODY.PEEK[HEADER.FIELDS",
- strlen("BODY.PEEK[HEADER.FIELDS")) == 0) {
- if (parse_body_peek_header_fields_tok(tok, &attrs,
- header_fields_not_out, header_fields_out,
- header_fields_outsize, header_fields_label_out,
- header_fields_label_outsize, errmsg) == -1)
- return (-1);
+ if (plain && !d)
+ attrs |= MBOX_FETCH_SET_SEEN;
+ degraded |= d;
} else if (strcasecmp(tok, "ENVELOPE") == 0) {
/* SS7.5.2; no .PEEK, no \Seen effect */
attrs |= MBOX_FETCH_ENVELOPE;
if (attrs == 0) {
*errmsg = "cannot fetch that message content yet, "
"supported: FLAGS/UID/INTERNALDATE/RFC822.SIZE/MODSEQ/"
- "ENVELOPE/(BODY|BODYSTRUCTURE)/BODY.PEEK[...]";
+ "ENVELOPE/(BODY|BODYSTRUCTURE)/BODY[...]/BODY.PEEK[...]";
return (-2);
}
char date[40];
size_t len = 0;
int need_sp = 0;
+ int cs = meta->seen_set && s->condstore_enabled;
int have_header = (s->fetch_attrs & (MBOX_FETCH_BODY_HEADER |
MBOX_FETCH_HEADER_FIELDS)) && s->pending_header_found;
int have_body = (s->fetch_attrs &
fetch_append(buf, sizeof(buf), &len, "%u FETCH (", meta->seqno);
- if (s->fetch_attrs & MBOX_FETCH_FLAGS) {
+ /* RFC 9051 SS6.4.5, RFC 7162 SS3.2.4: report a \Seen this set */
+ if ((s->fetch_attrs & MBOX_FETCH_FLAGS) || meta->seen_set) {
fetch_append(buf, sizeof(buf), &len, "FLAGS (%s)", meta->flags);
need_sp = 1;
}
- if (s->fetch_attrs & MBOX_FETCH_UID) {
+ if ((s->fetch_attrs & MBOX_FETCH_UID) || cs) {
fetch_append(buf, sizeof(buf), &len, "%sUID %u",
need_sp ? " " : "", meta->uid);
need_sp = 1;
need_sp ? " " : "", (unsigned long long)meta->size);
need_sp = 1;
}
- if (s->fetch_attrs & MBOX_FETCH_MODSEQ) {
+ if ((s->fetch_attrs & MBOX_FETCH_MODSEQ) || cs) {
/* RFC 7162 SS3.1.4.2 fetch-mod-resp */
fetch_append(buf, sizeof(buf), &len, "%sMODSEQ (%llu)",
need_sp ? " " : "", (unsigned long long)meta->modseq);
session_reply(s, tag, "NO", errmsg);
return (1);
}
+ /* RFC 9051 SS6.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 silently dropped (plain BODY[...]/BODY[<part>]"
- "/BODY.PEEK[<part>] with MESSAGE/RFC822|GLOBAL or MULTIPART"
- " nested numbering/RFC822[.HEADER/.TEXT]), answering "
- "with whatever was recognized", s->id, cmdname);
+ "data items not returned (a part number before "
+ "HEADER/TEXT/MIME, RFC822[.HEADER/.TEXT]), "
+ "answering the rest, then NO", s->id, cmdname);
memset(&req, 0, sizeof(req));
req.attrs = attrs;
}
s->fetch_attrs = req.attrs;
s->cmd_by_uid = by_uid;
- s->fetch_incomplete = 0;
+ /* RFC 9051 SS6.4.5: "NO - fetch error: can't fetch that data" */
+ s->fetch_incomplete = degraded;
s->state = SESSION_FETCHING;
if (!send_mbox_request(s, IMSG_MBOX_FETCH, cmdname, "IMSG_MBOX_FETCH",
blob - e17940a4005eba802bafef76c6836b6948719c4f
blob + 89cc5d2c5094633ba2e50057c592a262ca6ef0ec
--- src/imapd.8
+++ src/imapd.8
.\" Written for the OpenIMAPD project. Public domain / no rights reserved,
.\" matching the project's ports-oriented, OpenBSD-base-inclusion goal.
.\"
-.Dd $Mdocdate: September 27 2026 $
+.Dd $Mdocdate: September 28 2026 $
.Dt IMAPD 8
.Os
.Sh NAME
blob - 8b8d979bbea17a86332be12e7642d65a0584705f
blob + 6eb1a0411a611331b59b79c7139120c4135ecfc8
--- src/imapd.h
+++ src/imapd.h
#include <imsg.h>
#include <stdint.h>
-#define IMAPD_VERSION "0.1.7"
+#define IMAPD_VERSION "0.1.8"
+#define IMAPD_USER "_imapd"
+#define IMAPD_AUTH_USER "_imapauth"
+#define IMAPD_KEY_USER "_imapkey"
+
enum openimap_proc_type {
PROC_PARENT,
PROC_LISTENER,
IMSG_MBOX_SELECT_VANISHED,
IMSG_MBOX_STORE_MODIFIED,
- /* RFC 9051 SS6.3.13 (IDLE) */
+ /* RFC 9051 SS6.3.13 (IDLE); the VIEW ones go with any command */
IMSG_MBOX_IDLE_REFRESH,
- IMSG_MBOX_IDLE_EXPUNGE,
- IMSG_MBOX_IDLE_FETCH,
+ IMSG_MBOX_VIEW_EXPUNGE,
+ IMSG_MBOX_VIEW_FETCH,
+ IMSG_MBOX_VIEW_EXISTS,
IMSG_MBOX_IDLE_REFRESHED,
/* RFC 9051 SS6.3.9 (LIST) */
MBOX_OP_ERR_NO_SUCH_MAILBOX,
MBOX_OP_ERR_ALREADY_EXISTS,
MBOX_OP_ERR_BUSY,
+ MBOX_OP_ERR_EXPUNGEISSUED,
};
/* QRESYNC select-param (RFC 7162 SS3.2.5). */
#define MBOX_FETCH_ENVELOPE (1U << 9)
#define MBOX_FETCH_BODYSTRUCTURE (1U << 10) /* and bare BODY */
#define MBOX_FETCH_BODY_PART (1U << 11)
+#define MBOX_FETCH_SET_SEEN (1U << 12)
#define SECTION_PART_MAX 40
/* relayd's RELAY_TLS_PRIV_TIMEOUT bounds a similar wait */
#define PARSER_REPLY_TIMEOUT_SEC 10
+#define KEYMGR_REPLY_TIMEOUT_SEC 10
/* SIGXCPU at soft, SIGKILL at hard (sys/kern/kern_resource.c) */
#define PARSER_CPU_SOFT_SEC 8
int64_t internaldate; /* from the basename, not mtime */
char flags[MBOX_FLAGS_MAX];
uint64_t modseq;
+ int seen_set;
};
/* sent just before the same message's IMSG_MBOX_FETCH_META */
enum mbox_op_error error; /* NO_SUCH_MAILBOX gets [TRYCREATE] */
uint32_t uidvalidity;
uint32_t uid; /* APPENDUID, with uidvalidity */
- uint32_t exists;
};
/* RFC 9051 SS6.4.4 search-program, as a postfix array of nodes */
uint64_t modseq; /* RFC 7162 SS3.1.6 */
};
-/* RFC 9051 SS6.3.13 IDLE: seeded once, then a diff per poll */
-struct imsg_mbox_idle_refresh {
- /* adopt the current state as baseline, report nothing */
- int seed;
-};
-
-struct imsg_mbox_idle_expunge {
- uint32_t seqno;
-};
-
+/* RFC 9051 SS6.3.13 IDLE, SS6.1.2 NOOP; EXPUNGE is imsg_mbox_expunged */
struct imsg_mbox_idle_refreshed {
int ok;
- /* the probe saw no change; the fields below are zero */
- int unchanged;
- int exists_changed;
- uint32_t exists;
+ int unchanged; /* the probe saw no change */
int busy; /* lock held elsewhere; ok says if it seeded */
};
+struct imsg_mbox_view_exists {
+ uint32_t exists;
+};
+
/* RFC 9051 SS6.3.4/SS6.3.5; store.c re-validates the name */
struct imsg_mbox_create {
char mailbox[MBOX_NAME_MAX];
blob - cf71d95cf2cc0880c154fda71df483e955db9cae
blob + fe42950141044244657ac834101c6d2d9009749c
--- src/index.c
+++ src/index.c
base->uids = NULL;
base->n = 0;
base->modseq = 0;
+ base->ghosts = 0;
base->valid = 0;
}
+static size_t
+uid_find(const uint32_t *list, size_t n, uint32_t uid)
+{
+ size_t lo = 0, hi = n, mid;
+
+ while (lo < hi) {
+ mid = lo + (hi - lo) / 2;
+ if (list[mid] < uid)
+ lo = mid + 1;
+ else
+ hi = mid;
+ }
+ return ((lo < n && list[lo] == uid) ? lo : n);
+}
+
+/* RFC 9051 SS7.5.1: once told, never again, so drop it from the baseline */
+int
+send_expunged(struct store_session *ss, uint32_t seqno, uint32_t uid)
+{
+ struct store_idle_baseline *base = &ss->idle_baseline;
+ struct imsg_mbox_expunged exp;
+ size_t at;
+
+ at = uid_find(base->uids, base->n, uid);
+ memset(&exp, 0, sizeof(exp));
+ exp.seqno = at < base->n ? (uint32_t)at + 1 : seqno;
+ exp.uid = uid;
+ if (imsg_compose(&ss->iev.ibuf, IMSG_MBOX_EXPUNGED, 0, 0, -1, &exp,
+ sizeof(exp)) == -1) {
+ log_warn("session %u: imsg_compose IMSG_MBOX_EXPUNGED",
+ session_id);
+ return (-1);
+ }
+ if (at < base->n) {
+ memmove(&base->uids[at], &base->uids[at + 1],
+ (base->n - at - 1) * sizeof(*base->uids));
+ base->n--;
+ }
+ return (0);
+}
+
+uint32_t
+view_seqno(const struct store_session *ss, uint32_t uid, uint32_t fallback)
+{
+ const struct store_idle_baseline *base = &ss->idle_baseline;
+ size_t at;
+
+ if (!base->valid)
+ return (fallback);
+ at = uid_find(base->uids, base->n, uid);
+ return (at < base->n ? (uint32_t)at + 1 : fallback);
+}
+
+/* a sequence set, read through the view, as UIDs; *by_uid then set */
+void
+view_resolve(const struct store_session *ss, const struct seq_range *in,
+ uint32_t nin, struct seq_range *out, uint32_t *nout, int *by_uid)
+{
+ const struct store_idle_baseline *base = &ss->idle_baseline;
+ uint32_t i, lo, hi, tmp, n = 0;
+
+ if (*by_uid || !base->valid) {
+ for (i = 0; i < nin; i++)
+ out[i] = in[i];
+ *nout = nin;
+ return;
+ }
+ for (i = 0; i < nin; i++) {
+ lo = in[i].lo_is_star ? (uint32_t)base->n : in[i].lo;
+ hi = in[i].hi_is_star ? (uint32_t)base->n : in[i].hi;
+ if (lo > hi) {
+ tmp = lo;
+ lo = hi;
+ hi = tmp;
+ }
+ if (lo < 1)
+ lo = 1;
+ if (hi > base->n)
+ hi = (uint32_t)base->n;
+ if (lo > hi)
+ continue;
+ memset(&out[n], 0, sizeof(out[n]));
+ out[n].lo = base->uids[lo - 1];
+ out[n].hi = base->uids[hi - 1];
+ n++;
+ }
+ *nout = n;
+ *by_uid = 1;
+}
+
+/* RFC 2180 SS4.1.2: does the set name a message expunged, 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)
+{
+ const struct store_idle_baseline *base = &ss->idle_baseline;
+ struct index_rec rec;
+ size_t i, j = 0;
+
+ if (!base->valid || base->ghosts == 0)
+ return (0);
+ memset(&rec, 0, sizeof(rec));
+ for (i = 0; i < base->n; i++) {
+ if (!seqset_contains(uids, nuids, base->uids[i]))
+ continue;
+ for (; j < idx->nlines; j++)
+ if (index_parse_line(idx->lines[j], &rec) == 0 &&
+ rec.uid >= base->uids[i])
+ break;
+ if (j == idx->nlines || rec.uid != base->uids[i])
+ return (1);
+ }
+ return (0);
+}
+
/* RFC 9051 SS7.5.1: each EXPUNGE renumbers those above it */
static size_t
-idle_send_expunges(const uint32_t *old, size_t oldn, const uint32_t *cur,
- size_t curn, struct imsgev *iev)
+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,
+ struct imsgev *iev)
{
- struct imsg_mbox_idle_expunge item;
- size_t i, j = 0, gone = 0;
- uint32_t seqno = 1;
+ struct imsg_mbox_expunged item;
+ size_t i = 0, j = 0, n = 0, gone = 0;
- for (i = 0; i < oldn; i++) {
- while (j < curn && cur[j] < old[i])
- j++;
+ while (i < oldn || j < curn) {
+ if (j < curn && (i == oldn || cur[j] < old[i])) {
+ view[n++] = cur[j++];
+ continue;
+ }
if (j < curn && cur[j] == old[i]) {
+ view[n++] = old[i++];
j++;
- seqno++;
continue;
}
+ if (!expunge_ok) {
+ view[n++] = old[i++];
+ continue;
+ }
memset(&item, 0, sizeof(item));
- item.seqno = seqno;
+ item.seqno = (uint32_t)n + 1;
+ item.uid = old[i++];
gone++;
- if (imsg_compose(&iev->ibuf, IMSG_MBOX_IDLE_EXPUNGE, 0, 0, -1,
+ if (imsg_compose(&iev->ibuf, IMSG_MBOX_VIEW_EXPUNGE, 0, 0, -1,
&item, sizeof(item)) == -1)
log_warn("session %u: imsg_compose "
- "IMSG_MBOX_IDLE_EXPUNGE", session_id);
+ "IMSG_MBOX_VIEW_EXPUNGE", session_id);
}
+ *viewn = n;
return (gone);
}
/* RFC 9051 SS6.3.13: flag changes, as FETCH with UID */
static size_t
-idle_send_flag_fetches(const struct mbox_index *idx, const uint32_t *old,
- size_t oldn, uint64_t since, struct store_session *ss)
+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,
+ struct store_session *ss)
{
struct imsgev *iev = &ss->iev;
struct imsg_mbox_fetch_meta meta;
struct index_rec rec;
char suffix[64];
off_t size;
- size_t i, j = 0, seqno = 0, sent = 0;
+ size_t i, j = 0, sent = 0;
for (i = 0; i < idx->nlines; i++) {
if (index_parse_line(idx->lines[i], &rec) == -1)
continue; /* malformed, skipped as everywhere */
- seqno++; /* position after the EXPUNGEs above */
if (rec.modseq <= since)
continue;
while (j < oldn && old[j] < rec.uid)
continue;
}
memset(&meta, 0, sizeof(meta));
- meta.seqno = (uint32_t)seqno;
+ meta.seqno = (uint32_t)uid_find(view, viewn, rec.uid) + 1;
meta.uid = rec.uid;
meta.modseq = rec.modseq;
build_flags_string(suffix, rec.keywords, meta.flags,
sizeof(meta.flags));
sent++;
- if (imsg_compose(&iev->ibuf, IMSG_MBOX_IDLE_FETCH, 0, 0, -1,
+ if (imsg_compose(&iev->ibuf, IMSG_MBOX_VIEW_FETCH, 0, 0, -1,
&meta, sizeof(meta)) == -1)
log_warn("session %u: imsg_compose "
- "IMSG_MBOX_IDLE_FETCH", session_id);
+ "IMSG_MBOX_VIEW_FETCH", session_id);
}
return (sent);
}
return (0);
}
+void
+idle_baseline_seed(struct store_session *ss, const struct mbox_index *idx)
+{
+ struct store_idle_baseline *base = &ss->idle_baseline;
+ uint32_t *list;
+ uint64_t modseq;
+ size_t n;
+
+ idle_baseline_reset(ss);
+ if (idle_uid_list(idx, &list, &n, &modseq) == -1)
+ return;
+ base->uids = list;
+ base->n = n;
+ base->modseq = modseq;
+ base->valid = 1;
+}
+
+/* RFC 9051 SS5.2, SS7.5.1: what changed; EXPUNGE only if expunge_ok */
+int
+view_sync(struct store_session *ss, const struct mbox_index *idx,
+ int expunge_ok)
+{
+ struct store_idle_baseline *base = &ss->idle_baseline;
+ struct imsg_mbox_view_exists ve;
+ uint32_t *cur, *view;
+ uint64_t seen;
+ size_t curn, viewn, gone;
+
+ if (!base->valid) {
+ idle_baseline_seed(ss, idx);
+ return (base->valid ? 0 : -1);
+ }
+ /* index_append() and every removal raise HIGHESTMODSEQ */
+ if (idx->nlines == base->n && idx->highestmodseq <= base->modseq)
+ return (0);
+ if (idle_uid_list(idx, &cur, &curn, &seen) == -1)
+ return (-1);
+ if ((view = reallocarray(NULL, base->n + curn + 1,
+ sizeof(*view))) == NULL) {
+ log_warn("session %u: view: reallocarray", session_id);
+ free(cur);
+ return (-1);
+ }
+ gone = view_merge(base->uids, base->n, cur, curn, expunge_ok, view,
+ &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 */
+ if (gone > 0 || viewn != base->n) {
+ memset(&ve, 0, sizeof(ve));
+ ve.exists = (uint32_t)viewn;
+ if (imsg_compose(&ss->iev.ibuf, IMSG_MBOX_VIEW_EXISTS, 0, 0,
+ -1, &ve, sizeof(ve)) == -1)
+ log_warn("session %u: imsg_compose "
+ "IMSG_MBOX_VIEW_EXISTS", session_id);
+ }
+ free(cur);
+ free(base->uids);
+ base->uids = view;
+ base->n = viewn;
+ base->ghosts = viewn - curn;
+ if (seen > base->modseq)
+ base->modseq = seen;
+ return (0);
+}
+
/* lockless: index_save() only ever replaces the index by rename(2) */
static int
-idle_seed_unlocked(struct store_session *ss,
- struct imsg_mbox_idle_refreshed *reply)
+idle_seed_unlocked(struct store_session *ss)
{
struct store_idle_baseline *base = &ss->idle_baseline;
struct mbox_index idx;
base->uids = list;
base->n = n;
base->modseq = modseq;
+ base->ghosts = 0;
base->valid = 1;
- reply->exists = (uint32_t)n;
return (0);
}
void
-handle_mbox_idle_refresh(const struct imsg_mbox_idle_refresh *req,
- struct store_session *ss)
+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 index_lock il = INDEX_LOCK_INIT;
- size_t newn = 0, gone = 0, changed = 0;
int pending = 0, seeded, locked;
- uint32_t *newlist;
- uint64_t seen_modseq = 0;
memset(&reply, 0, sizeof(reply));
+ if (!ss->mailbox_selected)
+ goto send;
/* a seed adopts what it finds rather than reporting it */
- seeded = req->seed || !base->valid;
+ seeded = !base->valid;
if (idle_probe_unchanged(ss)) {
reply.ok = 1;
}
}
- if (idle_uid_list(&idx, &newlist, &newn, &seen_modseq) == -1) {
- index_free(&idx);
- index_lock_release(&il);
- goto send;
- }
+ if (view_sync(ss, &idx, 1) == 0)
+ reply.ok = 1;
- if (!seeded) {
- gone = idle_send_expunges(base->uids, base->n, newlist, newn,
- iev);
- changed = idle_send_flag_fetches(&idx, base->uids, base->n,
- base->modseq, ss);
- reply.exists_changed = newn != base->n;
- }
- free(base->uids);
- base->uids = newlist;
- base->n = newn;
- base->modseq = seen_modseq;
- base->valid = 1;
-
- reply.ok = 1;
- reply.exists = (uint32_t)newn;
-
- log_debug("session %u: idle refresh: %zu uid(s), highestmodseq %llu, "
- "%zu expunge(s), %zu flag change(s)%s%s", session_id, newn,
- (unsigned long long)idx.highestmodseq, gone, changed,
+ log_debug("session %u: idle refresh: %zu uid(s) in view, "
+ "highestmodseq %llu%s%s", session_id, base->n,
+ (unsigned long long)idx.highestmodseq,
seeded ? " (baseline seeded)" : "",
pending ? " (escalated to LOCK_EX, indexed new delivery)" : "");
busy:
/* RFC 9051 SS7.5.1: a seed cannot skip, or EXPUNGEs repeat */
reply.busy = 1;
- if (seeded && idle_seed_unlocked(ss, &reply) == 0) {
+ if (seeded && idle_seed_unlocked(ss) == 0) {
reply.ok = 1;
/* so the next poll reads the index, not the probe */
idle_probe_reset(ss);
blob - 2c1312d3accc2cbb04903f56af503731f6550e94
blob + d1f56a65c1e211d40c9520058aff64700ffd3ce8
--- src/keymgr.c
+++ src/keymgr.c
#include <openssl/x509.h>
#include <event.h>
+#include <fcntl.h>
#include <grp.h>
#include <imsg.h>
#include <pwd.h>
explicit_bzero(key_buf, sizeof(key_buf));
keymgr_got_init = 1;
- if ((pw = getpwnam("_imapkey")) == NULL)
- fatalx("getpwnam _imapkey: no such user "
- "(expected, not yet provisioned by an install script)");
+ if ((pw = getpwnam(IMAPD_KEY_USER)) == NULL)
+ fatalx("unknown user %s", IMAPD_KEY_USER);
/* the key arrives over imsg; nothing is read from disk */
if (chroot("/var/empty") == -1)
uint32_t sess_id = imsg_get_id(&imsg);
int peer_fd = imsg_get_fd(&imsg);
struct keymgr_peer *kp;
+ int flags;
if (peer_fd == -1) {
log_warnx("IMSG_SETUP_PEER carried no fd");
break;
}
+ /* a listener that stops reading must not stop keymgr */
+ if ((flags = fcntl(peer_fd, F_GETFL)) == -1 ||
+ fcntl(peer_fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ log_warn("session %u: fcntl O_NONBLOCK",
+ sess_id);
+ close(peer_fd);
+ break;
+ }
if ((kp = calloc(1, sizeof(*kp))) == NULL) {
log_warn("calloc");
close(peer_fd);
blob - b83f6daf82d8b6e6b1b0d41ecc8fb9295a45c899
blob + 3726d921cf0beb10b9d3633754337174daf18e4a
--- src/listener.c
+++ src/listener.c
static EC_KEY_METHOD *keymgr_ecdsae_method;
static int
+keymgr_wait_reply(uint32_t type, uint32_t id, const char *op,
+ struct imsg *imsg)
+{
+ struct timespec start, now;
+ struct pollfd pfd;
+ ssize_t n;
+ int ms;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
+ fatal("%s: clock_gettime", op);
+ for (;;) {
+ if ((n = imsgbuf_get(&keymgr_ibuf, imsg)) == -1)
+ fatal("%s: imsg_get", op);
+ if (n != 0) {
+ if (imsg_get_type(imsg) == type &&
+ imsg_get_id(imsg) == id)
+ return (0);
+ log_warnx("%s: unexpected reply type %u id %u "
+ "(wanted %u/%u)", op, imsg_get_type(imsg),
+ imsg_get_id(imsg), type, id);
+ imsg_free(imsg);
+ continue;
+ }
+ if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
+ fatal("%s: clock_gettime", op);
+ ms = KEYMGR_REPLY_TIMEOUT_SEC * 1000 -
+ (int)((now.tv_sec - start.tv_sec) * 1000 +
+ (now.tv_nsec - start.tv_nsec) / 1000000);
+ if (ms <= 0) {
+ log_warnx("%s: keymgr did not answer within %d "
+ "seconds", op, KEYMGR_REPLY_TIMEOUT_SEC);
+ return (-1);
+ }
+ pfd.fd = keymgr_ibuf.fd;
+ pfd.events = POLLIN;
+ if ((n = poll(&pfd, 1, ms)) == -1) {
+ if (errno == EINTR)
+ continue;
+ fatal("%s: poll", op);
+ }
+ if (n == 0)
+ continue;
+ if ((n = imsgbuf_read(&keymgr_ibuf)) == -1)
+ fatal("%s: imsgbuf_read", op);
+ if (n == 0)
+ fatalx("%s: keymgr closed channel", op);
+ }
+}
+
+/* RSA_private_encrypt(3): -1 on error */
+static int
keymgr_forward_rsa(uint32_t type, const char *hash, const unsigned char *from,
int fromlen, unsigned char *to, size_t tosize, int padding)
{
struct imsg imsg;
static uint32_t reqid;
uint32_t id;
- ssize_t n;
- int done, ret;
+ int ret;
if (fromlen < 0 || (size_t)fromlen > KEYMGR_DATA_MAX) {
log_warnx("keymgr_forward_rsa: %d bytes over KEYMGR_DATA_MAX",
fromlen);
- return (0);
+ return (-1);
}
memset(&req, 0, sizeof(req));
if (strlcpy(req.hash, hash, sizeof(req.hash)) >= sizeof(req.hash)) {
log_warnx("keymgr_forward_rsa: pubkey hash too long");
- return (0);
+ return (-1);
}
req.padding = (uint32_t)padding;
req.fromlen = (uint32_t)fromlen;
if (imsg_compose(&keymgr_ibuf, type, id, 0, -1, combined,
sizeof(req) + (size_t)fromlen) == -1) {
log_warnx("keymgr_forward_rsa: imsg_compose");
- return (0);
+ return (-1);
}
if (imsgbuf_flush(&keymgr_ibuf) == -1)
fatal("keymgr_forward_rsa: imsgbuf_flush");
- ret = 0;
- done = 0;
- while (!done) {
- if ((n = imsgbuf_get(&keymgr_ibuf, &imsg)) == -1)
- fatal("keymgr_forward_rsa: imsg_get");
- if (n == 0) {
- if ((n = imsgbuf_read(&keymgr_ibuf)) == -1)
- fatal("keymgr_forward_rsa: imsgbuf_read");
- if (n == 0)
- fatalx("keymgr_forward_rsa: keymgr closed "
- "channel");
- continue;
- }
- if (imsg_get_type(&imsg) != type ||
- imsg_get_id(&imsg) != id) {
- log_warnx("keymgr_forward_rsa: unexpected reply "
- "type %u id %u (wanted %u/%u)",
- imsg_get_type(&imsg), imsg_get_id(&imsg), type,
- id);
- imsg_free(&imsg);
- continue;
- }
- if (imsg_get_buf(&imsg, &rep, sizeof(rep)) == -1) {
- log_warnx("keymgr_forward_rsa: bad reply header");
- imsg_free(&imsg);
- break;
- }
- /* bounded by OpenSSL's buffer, tosize */
- if (rep.ok && rep.tolen <= tosize &&
- imsg_get_len(&imsg) == rep.tolen) {
- if (imsg_get_buf(&imsg, to, rep.tolen) == -1)
- log_warnx("keymgr_forward_rsa: bad reply "
- "data");
- else
- ret = (int)rep.tolen;
- }
+ if (keymgr_wait_reply(type, id, "keymgr_forward_rsa", &imsg) == -1)
+ return (-1);
+ if (imsg_get_buf(&imsg, &rep, sizeof(rep)) == -1) {
+ log_warnx("keymgr_forward_rsa: bad reply header");
imsg_free(&imsg);
- done = 1;
+ return (-1);
}
+ ret = -1;
+ if (rep.ok && rep.tolen <= tosize &&
+ imsg_get_len(&imsg) == rep.tolen) {
+ if (imsg_get_buf(&imsg, to, rep.tolen) == -1)
+ log_warnx("keymgr_forward_rsa: bad reply data");
+ else
+ ret = (int)rep.tolen;
+ }
+ imsg_free(&imsg);
return (ret);
}
ECDSA_SIG *sig = NULL;
static uint32_t reqid;
uint32_t id;
- ssize_t n;
- int done;
const unsigned char *sigp;
if (dgst_len < 0 || (size_t)dgst_len > KEYMGR_DATA_MAX) {
if (imsgbuf_flush(&keymgr_ibuf) == -1)
fatal("keymgr_forward_ecdsa: imsgbuf_flush");
- done = 0;
- while (!done) {
- if ((n = imsgbuf_get(&keymgr_ibuf, &imsg)) == -1)
- fatal("keymgr_forward_ecdsa: imsg_get");
- if (n == 0) {
- if ((n = imsgbuf_read(&keymgr_ibuf)) == -1)
- fatal("keymgr_forward_ecdsa: imsgbuf_read");
- if (n == 0)
- fatalx("keymgr_forward_ecdsa: keymgr closed "
- "channel");
- continue;
- }
- if (imsg_get_type(&imsg) != IMSG_KEYMGR_ECDSA_SIGN ||
- imsg_get_id(&imsg) != id) {
- log_warnx("keymgr_forward_ecdsa: unexpected reply "
- "type %u id %u (wanted %u/%u)",
- imsg_get_type(&imsg), imsg_get_id(&imsg),
- IMSG_KEYMGR_ECDSA_SIGN, id);
- imsg_free(&imsg);
- continue;
- }
- if (imsg_get_buf(&imsg, &rep, sizeof(rep)) == -1) {
- log_warnx("keymgr_forward_ecdsa: bad reply header");
- imsg_free(&imsg);
- break;
- }
- if (rep.ok && rep.tolen <= KEYMGR_DATA_MAX &&
- imsg_get_len(&imsg) == rep.tolen) {
- if (imsg_get_buf(&imsg, sigbuf, rep.tolen) == -1)
- log_warnx("keymgr_forward_ecdsa: bad reply "
- "data");
- else {
- sigp = sigbuf;
- d2i_ECDSA_SIG(&sig, &sigp, (long)rep.tolen);
- }
- }
+ if (keymgr_wait_reply(IMSG_KEYMGR_ECDSA_SIGN, id,
+ "keymgr_forward_ecdsa", &imsg) == -1)
+ return (NULL);
+ if (imsg_get_buf(&imsg, &rep, sizeof(rep)) == -1) {
+ log_warnx("keymgr_forward_ecdsa: bad reply header");
imsg_free(&imsg);
- done = 1;
+ return (NULL);
}
+ if (rep.ok && rep.tolen <= KEYMGR_DATA_MAX &&
+ imsg_get_len(&imsg) == rep.tolen) {
+ if (imsg_get_buf(&imsg, sigbuf, rep.tolen) == -1)
+ log_warnx("keymgr_forward_ecdsa: bad reply data");
+ else {
+ sigp = sigbuf;
+ d2i_ECDSA_SIG(&sig, &sigp, (long)rep.tolen);
+ }
+ }
+ imsg_free(&imsg);
return (sig);
}
fatalx("listener: IMSG_LISTENER_SESSION_INIT carried no "
"client fd");
- if ((pw = getpwnam("_imapd")) == NULL)
- fatalx("getpwnam _imapd: no such user "
- "(expected, not yet provisioned by an install script)");
+ if ((pw = getpwnam(IMAPD_USER)) == NULL)
+ fatalx("unknown user %s", IMAPD_USER);
if (chroot("/var/empty") == -1)
fatal("chroot /var/empty");
static int
session_is_busy(const struct session *s)
{
+ if (s->refresh_status != NULL)
+ return (1);
switch (s->state) {
case SESSION_SELECTING:
case SESSION_FETCHING:
blob - f33cba5aa3d76a8ba44c5945fc1a086bc3970e2d
blob + 272fe1eb376922247b102eb04ff8e4fc7e165ebc
--- src/listener.h
+++ src/listener.h
/* RFC 9051 SS6.3.13 IDLE; the store child keeps the UID list */
int idle_refresh_pending;
int idle_refresh_again;
- int idle_refresh_again_seed;
+ /* the tagged reply IDLE or NOOP owes once the refresh is in */
+ const char *refresh_status;
+ const char *refresh_text;
struct event idle_ev;
struct event grace_ev;
const struct imsg_mbox_fetch_meta *);
void session_handle_store_modified(struct session *,
struct imsg_mbox_store_modified *);
-void session_handle_idle_expunge(struct session *,
- const struct imsg_mbox_idle_expunge *);
-void session_handle_idle_fetch(struct session *,
+void session_handle_view_fetch(struct session *,
const struct imsg_mbox_fetch_meta *);
void session_handle_idle_refreshed(struct session *,
const struct imsg_mbox_idle_refreshed *);
-void session_request_idle_refresh(struct session *, int);
+int session_request_idle_refresh(struct session *);
void session_idle_poll_init(struct session *);
void session_login_grace_init(struct session *);
void session_login_grace_disarm(struct session *);
blob - 07161abf280fd522a6ce6a00dc337ee613c5a0b9
blob + 328b47f988740d0a5fe9f0f6576e7df5ddcd1803
--- src/main.c
+++ src/main.c
#include <sys/types.h>
#include <err.h>
+#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
if (geteuid() != 0)
fatalx("parent must start as root (running as "
"uid %u)", (unsigned int)geteuid());
+ if (getpwnam(IMAPD_USER) == NULL)
+ fatalx("unknown user %s", IMAPD_USER);
+ if (getpwnam(IMAPD_AUTH_USER) == NULL)
+ 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);
blob - 458f5c3e1ebc8d703f72a9e02f98902b7b6a33c1
blob + c64b5e7d6cf102bfad49200a205fe00d009d82b6
--- src/mbox_copy.c
+++ src/mbox_copy.c
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;
goto done;
}
dfd = cross_mailbox ? destfd : ss->mailbox_dir_fd;
+ /* RFC 2180 SS4.4.2: the numbers are those before the EXPUNGEs */
+ view_resolve(ss, ranges, nranges, vr, &nvr, &req->by_uid);
+ (void)view_sync(ss, srcidx, 1);
- if (!stage_copy_messages(ss, srcidx, req, ranges, nranges, &staged,
+ if (!stage_copy_messages(ss, srcidx, req, vr, nvr, &staged,
&nstaged)) {
ok = 0;
goto close_dest;
}
/* then EXPUNGE (RFC 9051 SS6.4.8) */
for (i = 0; i < nmoved; i++) {
- struct imsg_mbox_expunged exp;
-
pcache_drop_uid(ss->selected_mailbox, moved[i].old_uid);
- memset(&exp, 0, sizeof(exp));
- exp.seqno = moved[i].old_seqno;
- exp.uid = moved[i].old_uid;
- if (imsg_compose(&iev->ibuf, IMSG_MBOX_EXPUNGED, 0, 0, -1,
- &exp, sizeof(exp)) == -1)
- log_warn("session %u: imsg_compose IMSG_MBOX_EXPUNGED",
- session_id);
+ (void)send_expunged(ss, moved[i].old_seqno, moved[i].old_uid);
}
done:
uint32_t nranges, struct mbox_index *srcidx, struct mbox_index *destidx,
int destfd, uint32_t *nmoved_out, struct store_session *ss)
{
- struct imsgev *iev = &ss->iev;
struct copy_staged *staged = NULL;
size_t nstaged = 0, i;
int ok = 1, any_removed = 0;
unlinkat(ss->mailbox_dir_fd, path, 0);
}
- {
- struct imsg_mbox_expunged exp;
-
- memset(&exp, 0, sizeof(exp));
- exp.seqno = old_seqno;
- exp.uid = staged[i].src_uid;
- if (imsg_compose(&iev->ibuf, IMSG_MBOX_EXPUNGED, 0, 0,
- -1, &exp, sizeof(exp)) == -1)
- log_warn("session %u: imsg_compose "
- "IMSG_MBOX_EXPUNGED", session_id);
- }
+ (void)send_expunged(ss, old_seqno, staged[i].src_uid);
}
/* RFC 7162 SS3.1 */
char desttarget[MBOX_NAME_MAX];
int cross_mailbox;
int destfd = -1, got;
+ struct seq_range vr[SEQSET_MAX_RANGES];
+ uint32_t nvr;
memset(&idx_a, 0, sizeof(idx_a));
memset(&idx_b, 0, sizeof(idx_b));
goto done;
}
+ view_resolve(ss, ranges, nranges, vr, &nvr, &req->by_uid);
+ (void)view_sync(ss, srcidx, 1);
if (!cross_mailbox) {
- if (!move_same_mailbox(req, ranges, nranges, srcidx, &nmoved,
- ss))
+ if (!move_same_mailbox(req, vr, nvr, srcidx, &nmoved, ss))
ok = 0;
} else {
- if (!move_cross_mailbox(req, ranges, nranges, srcidx, destidx,
+ if (!move_cross_mailbox(req, vr, nvr, srcidx, destidx,
destfd, &nmoved, ss))
ok = 0;
}
blob - 0b694205c1d879f3ca71a8c24865436b4b7da1e7
blob + 3088dce4bc163aa0100848cfe302e99edd45c2d5
--- src/mbox_fetch.c
+++ src/mbox_fetch.c
free(combined);
}
+/* RFC 9051 SS6.4.5: BODY[...] sets \Seen, with STORE's own code */
+static int
+fetch_set_seen(struct store_session *ss, int synced)
+{
+ struct store_fetch_walk *fw = &ss->fetch;
+ struct imsg_mbox_store sreq;
+ struct store_step *steps = NULL;
+ size_t nsteps = 0;
+ uint64_t before = fw->idx.highestmodseq;
+ int ok;
+
+ memset(&sreq, 0, sizeof(sreq));
+ sreq.mode = MBOX_STORE_ADD;
+ sreq.sysflags = MBOX_FLAG_SEEN;
+ ok = store_apply(ss, &sreq, &fw->idx, fw->resolved, fw->nresolved,
+ fw->req.by_uid, synced, fw->req.has_changedsince ?
+ &fw->req.changedsince : NULL, &steps, &nsteps);
+ free(steps);
+ if (!ok)
+ return (-1);
+ if (fw->idx.highestmodseq != before) {
+ fw->seen_modseq = fw->idx.highestmodseq;
+ /* its renames left cur/'s snapshot naming the old files */
+ cur_snapshot_discard(&ss->cur_snap);
+ }
+ return (0);
+}
+
/* returns 1, unanswered, if the index lock is busy */
int
handle_mbox_fetch(struct imsg_mbox_fetch *req, const struct seq_range *ranges,
struct imsgev *iev = &ss->iev;
struct mbox_index *idx = &fw->idx;
struct index_lock il = INDEX_LOCK_INIT;
- uint32_t i;
- int locked;
+ struct seq_range vr[SEQSET_MAX_RANGES];
+ uint32_t i, nvr;
+ int locked, synced, rc, set_seen;
+ set_seen = (req->attrs & MBOX_FETCH_SET_SEEN) != 0;
/* Before the reset below, so that a busy return changes nothing. */
- locked = index_lock_acquire(ss->mailbox_dir_fd, &il, LOCK_SH | LOCK_NB);
+ locked = index_lock_acquire(ss->mailbox_dir_fd, &il,
+ (set_seen ? LOCK_EX : LOCK_SH) | LOCK_NB);
if (locked == 1)
return (1);
fetch_walk_finish(ss, 0);
return (0);
}
- index_lock_release(&il);
+ if (!set_seen)
+ index_lock_release(&il);
pcache_check_mailbox(ss->selected_mailbox, idx->uidvalidity);
+ synced = view_sync(ss, idx, req->by_uid) == 0;
+ view_resolve(ss, ranges, nranges, vr, &nvr, &fw->req.by_uid);
/* RFC 9051 SS6.4.9 */
- fw->nresolved = seqset_resolve(ranges, nranges, req->by_uid ?
- index_max_uid(idx) : (uint32_t)idx->nlines, !req->by_uid,
+ fw->nresolved = seqset_resolve(vr, nvr, fw->req.by_uid ?
+ index_max_uid(idx) : (uint32_t)idx->nlines, !fw->req.by_uid,
fw->resolved);
fw->max_hi = seqset_max_hi(fw->resolved, fw->nresolved);
+ fw->ghost = !req->by_uid &&
+ view_names_ghost(ss, idx, fw->resolved, fw->nresolved);
+ if (set_seen) {
+ rc = fetch_set_seen(ss, synced);
+ index_lock_release(&il);
+ if (rc == -1) {
+ fetch_walk_finish(ss, 0);
+ return (0);
+ }
+ }
+
/* RFC 7162 SS3.2.6: VANISHED (EARLIER) first */
if (req->by_uid && req->want_vanished) {
for (i = 0; i < fw->nresolved; i++)
}
memset(&meta, 0, sizeof(meta));
- meta.seqno = i;
+ 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 (req->attrs & (MBOX_FETCH_RFC822_SIZE | MBOX_FETCH_FLAGS)) {
+ 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) {
if (req->attrs & MBOX_FETCH_RFC822_SIZE)
meta.size = (uint64_t)size;
- if (req->attrs & MBOX_FETCH_FLAGS)
+ 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)
int mfd, rc;
memset(&hdrmeta, 0, sizeof(hdrmeta));
- hdrmeta.seqno = i;
+ 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,
!want_part;
memset(&bodymeta, 0, sizeof(bodymeta));
- bodymeta.seqno = i;
+ bodymeta.seqno = view_seqno(ss, rec.uid, i);
bodymeta.uid = rec.uid;
if (want_part) {
int mfd;
memset(&envmeta, 0, sizeof(envmeta));
- envmeta.seqno = i;
+ 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)
int mfd;
memset(&bsmeta, 0, sizeof(bsmeta));
- bsmeta.seqno = i;
+ 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)
cur_snapshot_discard(&ss->cur_snap);
memset(&result, 0, sizeof(result));
- result.error = ok ? MBOX_OP_OK : MBOX_OP_ERR_GENERIC;
+ result.error = !ok ? MBOX_OP_ERR_GENERIC : fw->ghost ?
+ MBOX_OP_ERR_EXPUNGEISSUED : MBOX_OP_OK;
result.count = fw->sent;
if (imsg_compose(&iev->ibuf, IMSG_MBOX_RESULT, 0, 0, -1, &result,
sizeof(result)) == -1)
blob - 63953515d197c7229551665b077361c892fc219a
blob + db797c1b29abb06e90a0be290a8fd944fd711c7f
--- src/mbox_manage.c
+++ src/mbox_manage.c
if (req->qresync && req->qresync_uidvalidity == idx.uidvalidity)
qresync_send_resync(req, ranges, nranges, &idx, ss);
+ idle_baseline_seed(ss, &idx);
index_free(&idx);
index_lock_release(&il);
goto done_index;
reply.uidvalidity = idx.uidvalidity;
- reply.exists = (uint32_t)idx.nlines;
+ if (ss->mailbox_selected && strcmp(ss->selected_mailbox,
+ mailbox_name_is_inbox(ap->req.mailbox) ? "" : ap->req.mailbox) == 0)
+ (void)view_sync(ss, &idx, 1);
index_lock_release(&il);
index_free(&idx);
blob - 3f440822a5f3f8e4528335336b76cdb09c52bb88
blob + fb8ba5603a5446fd95335c1e1ef8654a5f73daf2
--- src/mbox_search.c
+++ src/mbox_search.c
struct store_search_walk *sw = &ss->search;
struct index_lock il = INDEX_LOCK_INIT;
struct imsg_parser_leaf leaf;
- uint32_t max_uid, i;
- int locked;
+ uint32_t max_uid, i, nout;
+ int locked, by_uid;
/* Before the reset below, so that a busy return changes nothing. */
locked = index_lock_acquire(ss->mailbox_dir_fd, &il, LOCK_SH | LOCK_NB);
sw->nnodes = nnodes;
max_uid = index_max_uid(&sw->idx);
+ (void)view_sync(ss, &sw->idx, 0);
for (i = 0; i < nnodes; i++) {
struct search_node *n = &sw->nodes[i];
in.lo_is_star = n->lo_is_star;
in.hi_is_star = n->hi_is_star;
+ by_uid = n->op == SEARCH_OP_UIDSET;
+ view_resolve(ss, &in, 1, &out, &nout, &by_uid);
+ if (n->op == SEARCH_OP_SEQSET && by_uid) {
+ n->op = SEARCH_OP_UIDSET;
+ n->seq_lo = nout > 0 ? out.lo : 0;
+ n->seq_hi = nout > 0 ? out.hi : 0;
+ n->lo_is_star = n->hi_is_star = 0;
+ continue;
+ }
(void)seqset_resolve(&in, 1, max, n->op == SEARCH_OP_SEQSET,
&out);
n->seq_lo = out.lo;
continue;
memset(&m, 0, sizeof(m));
- m.seqno = i;
+ m.seqno = view_seqno(ss, rec.uid, i);
m.uid = rec.uid;
m.modseq = rec.modseq;
struct imsg_mbox_search_match match;
memset(&match, 0, sizeof(match));
- match.seqno = i;
+ match.seqno = m.seqno;
match.uid = m.uid;
match.modseq = m.modseq;
if (imsg_compose(&iev->ibuf, IMSG_MBOX_SEARCH_MATCH,
blob - ffb4cf902c1fc490f7cdf4d038efe4efab3c46f3
blob + 4061bcf4257fbef8e964f7f25980a2576a5360c8
--- src/mbox_store.c
+++ src/mbox_store.c
}
}
-/* RFC 9051 SS6.4.6 STORE: plan every message, then change */
+/* RFC 9051 SS6.4.6 STORE: plan every message, then change; LOCK_EX held */
int
-handle_mbox_store(struct imsg_mbox_store *req, const struct seq_range *ranges,
- uint32_t nranges, struct store_session *ss)
+store_apply(struct store_session *ss, const struct imsg_mbox_store *req,
+ struct mbox_index *idx, const struct seq_range *resolved,
+ uint32_t nresolved, int by_uid, int synced, const uint64_t *since,
+ struct store_step **stepsp, size_t *nstepsp)
{
- struct imsgev *iev = &ss->iev;
- int locked;
- struct mbox_index idx;
- struct imsg_mbox_result result;
- struct index_lock il = INDEX_LOCK_INIT;
- struct seq_range resolved[SEQSET_MAX_RANGES];
struct store_step *steps = NULL, *grown;
- size_t nsteps = 0, maxsteps = 0, k;
- uint32_t i, nresolved, max_hi, sent = 0;
- uint64_t new_modseq, reported = 0;
+ size_t nsteps = 0, maxsteps = 0;
+ uint32_t i, max_hi;
+ uint64_t new_modseq = idx->highestmodseq + 1;
int ok = 1, changed = 0, pass;
- memset(&idx, 0, sizeof(idx));
-
- if (!index_field_valid(req->keywords)) {
- log_warnx("session %u: STORE: refusing unsafe keywords field",
- session_id);
- ok = 0;
- goto done;
- }
- /* nothing touched yet: a busy return is free */
- locked = index_lock_acquire(ss->mailbox_dir_fd, &il, LOCK_EX | LOCK_NB);
- if (locked == 1)
- return (1);
- if (locked == -1) {
- ok = 0;
- goto done;
- }
- if (index_load(il.fd, &idx) == -1) {
- ok = 0;
- goto done;
- }
-
- reported = idx.highestmodseq;
- new_modseq = idx.highestmodseq + 1;
-
- /* RFC 9051 SS6.4.9 */
- nresolved = seqset_resolve(ranges, nranges, req->by_uid ?
- index_max_uid(&idx) : (uint32_t)idx.nlines, !req->by_uid,
- resolved);
max_hi = seqset_max_hi(resolved, nresolved);
-
for (pass = 0; pass < 2 && ok; pass++) {
- for (i = 1; i <= (uint32_t)idx.nlines; i++) {
+ for (i = 1; i <= (uint32_t)idx->nlines; i++) {
struct index_rec rec;
struct store_step st;
enum seqset_pos pos;
char *dup;
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,
- req->by_uid, rec.uid, i);
+ by_uid, rec.uid, i);
if (pos == SEQSET_PAST_END)
break;
- if (pos == SEQSET_SKIP)
+ if (pos == SEQSET_SKIP ||
+ (since != NULL && rec.modseq <= *since))
continue;
memset(&st, 0, sizeof(st));
break;
}
steps[nsteps++] = st;
- free(idx.lines[i - 1]);
- idx.lines[i - 1] = dup;
+ free(idx->lines[i - 1]);
+ idx->lines[i - 1] = dup;
if (st.changed)
changed = 1;
}
}
if (ok && changed) {
- idx.highestmodseq = new_modseq;
- if (index_save(ss->mailbox_dir_fd, &idx) == -1)
+ idx->highestmodseq = new_modseq;
+ if (index_save(ss->mailbox_dir_fd, idx) == -1)
ok = 0;
}
- if (!ok) {
- store_undo(ss, &idx, steps, nsteps);
+ if (!ok)
+ store_undo(ss, idx, steps, nsteps);
+ 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;
+ *stepsp = steps;
+ *nstepsp = nsteps;
+ return (ok);
+}
+
+int
+handle_mbox_store(struct imsg_mbox_store *req, const struct seq_range *ranges,
+ 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;
+ struct seq_range resolved[SEQSET_MAX_RANGES];
+ struct seq_range vr[SEQSET_MAX_RANGES];
+ struct store_step *steps = NULL;
+ size_t nsteps = 0, k;
+ uint32_t nresolved, nvr, sent = 0;
+ uint64_t reported = 0;
+ int ok = 1, by_uid, synced;
+ int ghost;
+
+ memset(&idx, 0, sizeof(idx));
+
+ if (!index_field_valid(req->keywords)) {
+ log_warnx("session %u: STORE: refusing unsafe keywords field",
+ session_id);
+ ok = 0;
goto done;
}
+ /* nothing touched yet: a busy return is free */
+ locked = index_lock_acquire(ss->mailbox_dir_fd, &il, LOCK_EX | LOCK_NB);
+ if (locked == 1)
+ return (1);
+ if (locked == -1) {
+ ok = 0;
+ goto done;
+ }
+ if (index_load(il.fd, &idx) == -1) {
+ ok = 0;
+ goto done;
+ }
+
+ synced = view_sync(ss, &idx, req->by_uid) == 0;
+ by_uid = req->by_uid;
+ view_resolve(ss, ranges, nranges, vr, &nvr, &by_uid);
+
reported = idx.highestmodseq;
+ /* RFC 9051 SS6.4.9 */
+ nresolved = seqset_resolve(vr, nvr, by_uid ?
+ index_max_uid(&idx) : (uint32_t)idx.nlines, !by_uid, resolved);
+ ghost = !req->by_uid && view_names_ghost(ss, &idx, resolved,
+ nresolved);
+
+ ok = store_apply(ss, req, &idx, resolved, nresolved, by_uid, synced,
+ NULL, &steps, &nsteps);
+ if (!ok)
+ goto done;
+ reported = idx.highestmodseq;
+
for (k = 0; k < nsteps; k++) {
const struct store_step *st = &steps[k];
struct index_rec rec;
struct imsg_mbox_store_modified mod;
memset(&mod, 0, sizeof(mod));
- mod.seqno = st->seqno;
+ mod.seqno = view_seqno(ss, rec.uid, st->seqno);
mod.uid = rec.uid;
if (imsg_compose(&iev->ibuf, IMSG_MBOX_STORE_MODIFIED,
0, 0, -1, &mod, sizeof(mod)) == -1)
char newsuffix[16];
memset(&meta, 0, sizeof(meta));
- meta.seqno = st->seqno;
+ meta.seqno = view_seqno(ss, rec.uid, st->seqno);
meta.uid = rec.uid;
meta.modseq = st->modseq;
(void)snprintf(newsuffix, sizeof(newsuffix), "2,%s",
free(steps);
memset(&result, 0, sizeof(result));
- result.error = ok ? MBOX_OP_OK : MBOX_OP_ERR_GENERIC;
+ /* RFC 2180 SS4.2.1-SS4.2.3 */
+ result.error = !ok ? MBOX_OP_ERR_GENERIC : ghost && !req->silent ?
+ MBOX_OP_ERR_EXPUNGEISSUED : MBOX_OP_OK;
result.count = sent;
/* never a value the index does not hold */
result.highestmodseq = reported;
goto done;
}
reported = idx.highestmodseq;
+ if (!req->silent)
+ (void)view_sync(ss, &idx, 1);
if (req->by_uid)
nresolved = seqset_resolve(ranges, nranges,
log_warn("session %u: unlink %s, left on disk with no "
"index entry", session_id, path);
- if (!req->silent) {
- struct imsg_mbox_expunged exp;
-
- memset(&exp, 0, sizeof(exp));
- exp.seqno = gone[k].seqno;
- exp.uid = rec.uid;
- if (imsg_compose(&iev->ibuf, IMSG_MBOX_EXPUNGED, 0, 0,
- -1, &exp, sizeof(exp)) == -1)
- log_warn("session %u: imsg_compose "
- "IMSG_MBOX_EXPUNGED", session_id);
- else
- sent++;
- }
+ if (!req->silent &&
+ send_expunged(ss, gone[k].seqno, rec.uid) == 0)
+ sent++;
}
done:
blob - b27d4cefa382bca517e4f751af19673567f77a9d
blob + 0efcbefdf39129fc1141c56af54fa50edb9a656c
--- src/mime.c
+++ src/mime.c
int has_boundary;
size_t part_starts[MIME_MAX_PARTS], part_ends[MIME_MAX_PARTS];
int n, want;
- const char *pbuf;
- size_t plen, phdrend;
+ const char *pbuf, *pbody;
+ size_t plen, phdrend, pbodylen, ih;
if (depth > MIME_MAX_DEPTH)
return (-1);
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)
+ return (-1);
+ pbody = pbuf + phdrend;
+ pbodylen = plen - phdrend;
+
if (pathlen == 1) {
- /* a leaf only: no "combined children" of a multipart */
- char ctype[64], csub[64], cparams[600];
- char cboundary[70 + 1];
- int chb;
-
- cparams[0] = '\0';
- if (parse_content_type(pbuf, phdrend, ctype, sizeof(ctype),
- csub, sizeof(csub), cparams, sizeof(cparams), cboundary,
- sizeof(cboundary), &chb) == -1)
+ /* a leaf or a message: no "combined children" of a multipart */
+ if (strcasecmp(type, "MULTIPART") == 0)
return (-1);
- if (strcasecmp(ctype, "MULTIPART") == 0 ||
- (strcasecmp(ctype, "MESSAGE") == 0 &&
- (strcasecmp(csub, "RFC822") == 0 ||
- strcasecmp(csub, "GLOBAL") == 0)))
- return (-1);
-
- *part_out = pbuf + phdrend;
- *partlen_out = plen - phdrend;
+ *part_out = pbody;
+ *partlen_out = pbodylen;
return (0);
}
- return (find_mime_part(depth + 1, pbuf, phdrend, pbuf + phdrend,
- plen - phdrend, path + 1, pathlen - 1, part_out, partlen_out));
+ /* 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)) {
+ if (pbodylen == 0 ||
+ find_header_body_split(pbody, pbodylen, &ih) == -1)
+ return (-1);
+ return (locate_mime_part(depth + 1, pbody, ih, pbody + ih,
+ pbodylen - ih, path + 1, pathlen - 1, part_out,
+ partlen_out));
+ }
+
+ return (find_mime_part(depth + 1, pbuf, phdrend, pbody, pbodylen,
+ path + 1, pathlen - 1, part_out, partlen_out));
}
/* RFC 9051 SS6.4.5.1: a non-multipart message is its own part 1 */
int
-locate_mime_part(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)
+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;
- if (pathlen < 1)
+ if (depth > MIME_MAX_DEPTH || pathlen < 1)
return (-1);
params_fmt[0] = '\0';
return (-1);
if (strcasecmp(type, "MULTIPART") == 0)
- return (find_mime_part(1, hdr, hdrlen, body, bodylen, path,
- pathlen, part_out, partlen_out));
+ return (find_mime_part(depth + 1, hdr, hdrlen, body, bodylen,
+ path, pathlen, part_out, partlen_out));
- if (pathlen != 1 || path[0] != 1)
+ if (path[0] != 1)
return (-1);
- *part_out = body;
- *partlen_out = bodylen;
- return (0);
+ if (pathlen == 1) {
+ *part_out = body;
+ *partlen_out = bodylen;
+ return (0);
+ }
+ if (strcasecmp(type, "MESSAGE") != 0 ||
+ (strcasecmp(subtype, "RFC822") != 0 &&
+ strcasecmp(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 "<start.count>" applied to a range; past its end is empty */
}
/* RFC 9051 SS6.4.5: a missing part is an empty item */
- if (locate_mime_part(wholebuf, hdrend, wholebuf + hdrend,
+ if (locate_mime_part(0, wholebuf, hdrend, wholebuf + hdrend,
wholelen - hdrend, path, pathlen, &part, &partlen) == 0) {
*off_out = (uint64_t)(part - wholebuf);
*len_out = (uint64_t)partlen;
blob - 108e7ceb1611d9fc797ea19eeae6596f60131974
blob + e861c44b599d3f715f3058d691d8bba81707ce65
--- src/store.c
+++ src/store.c
free(ranges);
break;
}
- case IMSG_MBOX_IDLE_REFRESH: {
- struct imsg_mbox_idle_refresh req;
-
- if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
+ case IMSG_MBOX_IDLE_REFRESH:
+ if (imsg_get_len(&imsg) != 0) {
log_warnx("bad IMSG_MBOX_IDLE_REFRESH");
break;
}
- if (!require_mailbox_selected(ss,
- "IMSG_MBOX_IDLE_REFRESH"))
- break;
- handle_mbox_idle_refresh(&req, ss);
+ /* answered even if refused: the listener waits */
+ (void)require_mailbox_selected(ss,
+ "IMSG_MBOX_IDLE_REFRESH");
+ handle_mbox_idle_refresh(ss);
break;
- }
case IMSG_MBOX_APPEND: {
struct imsg_mbox_append req;
blob - bba0601daaaca5fdca6c7ef25b28c96d2e6f1a03
blob + 6ea1823a6c6737cef6a9d3176be27be5ab7ac21d
--- src/store_cmd.c
+++ src/store_cmd.c
session_write(s, "+ idling\r\n", 10);
if (s->state == SESSION_SELECTED) {
- session_request_idle_refresh(s, 1); /* seeds the baseline */
+ (void)session_request_idle_refresh(s);
session_idle_poll_arm(s);
}
blob - a37ae3887f2d08276ba8a3bbe156d22bf670f4e9
blob + 3c83389b2eb5b76d0b40c888d57458d13bb30233
--- src/store_internal.h
+++ src/store_internal.h
int fds;
int wait_parser; /* paused until one comes */
int no_parser; /* none to be had: go without */
+ int ghost;
+ uint64_t seen_modseq;
};
/* RFC 9051 SS6.4.4 SEARCH, yielding every SEARCH_YIELD_REQUESTS */
uint32_t *uids;
size_t n;
uint64_t modseq; /* highest reported; above it is news */
+ size_t ghosts; /* expunged, not yet told */
int valid;
};
const struct seq_range *, uint32_t, struct store_session *);
int handle_mbox_store(struct imsg_mbox_store *,
const struct seq_range *, uint32_t, struct store_session *);
+struct store_step;
+int store_apply(struct store_session *,
+ const struct imsg_mbox_store *, struct mbox_index *,
+ const struct seq_range *, uint32_t, int, int,
+ const uint64_t *, struct store_step **, size_t *);
int handle_mbox_expunge(struct imsg_mbox_expunge *,
const struct seq_range *, uint32_t, struct store_session *);
void handle_mbox_append_begin(struct store_session *,
int envbuf_append_address_list(char *, size_t, size_t *, char *,
size_t);
int append_field_nstring(char *, size_t, size_t *, const char *,
- uint32_t, const char *);
+ size_t, const char *);
int build_envelope(int, const char *, char **, uint32_t *);
int parser_request(uint32_t, const char *, int, const char *,
struct imsg_parser_req *, void *, size_t, uint32_t,
int parse_section_part(const char *, int *, int);
int find_mime_part(int, const char *, size_t, const char *,
size_t, const int *, int, const char **, size_t *);
-int locate_mime_part(const char *, size_t, const char *, size_t,
+int locate_mime_part(int, const char *, size_t, const char *, size_t,
const int *, int, const char **, size_t *);
void partial_range(int, uint32_t, uint32_t, uint64_t *, uint64_t *);
int extract_mime_part(int, const char *, const int *, int,
void idle_baseline_reset(struct store_session *);
int index_lock_acquire(int, struct index_lock *, int);
void index_lock_release(struct index_lock *);
-void handle_mbox_idle_refresh(const struct imsg_mbox_idle_refresh *,
- struct store_session *);
+void handle_mbox_idle_refresh(struct store_session *);
+void idle_baseline_seed(struct store_session *, const struct mbox_index *);
+int view_sync(struct store_session *, const struct mbox_index *, int);
+void view_resolve(const struct store_session *, const struct seq_range *,
+ uint32_t, struct seq_range *, uint32_t *, int *);
+uint32_t view_seqno(const struct store_session *, uint32_t, uint32_t);
+int view_names_ghost(const struct store_session *,
+ const struct mbox_index *, const struct seq_range *,
+ uint32_t);
+int send_expunged(struct store_session *, uint32_t, uint32_t);
void qresync_send_resync(const struct imsg_mbox_select *,
const struct seq_range *, uint32_t, struct mbox_index *,
struct store_session *);
blob - 760d3f6ac69ae23a6461fef09899118f0e047e3d
blob + 9a98af9d87a28cfac11ea767354ca2ceeecbbafd
--- src/store_ipc.c
+++ src/store_ipc.c
session_handle_store_modified(s, &m);
break;
}
- case IMSG_MBOX_IDLE_EXPUNGE: {
- struct imsg_mbox_idle_expunge item;
+ case IMSG_MBOX_VIEW_EXPUNGE: {
+ struct imsg_mbox_expunged item;
if (imsg_get_data(&imsg, &item, sizeof(item)) == -1) {
- log_warnx("bad IMSG_MBOX_IDLE_EXPUNGE");
+ log_warnx("bad IMSG_MBOX_VIEW_EXPUNGE");
break;
}
- session_handle_idle_expunge(s, &item);
+ session_send_expunge_response(s, &item);
break;
}
- case IMSG_MBOX_IDLE_FETCH: {
+ 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_IDLE_FETCH");
+ log_warnx("bad IMSG_MBOX_VIEW_FETCH");
break;
}
/* same risk as IMSG_MBOX_FETCH_META's flags above */
meta.flags[sizeof(meta.flags) - 1] = '\0';
- session_handle_idle_fetch(s, &meta);
+ session_handle_view_fetch(s, &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");
+ break;
+ }
+ snprintf(buf, sizeof(buf), "%u EXISTS", ve.exists);
+ session_untagged(s, buf);
+ break;
+ }
case IMSG_MBOX_IDLE_REFRESHED: {
struct imsg_mbox_idle_refreshed res;
s->qresync_fetches[s->qresync_nfetches++] = *m;
}
-void
-session_handle_idle_expunge(struct session *s,
- const struct imsg_mbox_idle_expunge *item)
-{
- char buf[32];
-
- /* DONE may have arrived while the refresh was in flight. */
- if (!s->idling || s->state != SESSION_SELECTED)
- return;
-
- snprintf(buf, sizeof(buf), "%u EXPUNGE", item->seqno);
- session_untagged(s, buf);
-}
-
/* RFC 9051 SS6.3.13: an unsolicited FETCH carries UID */
void
-session_handle_idle_fetch(struct session *s,
+session_handle_view_fetch(struct session *s,
const struct imsg_mbox_fetch_meta *meta)
{
char buf[MBOX_FLAGS_MAX + 96];
- /* DONE may have arrived while the refresh was in flight. */
- if (!s->idling || s->state != SESSION_SELECTED)
- return;
-
if (s->condstore_enabled)
snprintf(buf, sizeof(buf),
"%u FETCH (UID %u FLAGS (%s) MODSEQ (%llu))", meta->seqno,
session_handle_idle_refreshed(struct session *s,
const struct imsg_mbox_idle_refreshed *res)
{
- char buf[32];
-
s->idle_refresh_pending = 0;
if (!res->ok && res->busy)
else if (!res->ok)
log_warnx("session %u: IDLE refresh failed, keeping last "
"known state", s->id);
- else if (res->exists_changed && s->idling &&
- s->state == SESSION_SELECTED) {
- snprintf(buf, sizeof(buf), "%u EXISTS", res->exists);
- session_untagged(s, buf);
- }
- if (s->idle_refresh_again) {
- int seed = s->idle_refresh_again_seed;
-
+ if (s->refresh_status != NULL) {
+ session_reply(s, s->pending_tag, s->refresh_status,
+ s->refresh_text);
+ s->refresh_status = NULL;
+ } else if (s->idle_refresh_again && s->idling) {
s->idle_refresh_again = 0;
- s->idle_refresh_again_seed = 0;
- session_request_idle_refresh(s, seed);
+ (void)session_request_idle_refresh(s);
}
}
-void
-session_request_idle_refresh(struct session *s, int seed)
+int
+session_request_idle_refresh(struct session *s)
{
- struct imsg_mbox_idle_refresh req;
-
if (s->idle_refresh_pending) {
s->idle_refresh_again = 1;
- if (seed)
- s->idle_refresh_again_seed = 1;
- return;
+ return (0);
}
if (s->store_iev == NULL)
- return; /* no store child wired, nothing to ask */
+ return (-1);
- memset(&req, 0, sizeof(req));
- req.seed = seed;
- s->idle_refresh_pending = 1;
if (imsg_compose(&s->store_iev->ibuf, IMSG_MBOX_IDLE_REFRESH, 0, 0,
- -1, &req, sizeof(req)) == -1)
+ -1, NULL, 0) == -1) {
log_warn("session %u: imsg_compose IMSG_MBOX_IDLE_REFRESH",
s->id);
+ return (-1);
+ }
+ s->idle_refresh_pending = 1;
+ return (0);
}
static void
return;
}
- session_request_idle_refresh(s, 0);
+ (void)session_request_idle_refresh(s);
session_idle_poll_arm(s); /* an evtimer is one-shot */
}
SESSION_AUTHENTICATED : SESSION_SELECTED;
if (res->error != MBOX_OP_OK) {
- char text[32];
+ char text[64];
free(s->store_modified);
s->store_modified = NULL;
IMAP_BUSY_TEXT);
return;
}
- snprintf(text, sizeof(text), "%s failed", cmdname);
+ snprintf(text, sizeof(text), "%s%s failed",
+ res->error == MBOX_OP_ERR_EXPUNGEISSUED ?
+ "[EXPUNGEISSUED] " : "", cmdname);
session_reply(s, s->pending_tag, "NO", text);
return;
}