commit - 58a006b393da74d6143bdf2a5378f87f87bd70be
commit + 81c42ca212c93182ef77d20cbb4e58ac4b0de3c7
blob - 152b3f5b275cc7d4db1e7535377d06795b9e40fa
blob + 8c49ea0424b08f1a24a575117dad815777540219
--- README.md
+++ README.md
A from-scratch IMAP4rev2 ([RFC 9051](https://www.rfc-editor.org/rfc/rfc9051)) server for OpenBSD, written in C in the privilege-separated tradition of `smtpd(8)`, `httpd(8)`, and `ntpd(8)`. No third-party IMAP library.
-**Status:** 0.2.2 Pre-release, actively developed. Not a port. See [Getting the source](#getting-the-source) below for the repository.
+**Status:** 0.2.3 Pre-release, actively developed. Not a port. See [Getting the source](#getting-the-source) below for the repository.
## What it is
`SIGHUP` reloads `spool`, `append max`, `attachment max`, `account sessions`, `connections max`, `idle poll`, `lock timeout`, `login grace`, `startups`, and the TLS certificate/key without dropping connected sessions, `listen on` and `credentials` changes still require a restart. See `imapd.conf(5)`.
-IPv6 is supported (`listen on ::` or `listen on *` for dual-stack) but not the default, see the `listen on` directive in `imapd.conf(5)`.
+imapd listens on both IPv4 and IPv6 by default (`listen on *`); use `listen on 0.0.0.0` for IPv4 only or `listen on ::` for IPv6 only. See the `listen on` directive in `imapd.conf(5)`.
## Getting the source
## License
-ISC. See the copyright header in each source file.
+ISC. See the copyright header in each source file, and `CREDITS` for what each third-party copyright in them is for.
## More
blob - /dev/null
blob + 7e624d172740c7fcc358be72fe06639b4ba8ea66 (mode 644)
--- /dev/null
+++ CREDITS
+OpenIMAPD: credits and lineage
+
+Each file under src/ carries its copyright lines and the ISC licence text.
+Where a file carries a copyright other than David Williams', the notes
+below say what was borrowed from whom and why that copyright is carried.
+They were the files' header comments until 2026-10-05, and are kept here
+so the headers can take the form the rest of the tree uses; the
+attribution they record is unchanged.
+
+log.c
+-----
+This file's log_init()/log_procinit()/log_setverbose()/log_getverbose()/
+vlog()/logit()/log_warn()/log_warnx()/log_info()/log_debug()/fatal()/
+fatalx() API is the same daemon-logging idiom Henning Brauer wrote and
+that is carried, nearly verbatim, across essentially every privsep
+daemon in the OpenBSD base system (smtpd, bgpd, and many others all
+credit him in their own log.c). This file's implementation details
+(log_procname storage, log_init()'s parameters) are this project's own,
+but the API shape and name are that same shared idiom, so his copyright
+is carried forward here too, same rationale as parse.y's copyright
+chain.
+
+log.h
+-----
+The declarations here, log_init(), log_procinit(), log_setverbose(),
+log_getverbose(), log_warn(), log_warnx(), log_info(), log_debug(),
+logit(), vlog(), fatal() and fatalx(), have the names, argument types
+and return types of those in smtpd's log.h (usr.sbin/smtpd/log.h:26-55).
+That is the daemon-logging idiom Henning Brauer wrote and that smtpd,
+bgpd and most other privsep daemons in the OpenBSD base system carry
+his copyright for. smtpd's _r variants are not declared here. His
+copyright is carried forward for the same reason as log.c's.
+
+imsgev.c
+--------
+This file's name and its "struct imsgev" wrapper-around-imsgbuf+
+event(3) concept match Eric Faurot's imsgev.c in OpenBSD's ldapd
+(usr.sbin/ldapd/imsgev.c), not a generic/obvious name, so his copyright
+is carried forward here even though this file's actual function
+signatures and dispatch design (a caller-supplied raw libevent handler,
+vs. ldapd's callback+needfd model) were written independently and differ
+from his implementation.
+
+imsgev_add() below is the exception: it is a verbatim copy (up to
+whitespace and the choice of iev vs. iev->data as event_set()'s last
+argument) of the imsg_event_add() idiom shared across OpenBSD privsep
+daemons, traceable to usr.sbin/ospfd/ospfd.c:525-534 (Claudio Jeker
+2005, Esben Norby 2004, Henning Brauer 2003-2004) and copied with only
+that one-argument variation into dvmrpd.c, npppd.c, and rad.c. Their
+copyright is carried forward for that function specifically, same
+rationale as log.c's and parse.y's shared-idiom copyright chains.
+
+parent.c
+--------
+This file's boot-time peer-wiring handshake, setup_peer_send() and
+setup_done_send(), and the IMSG_SETUP_PEER/IMSG_SETUP_DONE message names
+they and imapd.h use, follows smtpd's setup_peers()/ setup_done()
+(usr.sbin/smtpd/smtpd.c:861-903) and its identically-named
+IMSG_SETUP_PEER/IMSG_SETUP_DONE enumerators (smtpd.h:212-213), which no
+other daemon in the OpenBSD base system defines under those names. The
+functions here are reworked onto imapd's own struct imsgev and multi-
+child bookkeeping rather than copied verbatim, but the message protocol
+and handshake shape are smtpd's; see the inline citations at this file's
+setup_peer_send() and setup_done_send().
+
+send_keymgr_init()'s TLS key permission check, fstat(2), then st_uid
+!= 0 and st_mode & (S_IRWXU|S_IRWXG|S_IRWXO) & ~0740, reuses smtpd's
+ssl_load_key() check (usr.sbin/smtpd/ssl.c:132-145) verbatim for that
+mask and rejection order; only the fixed 0740 bound and the imsg
+delivery around it are this file's own.
+
+listener.c
+----------
+The RSA_METHOD/EC_KEY_METHOD engine override below (keymgr_engine_
+init() and everything it installs) adapts smtpd's ca.c
+(rsa_engine_init()/ecdsa_engine_init()/rsae_priv_enc()/rsae_priv_
+dec()/ecdsae_do_sign(), ca.c:329-508) to imapd's own imsg conventions:
+the OpenSSL API shape leaves little room for independent structure, and
+this project's own licensing precedent (log.c, imsgev.c) already treats
+a borrow this close as needing the original author's copyright even
+where the implementation differs. keymgr_use_fake_private_key()'s two-
+line call shape is lifted from smtpd's smtp.c:187-194 (Gilles Chehade,
+Pierre-Yves Ritschard, Jacek Masiulaniec); see that function's own
+comment.
+
+keymgr.c
+--------
+This file's overall architecture, a dedicated process holding the real
+TLS private key, a "fake private key" installed in the TLS-terminating
+process instead, and a process-wide OpenSSL RSA_METHOD/EC_KEY_METHOD
+engine override that forwards every private-key operation here as a
+synchronous imsg round-trip, is smtpd's ca.c (Reyk Floeter, Gilles
+Chehade), ported to imapd's own imsg/privsep conventions rather than
+copied verbatim. The RSA_METHOD/ EC_KEY_METHOD engine-override code
+itself (the code this file's request/reply pair answers) lives in
+listener.c, not here; see the listener.c entry above for the matching
+attribution.
+
+keymgr_pubkey_hash() below additionally replicates, byte-for-byte,
+smtpd's ssl.c hash_x509()/ssl_pubkey_hash() algorithm (Pierre-Yves
+Ritschard, Reyk Floeter, Gilles Chehade): SHA256 of the certificate's
+DER-encoded SubjectPublicKeyInfo, formatted "SHA256:" plus lowercase
+hex. That exact format is what libtls's own (unexported)
+tls_cert_pubkey_hash() tags onto the fake key's OpenSSL ex_data slot 0
+at TLS-config time (lib/libtls/tls.c); this process's key lookup only
+works if it derives the identical string from the same certificate.
+
+parse.y
+-------
+The parser skeleton below (lgetc()/lungetc()/findeol(), the hand-
+written yylex() built on top of them, pushfile()/popfile(), and
+symset()/symget()) follows the structure common to ripd's, smtpd's, and
+httpd's own parse.y in the OpenBSD base system, all of which carry this
+same four-name copyright chain at their root. This file's grammar and
+domain-specific rules are original; the above four names are carried
+forward for the shared parser-skeleton lineage only.
+
+mbox_manage.c
+-------------
+Carries Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> in its
+header.
blob - 28d70578cab1d0af11363ec0d4b50101db9f8375
blob + f38b0c9a173bb56afa6dc4a4b3e3ce0e9cce1bea
--- src/Makefile
+++ src/Makefile
MAN= imapd.8 imapd.conf.5
-CFLAGS+= -Wall -Wextra -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes
CFLAGS+= -Wmissing-declarations -Wshadow -Wpointer-arith
-CFLAGS+= -Wsign-compare -Wcast-qual -Wcast-align
+CFLAGS+= -Wsign-compare -Wcast-align
# Explicit -g: bsd.prog.mk's DEBUG?=-g default apparently isn't reaching
# the actual compile line in this tree (the crash-diagnosis gdb session
blob - 596c4dfcef0195bd1299f70fbea1d32586cee463
blob + 1bab52693fc3f4c5eb7ed1bcee3f795343e99c8b
--- src/append_cmd.c
+++ src/append_cmd.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <errno.h>
#include <event.h>
#include <imsg.h>
+#include <limits.h>
#include <resolv.h>
#include <stdint.h>
#include <stdio.h>
#include "listener.h"
#include "mboxname.h"
-/* RFC 9051 SS9 date-time */
+/* RFC 9051 section 9 date-time */
int
parse_date_time(const char *s, int64_t *out)
{
int litnonsync;
};
-/* RFC 9051 SS6.3.12 APPEND */
+/* RFC 9051 section 6.3.12 APPEND */
int
parse_append_args(char *args, struct append_parsed *out, const char **errmsg)
{
- char *p = args;
+ char *p = args, *start, *end, *digits_stop;
+ const char *sub_err, *errstr;
+ char datebuf[64];
+ char digitsbuf[24];
+ size_t dlen, digits_len;
+ char saved;
+ int rc;
memset(out, 0, sizeof(*out));
*errmsg = NULL;
while (*p == ' ')
p++;
if (*p == '(') {
- char *start = p;
- char *end = strchr(p, ')');
- char saved;
- int rc;
- const char *sub_err;
-
- if (end == NULL) {
+ start = p;
+ if ((end = strchr(p, ')')) == NULL) {
*errmsg = "unterminated flag list";
return (-1);
}
}
if (*p == '"') {
- const char *start = p + 1;
- char *end = strchr(start, '"');
- size_t dlen;
- char datebuf[64];
-
- if (end == NULL) {
+ start = p + 1;
+ if ((end = strchr(start, '"')) == NULL) {
*errmsg = "unterminated date-time string";
return (-1);
}
*errmsg = "expected a message literal";
return (-1);
}
- {
- const char *start = p + 1;
- char *end = strchr(start, '}');
- char *digits_end;
- const char *digits_stop;
- char digitsbuf[24];
- size_t digits_len;
- unsigned long long litlen;
+ start = p + 1;
+ if ((end = strchr(start, '}')) == NULL) {
+ *errmsg = "malformed literal announcement";
+ return (-1);
+ }
- if (end == NULL) {
- *errmsg = "malformed literal announcement";
- return (-1);
- }
+ out->litnonsync = (end > start && end[-1] == '+');
+ digits_stop = out->litnonsync ? end - 1 : end;
+ digits_len = (size_t)(digits_stop - start);
- out->litnonsync = (end > start && end[-1] == '+');
- digits_stop = out->litnonsync ? end - 1 : end;
- digits_len = (size_t)(digits_stop - start);
+ if (digits_len == 0 || digits_len >= sizeof(digitsbuf)) {
+ *errmsg = "malformed literal octet count";
+ return (-1);
+ }
+ memcpy(digitsbuf, start, digits_len);
+ digitsbuf[digits_len] = '\0';
- if (digits_len == 0 || digits_len >= sizeof(digitsbuf)) {
- *errmsg = "malformed literal octet count";
- return (-1);
- }
- memcpy(digitsbuf, start, digits_len);
- digitsbuf[digits_len] = '\0';
+ if (digitsbuf[0] < '0' || digitsbuf[0] > '9') {
+ *errmsg = "malformed literal octet count";
+ return (-1);
+ }
+ out->litlen = strtonum(digitsbuf, 0, LLONG_MAX, &errstr);
+ if (errstr != NULL) {
+ *errmsg = "malformed literal octet count";
+ return (-1);
+ }
- /* strtoull(3) accepts a sign */
- if (digitsbuf[0] < '0' || digitsbuf[0] > '9') {
- *errmsg = "malformed literal octet count";
- return (-1);
- }
- errno = 0;
- litlen = strtoull(digitsbuf, &digits_end, 10);
- if (*digits_end != '\0' || errno == ERANGE) {
- *errmsg = "malformed literal octet count";
- return (-1);
- }
- out->litlen = (uint64_t)litlen;
-
- if (end[1] != '\0') {
- *errmsg = "literal must be the final argument";
- return (-1);
- }
+ if (end[1] != '\0') {
+ *errmsg = "literal must be the final argument";
+ return (-1);
}
return (0);
if (parsed.litlen > listener_append_max) {
char text[96];
- /* RFC 5530 LIMIT, stating the number as RFC 9051 SS7.1 does */
+ /* RFC 5530 LIMIT; RFC 9051 section 7.1 states the number */
(void)snprintf(text, sizeof(text), "[LIMIT] message exceeds "
"this server's %llu octet limit",
(unsigned long long)listener_append_max);
s->literal_remaining = parsed.litlen;
s->literal_pending = 1;
- /* RFC 9051 SS4.3: "+" only for synchronizing literals */
+ /* RFC 9051 section 4.3: "+" only for synchronizing literals */
if (!parsed.litnonsync) {
static const char cont[] = "+ Ready for literal data\r\n";
session_handle_mbox_appended(struct session *s,
const struct imsg_mbox_appended *res)
{
+ char buf[96];
+
s->state = s->append_prev_state;
if (res->error != MBOX_OP_OK) {
if (res->error == MBOX_OP_ERR_NO_SUCH_MAILBOX)
session_reply(s, s->pending_tag, "NO",
- /* RFC 9051 SS6.3.12 */
+ /* RFC 9051 section 6.3.12 */
"[TRYCREATE] no such mailbox");
else if (res->error == MBOX_OP_ERR_BUSY)
session_reply(s, s->pending_tag, "NO",
return;
}
- {
- char buf[96];
-
- snprintf(buf, sizeof(buf),
- "[APPENDUID %u %u] APPEND completed", res->uidvalidity,
- res->uid);
- session_reply(s, s->pending_tag, "OK", buf);
- }
+ snprintf(buf, sizeof(buf), "[APPENDUID %u %u] APPEND completed",
+ res->uidvalidity, res->uid);
+ session_reply(s, s->pending_tag, "OK", buf);
}
blob - 21da97eeae47457e4c695fb195895f59f99d0393
blob + d5a6e53fece5ca9abd5fc56e604051fabac867ed
--- src/auth.c
+++ src/auth.c
#include <grp.h>
#include <imsg.h>
#include <pwd.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int peer_fd;
char *slash;
char chrootdir[1024];
+ char unveil_path[512];
ssize_t n;
imsgev_ibuf_init(&ibuf3, 3);
imsgev_init_from_ibuf(&iev_parent, &ibuf3, auth_dispatch_parent, NULL);
- {
- char unveil_path[512];
+ (void)snprintf(unveil_path, sizeof(unveil_path), "/%s",
+ cred_file_basename);
+ if (unveil(unveil_path, "r") == -1)
+ fatal("unveil %s", unveil_path);
+ if (unveil(NULL, NULL) == -1)
+ fatal("unveil lock");
- (void)snprintf(unveil_path, sizeof(unveil_path), "/%s",
- cred_file_basename);
- if (unveil(unveil_path, "r") == -1)
- fatal("unveil %s", unveil_path);
- if (unveil(NULL, NULL) == -1)
- fatal("unveil lock");
- }
-
/* no recvfd or sendfd: the one peer fd came before this */
-#ifdef __OpenBSD__
if (pledge("stdio rpath", NULL) == -1)
fatal("pledge");
-#endif
event_dispatch();
fatalx("auth: exited event loop");
log_warnx("bad IMSG_AUTH_REQUEST");
break;
}
- /* imsg_get_data() does not NUL-terminate */
req.username[sizeof(req.username) - 1] = '\0';
req.password[sizeof(req.password) - 1] = '\0';
setproctitle("session %u auth", req.session_id);
if (auth_session_already_resolved(req.session_id)) {
- log_warnx("session %u: IMSG_AUTH_REQUEST for a "
- "session already successfully "
- "authenticated, refusing", req.session_id);
+ log_warnx("session %u: IMSG_AUTH_REQUEST: "
+ "already authenticated", req.session_id);
explicit_bzero(req.password,
sizeof(req.password));
break;
imsg_free(&imsg);
}
imsgev_rearm_read(iev);
- (void)fd;
}
static void
imsg_free(&imsg);
}
imsgev_rearm_read(iev);
- (void)fd;
}
/* network usernames reach syslog only through here */
res->ok = 0;
auth_safe_name(req->username, overname, sizeof(overname));
- log_info("session %u: authentication failed for \"%s\" "
- "(over AUTH_MAX_TRIES, not checked)", req->session_id,
- overname);
+ log_info("session %u: authentication failed for \"%s\" (over "
+ "AUTH_MAX_TRIES)", req->session_id, overname);
return;
}
int *maxcost)
{
FILE *fp;
- char line[1024];
+ struct stat st;
struct cred_entry ce;
+ char *line = NULL;
+ size_t linesize = 0;
int found = 0, cost;
*maxcost = 0;
log_warn("fopen %s", path);
return (-1);
}
-
- {
- struct stat st;
-
- if (fstat(fileno(fp), &st) == -1) {
- log_warn("fstat %s", path);
- fclose(fp);
- return (-1);
- }
- if (!cred_file_secure(&st)) {
- log_warnx("%s: insecure (mode %04o, owner uid %u), "
- "must be owned by root or the current user and "
- "not group-writable, group-executable, or "
- "world-accessible; refusing all authentication "
- "until this is fixed",
- path, (unsigned int)(st.st_mode & 07777),
- (unsigned int)st.st_uid);
- fclose(fp);
- return (-1);
- }
+ if (fstat(fileno(fp), &st) == -1) {
+ log_warn("fstat %s", path);
+ fclose(fp);
+ return (-1);
}
+ if (!cred_file_secure(&st)) {
+ log_warnx("%s: insecure mode %04o, owner uid %u; "
+ "authentication refused",
+ path, (unsigned int)(st.st_mode & 07777),
+ (unsigned int)st.st_uid);
+ fclose(fp);
+ return (-1);
+ }
- while (fgets(line, sizeof(line), fp) != NULL) {
+ while (getline(&line, &linesize, fp) != -1) {
char *p = line;
char *fields[5];
+ const char *errstr;
int i;
- char *ep;
- unsigned long ulval;
- /* fgets(3) splits an over-long line */
- if (strchr(line, '\n') == NULL &&
- strlen(line) == sizeof(line) - 1) {
- log_warnx("%s: over-long line, refusing to parse the "
- "credential file", path);
- explicit_bzero(line, sizeof(line));
- explicit_bzero(&ce, sizeof(ce));
- fclose(fp);
- return (-1);
- }
line[strcspn(line, "\n")] = '\0';
if (line[0] == '\0' || line[0] == '#')
continue;
/* crypt_checkpass(3) passes an empty hash and password */
if (fields[1][0] != '$' || fields[1][1] != '2')
continue;
- /* strtoul(3) accepts "-1", and uid 0 is root */
+ /* strtonum(3) takes "-1"; 0 is root, (uid_t)-1 is not an id */
if (fields[2][0] < '0' || fields[2][0] > '9' ||
fields[3][0] < '0' || fields[3][0] > '9')
continue;
- errno = 0;
- ulval = strtoul(fields[2], &ep, 10);
- if (*ep != '\0' || errno != 0 || ulval == 0 ||
- ulval >= (unsigned long)(uid_t)-1)
+ ce.uid = strtonum(fields[2], 1, UINT32_MAX - 1, &errstr);
+ if (errstr != NULL)
continue;
- ce.uid = (uid_t)ulval;
- errno = 0;
- ulval = strtoul(fields[3], &ep, 10);
- if (*ep != '\0' || errno != 0 || ulval == 0 ||
- ulval >= (unsigned long)(gid_t)-1)
+ ce.gid = strtonum(fields[3], 1, UINT32_MAX - 1, &errstr);
+ if (errstr != NULL)
continue;
- ce.gid = (gid_t)ulval;
if (strlcpy(ce.maildir, fields[4], sizeof(ce.maildir)) >=
sizeof(ce.maildir))
continue;
}
}
- /* line[] and ce held credential records */
- explicit_bzero(line, sizeof(line));
+ freezero(line, linesize);
explicit_bzero(&ce, sizeof(ce));
fclose(fp);
return (found ? 0 : -1);
blob - 295dbfe792a76217279f47edda320d56449332aa
blob + 2c7a5f12af243cb6db3e03d426ed7bf90cbc842f
--- src/auth_cmd.c
+++ src/auth_cmd.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include "log.h"
#include "listener.h"
-/* RFC 9051 SS6.1.1 capability strings, selected by session->tls_active. */
+/* RFC 9051 section 6.1.1 capability strings, by session->tls_active */
#define CAPABILITY_PRE_TLS "IMAP4rev2 STARTTLS LOGINDISABLED ID " \
"CONDSTORE QRESYNC"
#define CAPABILITY_POST_TLS "IMAP4rev2 AUTH=PLAIN LOGINDISABLED ID " \
"CONDSTORE QRESYNC"
-
int
cmd_capability(struct session *s, const char *tag, char *args)
{
- (void)args;
-
session_untagged(s, s->tls_active ?
"CAPABILITY " CAPABILITY_POST_TLS :
"CAPABILITY " CAPABILITY_PRE_TLS);
return (1);
}
-
int
cmd_noop(struct session *s, const char *tag, char *args)
{
- (void)args;
-
- /* RFC 9051 SS6.1.2: the poll; the reply waits for the refresh */
+ /* RFC 9051 section 6.1.2: the poll; the reply waits for the refresh */
if (s->state != SESSION_SELECTED ||
strlcpy(s->pending_tag, tag, sizeof(s->pending_tag)) >=
sizeof(s->pending_tag) || session_request_idle_refresh(s) == -1) {
return (1);
}
-
int
cmd_logout(struct session *s, const char *tag, char *args)
{
- (void)args;
-
- /* Exact example text from RFC 9051 SS6.1.3. */
+ /* Exact example text from RFC 9051 section 6.1.3. */
session_untagged(s, "BYE IMAP4rev2 Server logging out");
session_reply(s, tag, "OK", "LOGOUT completed");
session_teardown(s, "logout");
return (0);
}
-
int
cmd_id(struct session *s, const char *tag, char *args)
{
const char *errmsg;
int inquote = 0;
- /* RFC 2971 SS3.1: only literals are read; the reply is NIL */
+ /* RFC 2971 section 3.1: only literals are read; the reply is NIL */
while (p != NULL && *p != '\0') {
if (!inquote && *p == '{') {
if (read_literal(&p, &lit, &len, &errmsg) == -1)
return (1);
}
-
int
cmd_login(struct session *s, const char *tag, char *args)
{
- (void)args;
-
session_reply(s, tag, "NO",
"LOGIN not supported, use AUTHENTICATE PLAIN");
return (1);
}
-
int
cmd_starttls(struct session *s, const char *tag, char *args)
{
return (1);
}
if (s->tls_active) {
- /* RFC 9051 SS6.2.1: BAD if STARTTLS comes after negotiation */
+ /* RFC 9051 section 6.2.1: STARTTLS after negotiation is BAD */
session_reply(s, tag, "BAD", "TLS already active");
return (1);
}
return (1);
}
-/* RFC 4616 SS2: authzid/authcid/passwd up to 255 octets + 2 NULs = 767 */
+/* RFC 4616 section 2: authzid/authcid/passwd up to 255 octets + 2 NULs = 767 */
#define SASL_PLAIN_MAX 768
/* as auth.c's auth_safe_name(): '?' for anything unprintable */
passwd = nul + 1;
passwdlen = (size_t)rawlen - off - authcidlen - 1;
- /* RFC 4616 SS2: empty prep result fails verification, NO not BAD */
+ /* RFC 4616 section 2: an empty prep result is NO, not BAD */
if (authcidlen == 0 || passwdlen == 0) {
session_reply(s, tag, "NO",
"[AUTHENTICATIONFAILED] authentication failed");
{
s->auth_cont = 0;
- /* RFC 9051 SS6.2.2: lone "*" cancels exchange */
+ /* RFC 9051 section 6.2.2: lone "*" cancels exchange */
if (strcmp(line, "*") == 0) {
session_reply(s, s->pending_tag, "BAD",
"AUTHENTICATE cancelled");
return (1);
}
- return sasl_plain_finish(s, s->pending_tag, line, 0);
+ return (sasl_plain_finish(s, s->pending_tag, line, 0));
}
int
done = strcasecmp(line, "DONE") == 0;
s->refresh_status = done ? "OK" : "BAD";
s->refresh_text = done ? "IDLE terminated" : "expected DONE";
- /* RFC 9051 SS7.5.1: an EXPUNGE in flight goes before the reply */
+ /* RFC 9051 section 7.5.1: an EXPUNGE in flight goes before the reply */
if (!s->idle_refresh_pending) {
session_reply(s, s->pending_tag, s->refresh_status,
s->refresh_text);
return (1);
}
-
int
cmd_authenticate(struct session *s, const char *tag, char *args)
{
initial = (*p != '\0') ? p : NULL;
}
- /* RFC 9051 SS6.2.2: MUST NOT permit plaintext mechanisms pre-TLS */
+ /* RFC 9051 section 6.2.2: no plaintext mechanisms before TLS */
if (!s->tls_active) {
/* RFC 5530 PRIVACYREQUIRED: retry after STARTTLS */
session_reply(s, tag, "NO",
if (initial != NULL) {
s->scrub_inbuf = 1;
- return sasl_plain_finish(s, tag, initial, 1);
+ return (sasl_plain_finish(s, tag, initial, 1));
}
if (strlcpy(s->pending_tag, tag, sizeof(s->pending_tag)) >=
int
stub_not_implemented(struct session *s, const char *tag, const char *cmdname)
{
- log_debug("session %u: %s not implemented (store.c's IMSG_MBOX_* "
- "wire protocol isn't designed yet)", s->id, cmdname);
+ log_debug("session %u: %s not implemented", s->id, cmdname);
session_reply(s, tag, "NO", "not implemented");
return (1);
}
-/* RFC 9051 SS6.3.1 ENABLE */
+/* RFC 9051 section 6.3.1 ENABLE */
int
cmd_enable(struct session *s, const char *tag, char *args)
{
blob - 8e8b857481d199891feabf48f917a2758c8c1a62
blob + a5e1441eb3b2b803cc7daebfc68f9faa3ddca484
--- src/envelope.c
+++ src/envelope.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
+#include <sys/param.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
return (0);
}
-
int
envbuf_append_str(char *buf, size_t bufsize, size_t *outlen, const char *s)
{
for (i = 0; i < vallen; i++) {
char c = val[i];
- /* RFC 9051 SS4.3: substituted, not dropped */
+ /* RFC 9051 section 4.3: substituted, not dropped */
if (c == '\0' || c == '\r' || c == '\n')
c = ' ';
if ((c == '"' || c == '\\') &&
return (-1);
}
-/* RFC 5322 SS3.2.4: the index just past the quoted string at i */
+/* RFC 5322 section 3.2.4: the index just past the quoted string at i */
static size_t
quoted_end(const char *s, size_t len, size_t i)
{
return (len);
}
-/* RFC 5322 SS3.2.2: each comment becomes one space; returns the length */
+/* RFC 5322 section 3.2.2: each comment becomes one space; returns the length */
size_t
address_uncomment(char *s, size_t len)
{
return (n);
}
-/* unquoted in place (RFC 5322 SS3.2.4); a phrase keeps one space per run */
+/* RFC 5322 section 3.2.4, unquoted in place; one space per run in a phrase */
size_t
address_cook(char *s, size_t len, int phrase)
{
return (n);
}
-/* RFC 5322 SS3.4 mailbox, split and cooked in place; -1 if it is none */
+/* RFC 5322 section 3.4 mailbox, split and cooked in place; -1 if it is none */
int
address_split(char *tok, size_t toklen, struct address *a)
{
speclen--;
}
- /* RFC 5322 SS4.4 obs-route, for RFC 9051's at-domain-list */
+ /* RFC 5322 section 4.4 obs-route, for RFC 9051's at-domain-list */
if (speclen > 0 && spec[0] == '@') {
colon = unquoted_find(spec, speclen, 0, ":");
if (colon == speclen)
return (0);
}
-/* RFC 9051 SS7.5.2 address structure; a group marker has no host */
+/* RFC 9051 section 7.5.2 address structure; a group marker has no host */
int
envbuf_append_one_address(char *buf, size_t bufsize, size_t *outlen,
const struct address *a)
return (envbuf_append(buf, bufsize, outlen, ")", 1));
}
-/* RFC 5322 SS3.4 address-list: the next mailbox, group start or end */
+/* RFC 5322 section 3.4 address-list: the next mailbox, group start or end */
int
address_list_next(char *val, size_t vallen, size_t *pos, int *ingroup,
char **tok_out, size_t *toklen_out)
return (0);
}
-/* RFC 5322 SS4.5.3: every occurrence of the field as one list, or NIL */
+/* RFC 5322 section 4.5.3: every occurrence of the field as one list, or NIL */
static int
envbuf_append_address_fields(char *buf, size_t bufsize, size_t *outlen,
const char *hdr, size_t hdrlen, const char *field, int *seen)
{
const char *name, *val;
char *unf;
- size_t flen = strlen(field), off = 0, namelen, vallen;
+ size_t flen, off = 0, namelen, vallen;
size_t ulen, i;
int any = 0, rc;
+ flen = strlen(field);
*seen = 0;
while (header_next_field(hdr, hdrlen, &off, &name, &namelen, &val,
&vallen) == 1) {
return (rc);
}
-/* RFC 9051 SS7.5.2 ENVELOPE; Sender and Reply-To default to From */
+/* RFC 9051 section 7.5.2 ENVELOPE; Sender and Reply-To default to From */
static int
envelope_from_header(const char *hdr, size_t hdrlen, char *out,
size_t outsize, size_t *outlen)
if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
return (-1);
- for (fi = 0; fi < 2; fi++) {
+ for (fi = 0; fi < nitems(fallback_fields); fi++) {
save = *outlen;
if (envbuf_append_address_fields(out, outsize, outlen, hdr,
hdrlen, fallback_fields[fi], &seen) == -1)
return (-1);
}
- for (fi = 0; fi < 3; fi++) {
+ for (fi = 0; fi < nitems(addr_fields); fi++) {
if (envbuf_append_address_fields(out, outsize, outlen, hdr,
hdrlen, addr_fields[fi], &seen) == -1)
return (-1);
return (-1);
if (envelope_from_header(hdrbuf, hdrlen, out, sizeof(out),
&outlen) == -1) {
- log_warnx("session %u: message %s: formatted ENVELOPE exceeds "
- "ENVELOPE_MAX, ENVELOPE skipped", session_id, basename);
+ log_warnx("session %u: message %s: ENVELOPE over "
+ "ENVELOPE_MAX, skipped", session_id, basename);
free(hdrbuf);
return (-1);
}
return (0);
}
-/* RFC 9051 SS7.5.2 BODYSTRUCTURE; no extension data */
+/* RFC 9051 section 7.5.2 BODYSTRUCTURE; no extension data */
int
build_body_structure(int depth, int *nparts_used, const char *hdr,
size_t hdrlen, const char *body, size_t bodylen, char *out,
size_t outsize, size_t *outlen)
{
- char type[64], subtype[64];
- char params_fmt[600];
- /* RFC 2046 SS5.1.1 caps boundary at 70 chars, +1 NUL */
- char boundary[70 + 1];
- int has_boundary;
+ static const char *const cte_known[] = { "7BIT", "8BIT", "BINARY",
+ "BASE64", "QUOTED-PRINTABLE" };
+ struct content_type ct;
+ char encstr[40], numbuf[32];
+ char *idval = NULL, *descval = NULL, *encval = NULL;
+ const char *enc;
+ size_t idlen = 0, desclen = 0, enclen = 0, ihdr, ki;
+ int is_text, is_msg, rc;
if (depth > MIME_MAX_DEPTH)
return (-1);
if (++*nparts_used > MIME_MAX_PARTS)
return (-1);
- params_fmt[0] = '\0';
- if (parse_content_type(hdr, hdrlen, type, sizeof(type), subtype,
- sizeof(subtype), params_fmt, sizeof(params_fmt), boundary,
- sizeof(boundary), &has_boundary) == -1)
+ if (parse_content_type(hdr, hdrlen, &ct) == -1)
return (-1);
- if (strcasecmp(type, "MULTIPART") == 0) {
+ if (strcasecmp(ct.type, "MULTIPART") == 0) {
size_t part_starts[MIME_MAX_PARTS], part_ends[MIME_MAX_PARTS];
int n, i;
- if (!has_boundary)
+ if (!ct.has_boundary)
return (-1);
- if (split_multipart(body, bodylen, boundary, part_starts,
+ if (split_multipart(body, bodylen, ct.boundary, part_starts,
part_ends, &n, MIME_MAX_PARTS) == -1)
return (-1);
size_t plen = part_ends[i] - part_starts[i];
size_t phdrend;
- /* RFC 2046 SS5.1.1: an empty part is legal */
+ /* RFC 2046 section 5.1.1: an empty part is legal */
if (plen == 0)
phdrend = 0;
else if (find_header_body_split(pbuf, plen,
}
if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
return (-1);
- if (envbuf_append_nstring(out, outsize, outlen, subtype,
- strlen(subtype)) == -1)
+ if (envbuf_append_nstring(out, outsize, outlen, ct.subtype,
+ strlen(ct.subtype)) == -1)
return (-1);
return (envbuf_append(out, outsize, outlen, ")", 1));
}
- {
- char *idval = NULL, *descval = NULL, *encval = NULL;
- size_t idlen = 0, desclen = 0, enclen = 0, ihdr;
- char encstr[40];
- int is_text = (strcasecmp(type, "TEXT") == 0);
- int is_msg = strcasecmp(type, "MESSAGE") == 0 &&
- (strcasecmp(subtype, "RFC822") == 0 ||
- strcasecmp(subtype, "GLOBAL") == 0);
- int rc = 0;
+ is_text = strcasecmp(ct.type, "TEXT") == 0;
+ is_msg = strcasecmp(ct.type, "MESSAGE") == 0 &&
+ (strcasecmp(ct.subtype, "RFC822") == 0 ||
+ strcasecmp(ct.subtype, "GLOBAL") == 0);
- if (envbuf_append(out, outsize, outlen, "(", 1) == -1)
- return (-1);
- if (envbuf_append_nstring(out, outsize, outlen, type,
- strlen(type)) == -1)
- return (-1);
- if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
- return (-1);
- if (envbuf_append_nstring(out, outsize, outlen, subtype,
- strlen(subtype)) == -1)
- return (-1);
- if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
- return (-1);
- if (envbuf_append(out, outsize, outlen, params_fmt,
- strlen(params_fmt)) == -1)
- return (-1);
- if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
- return (-1);
+ if (envbuf_append(out, outsize, outlen, "(", 1) == -1)
+ return (-1);
+ if (envbuf_append_nstring(out, outsize, outlen, ct.type,
+ strlen(ct.type)) == -1)
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
+ return (-1);
+ if (envbuf_append_nstring(out, outsize, outlen, ct.subtype,
+ strlen(ct.subtype)) == -1)
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, ct.params_fmt,
+ strlen(ct.params_fmt)) == -1)
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
+ return (-1);
- if (extract_header_field(hdr, hdrlen, "Content-Id", &idval,
- &idlen) == 0)
- rc = envbuf_append_nstring(out, outsize, outlen, idval,
- idlen);
- else
- rc = envbuf_append_nstring(out, outsize, outlen,
- NULL, 0);
- free(idval);
- if (rc == -1)
- return (-1);
- if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
- return (-1);
+ if (extract_header_field(hdr, hdrlen, "Content-Id", &idval,
+ &idlen) == 0)
+ rc = envbuf_append_nstring(out, outsize, outlen, idval, idlen);
+ else
+ rc = envbuf_append_nstring(out, outsize, outlen, NULL, 0);
+ free(idval);
+ if (rc == -1)
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
+ return (-1);
- if (extract_header_field(hdr, hdrlen, "Content-Description",
- &descval, &desclen) == 0)
- rc = envbuf_append_nstring(out, outsize, outlen,
- descval, desclen);
- else
- rc = envbuf_append_nstring(out, outsize, outlen,
- NULL, 0);
- free(descval);
- if (rc == -1)
- return (-1);
- if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
- return (-1);
+ if (extract_header_field(hdr, hdrlen, "Content-Description",
+ &descval, &desclen) == 0)
+ rc = envbuf_append_nstring(out, outsize, outlen, descval,
+ desclen);
+ else
+ rc = envbuf_append_nstring(out, outsize, outlen, NULL, 0);
+ free(descval);
+ if (rc == -1)
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
+ return (-1);
- if (extract_header_field(hdr, hdrlen,
- "Content-Transfer-Encoding", &encval, &enclen) == 0 &&
- enclen > 0 && enclen < sizeof(encstr)) {
- memcpy(encstr, encval, enclen);
- encstr[enclen] = '\0';
- } else if (strlcpy(encstr, "7BIT", sizeof(encstr)) >=
- sizeof(encstr)) {
- return (-1);
+ if (extract_header_field(hdr, hdrlen, "Content-Transfer-Encoding",
+ &encval, &enclen) == 0 && enclen > 0 && enclen < sizeof(encstr)) {
+ memcpy(encstr, encval, enclen);
+ encstr[enclen] = '\0';
+ } else
+ strlcpy(encstr, "7BIT", sizeof(encstr));
+ free(encval);
+ enc = encstr;
+ for (ki = 0; ki < nitems(cte_known); ki++) {
+ if (strcasecmp(encstr, cte_known[ki]) == 0) {
+ enc = cte_known[ki];
+ break;
}
- free(encval);
- {
- static const char *const known[] = { "7BIT", "8BIT",
- "BINARY", "BASE64", "QUOTED-PRINTABLE" };
- size_t ki;
+ }
+ if (envbuf_append_nstring(out, outsize, outlen, enc, strlen(enc)) == -1)
+ return (-1);
+ if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
+ return (-1);
- for (ki = 0; ki < 5; ki++) {
- if (strcasecmp(encstr, known[ki]) == 0) {
- if (strlcpy(encstr, known[ki],
- sizeof(encstr)) >= sizeof(encstr))
- return (-1);
- break;
- }
- }
- }
- if (envbuf_append_nstring(out, outsize, outlen, encstr,
- strlen(encstr)) == -1)
- return (-1);
- if (envbuf_append(out, outsize, outlen, " ", 1) == -1)
- return (-1);
+ snprintf(numbuf, sizeof(numbuf), "%zu", bodylen);
+ if (envbuf_append_str(out, outsize, outlen, numbuf) == -1)
+ return (-1);
- {
- char numbuf[32];
+ /* RFC 9051 section 7.5.2 body-type-msg: envelope, body, lines */
+ if (is_msg && (bodylen == 0 ||
+ find_header_body_split(body, bodylen, &ihdr) == -1 ||
+ envbuf_append(out, outsize, outlen, " ", 1) == -1 ||
+ envelope_from_header(body, ihdr, out, outsize, outlen) == -1 ||
+ envbuf_append(out, outsize, outlen, " ", 1) == -1 ||
+ build_body_structure(depth + 1, nparts_used, body, ihdr,
+ body + ihdr, bodylen - ihdr, out, outsize, outlen) == -1))
+ return (-1);
- snprintf(numbuf, sizeof(numbuf), "%zu", bodylen);
- if (envbuf_append_str(out, outsize, outlen,
- numbuf) == -1)
- return (-1);
- }
+ if (is_text || is_msg) {
+ size_t lines = 0, li;
- /* RFC 9051 SS7.5.2 body-type-msg: envelope, body, lines */
- if (is_msg && (bodylen == 0 ||
- find_header_body_split(body, bodylen, &ihdr) == -1 ||
- envbuf_append(out, outsize, outlen, " ", 1) == -1 ||
- envelope_from_header(body, ihdr, out, outsize,
- outlen) == -1 ||
- envbuf_append(out, outsize, outlen, " ", 1) == -1 ||
- build_body_structure(depth + 1, nparts_used, body, ihdr,
- body + ihdr, bodylen - ihdr, out, outsize, outlen) == -1))
- return (-1);
-
- if (is_text || is_msg) {
- size_t lines = 0, li;
- char numbuf2[32];
-
- for (li = 0; li < bodylen; li++) {
- if (body[li] == '\n')
- lines++;
- }
- snprintf(numbuf2, sizeof(numbuf2), " %zu", lines);
- if (envbuf_append_str(out, outsize, outlen,
- numbuf2) == -1)
- return (-1);
+ for (li = 0; li < bodylen; li++) {
+ if (body[li] == '\n')
+ lines++;
}
-
- return (envbuf_append(out, outsize, outlen, ")", 1));
+ snprintf(numbuf, sizeof(numbuf), " %zu", lines);
+ if (envbuf_append_str(out, outsize, outlen, numbuf) == -1)
+ return (-1);
}
+
+ return (envbuf_append(out, outsize, outlen, ")", 1));
}
int
blob - e04d21fe95064d78b9b7c40c63a0569221f683f7
blob + da4556a01b07ad01066be08de5e5a4b7ec0b1a8f
--- src/fetch_cmd.c
+++ src/fetch_cmd.c
#include "log.h"
#include "listener.h"
+/* RFC 9051 section 9 nz-number: digit-nz first, so no sign or leading zero */
int
parse_nz_number(const char *str, uint32_t *out)
{
- unsigned long v;
- char *end;
+ const char *errstr;
+ long long v;
- if (str == NULL || *str == '\0' || *str == '0')
+ if (str == NULL || *str < '1' || *str > '9')
return (-1);
- errno = 0;
- v = strtoul(str, &end, 10);
- if (*end != '\0' || errno == ERANGE || v == 0 || v > UINT32_MAX)
+ v = strtonum(str, 1, UINT32_MAX, &errstr);
+ if (errstr != NULL)
return (-1);
*out = (uint32_t)v;
return (0);
}
-/* RFC 9051 SS9 sequence-set */
+/* RFC 9051 section 9 sequence-set */
int
parse_sequence_set(const char *text, struct seq_range ranges[SEQSET_MAX_RANGES],
uint32_t *nranges, const char **errmsg)
if (*p == '\0')
break;
- p++; /* skip ',' */
+ p++;
}
*nranges = n;
return (start);
}
-/* RFC 9051 SS9 HEADER.FIELDS[.NOT]; -1 is BAD */
+/* RFC 9051 section 9 HEADER.FIELDS[.NOT]; -1 is BAD */
int
parse_header_fields_att(const char *inner, int *not_out, char *fields_out,
size_t fields_outsize)
return (0);
}
-/* RFC 9051 SS6.4.5.1 section-part */
+/* RFC 9051 section 6.4.5.1 section-part */
int
section_part_valid(const char *s)
{
return (0);
s++;
if (*s == '\0')
- return (0); /* trailing dot */
+ return (0);
}
return (1);
}
-/* RFC 9051 SS6.4.5 <start.count> */
+/* RFC 9051 section 6.4.5 <start.count> */
int
parse_partial_suffix(const char *s, int *has_partial_out,
uint32_t *start_out, uint32_t *count_out)
return (0);
}
+/* RFC 9051 section 6.4.5 fetch-att list, parsed */
+struct fetch_atts {
+ uint32_t attrs;
+ int degraded;
+ int bodystructure_full;
+ int header_fields_not;
+ int has_partial;
+ uint32_t partial_start;
+ uint32_t partial_count;
+ char header_fields[HEADER_FIELDS_MAX];
+ char header_fields_label[HEADER_FIELDS_LABEL_MAX];
+ char section_part[SECTION_PART_MAX];
+};
+
static int
-parse_body_peek_section_tok(const char *bracket_start, uint32_t *attrs_inout,
- char *section_part_out, size_t section_part_outsize,
- int *has_partial_out, uint32_t *partial_start_out,
- uint32_t *partial_count_out, int *degraded_out, const char **errmsg)
+parse_body_peek_section_tok(const char *bracket_start, struct fetch_atts *fa,
+ int *degraded_out, const char **errmsg)
{
char *close;
char inner[SECTION_PART_MAX];
suffix = close + 1;
if (suffix[0] != '\0' &&
- parse_partial_suffix(suffix, has_partial_out, partial_start_out,
- partial_count_out) == -1) {
+ parse_partial_suffix(suffix, &fa->has_partial, &fa->partial_start,
+ &fa->partial_count) == -1) {
*errmsg = "malformed <partial> range";
return (-1);
}
if (inner[0] == '\0') {
- *attrs_inout |= MBOX_FETCH_BODY_WHOLE;
+ fa->attrs |= MBOX_FETCH_BODY_WHOLE;
} else if (strcasecmp(inner, "TEXT") == 0) {
- *attrs_inout |= MBOX_FETCH_BODY_TEXT;
+ fa->attrs |= MBOX_FETCH_BODY_TEXT;
} else if (section_part_valid(inner)) {
- *attrs_inout |= MBOX_FETCH_BODY_PART;
- if (strlcpy(section_part_out, inner, section_part_outsize) >=
- section_part_outsize) {
- *attrs_inout &= ~MBOX_FETCH_BODY_PART;
+ fa->attrs |= MBOX_FETCH_BODY_PART;
+ if (strlcpy(fa->section_part, inner,
+ sizeof(fa->section_part)) >= sizeof(fa->section_part)) {
+ fa->attrs &= ~MBOX_FETCH_BODY_PART;
*degraded_out = 1;
return (0);
}
}
static int
-parse_body_peek_header_fields_tok(const char *sec, uint32_t *attrs_inout,
- int *header_fields_not_out, char *header_fields_out,
- size_t header_fields_outsize, char *header_fields_label_out,
- size_t header_fields_label_outsize, const char **errmsg)
+parse_body_peek_header_fields_tok(const char *sec, struct fetch_atts *fa,
+ const char **errmsg)
{
- size_t len = strlen(sec);
+ size_t len;
char inner[HEADER_FIELDS_LABEL_MAX];
+ len = strlen(sec);
if (len < 1 || sec[len - 1] != ']') {
*errmsg = "malformed HEADER.FIELDS section";
return (-1);
}
- if (*attrs_inout & MBOX_FETCH_HEADER_FIELDS)
+ if (fa->attrs & MBOX_FETCH_HEADER_FIELDS)
/* already captured one, ignore any further duplicates */
return (0);
memcpy(inner, sec, len - 1);
inner[len - 1] = '\0';
- if (parse_header_fields_att(inner, header_fields_not_out,
- header_fields_out, header_fields_outsize) == -1) {
+ if (parse_header_fields_att(inner, &fa->header_fields_not,
+ fa->header_fields, sizeof(fa->header_fields)) == -1) {
*errmsg = "malformed HEADER.FIELDS section";
return (-1);
}
- if (strlcpy(header_fields_label_out, inner,
- header_fields_label_outsize) >= header_fields_label_outsize) {
+ if (strlcpy(fa->header_fields_label, inner,
+ sizeof(fa->header_fields_label)) >=
+ sizeof(fa->header_fields_label)) {
*errmsg = "HEADER.FIELDS section too long";
return (-1);
}
- *attrs_inout |= MBOX_FETCH_HEADER_FIELDS;
+ fa->attrs |= MBOX_FETCH_HEADER_FIELDS;
return (1);
}
return (NULL);
}
-/* RFC 9051 SS6.4.5 fetch-att; unsupported items are skipped */
+/* RFC 9051 section 6.4.5 fetch-att; unsupported items are skipped */
int
-parse_fetch_atts(char *spec, uint32_t *attrs_out, int *degraded_out,
- int *header_fields_not_out, char *header_fields_out,
- size_t header_fields_outsize, char *header_fields_label_out,
- size_t header_fields_label_outsize, int *bodystructure_full_out,
- char *section_part_out, size_t section_part_outsize,
- int *has_partial_out, uint32_t *partial_start_out,
- uint32_t *partial_count_out, const char **errmsg)
+parse_fetch_atts(char *spec, struct fetch_atts *fa, const char **errmsg)
{
char *p, *tok, *save;
const char *sec;
size_t len;
- uint32_t attrs = 0;
- int degraded = 0, plain, d;
- int has_partial = 0;
- uint32_t partial_start = 0, partial_count = 0;
+ int plain, d;
*errmsg = NULL;
- *attrs_out = 0;
- *degraded_out = 0;
- *header_fields_not_out = 0;
- header_fields_out[0] = '\0';
- header_fields_label_out[0] = '\0';
- *bodystructure_full_out = 0;
- section_part_out[0] = '\0';
- *has_partial_out = 0;
- *partial_start_out = 0;
- *partial_count_out = 0;
+ memset(fa, 0, sizeof(*fa));
if (spec == NULL || *spec == '\0') {
*errmsg = "missing message data item(s)";
for (tok = fetch_att_tok(p, &save); tok != NULL;
tok = fetch_att_tok(NULL, &save)) {
if (strcasecmp(tok, "FAST") == 0) {
- /* RFC 9051 SS6.4.5 FAST macro */
- attrs |= MBOX_FETCH_FLAGS | MBOX_FETCH_INTERNALDATE |
- MBOX_FETCH_RFC822_SIZE;
+ fa->attrs |= MBOX_FETCH_FLAGS |
+ MBOX_FETCH_INTERNALDATE | MBOX_FETCH_RFC822_SIZE;
} else if (strcasecmp(tok, "ALL") == 0) {
/* FAST + ENVELOPE */
- attrs |= MBOX_FETCH_FLAGS | MBOX_FETCH_INTERNALDATE |
- MBOX_FETCH_RFC822_SIZE | MBOX_FETCH_ENVELOPE;
+ fa->attrs |= MBOX_FETCH_FLAGS |
+ MBOX_FETCH_INTERNALDATE | MBOX_FETCH_RFC822_SIZE |
+ MBOX_FETCH_ENVELOPE;
} else if (strcasecmp(tok, "FULL") == 0) {
/* ALL + bare BODY */
- attrs |= MBOX_FETCH_FLAGS | MBOX_FETCH_INTERNALDATE |
- MBOX_FETCH_RFC822_SIZE | MBOX_FETCH_ENVELOPE |
- MBOX_FETCH_BODYSTRUCTURE;
+ fa->attrs |= MBOX_FETCH_FLAGS |
+ MBOX_FETCH_INTERNALDATE | MBOX_FETCH_RFC822_SIZE |
+ MBOX_FETCH_ENVELOPE | MBOX_FETCH_BODYSTRUCTURE;
} else if (strcasecmp(tok, "FLAGS") == 0) {
- attrs |= MBOX_FETCH_FLAGS;
+ fa->attrs |= MBOX_FETCH_FLAGS;
} else if (strcasecmp(tok, "UID") == 0) {
- attrs |= MBOX_FETCH_UID;
+ fa->attrs |= MBOX_FETCH_UID;
} else if (strcasecmp(tok, "INTERNALDATE") == 0) {
- attrs |= MBOX_FETCH_INTERNALDATE;
+ fa->attrs |= MBOX_FETCH_INTERNALDATE;
} else if (strcasecmp(tok, "RFC822.SIZE") == 0) {
- attrs |= MBOX_FETCH_RFC822_SIZE;
+ fa->attrs |= MBOX_FETCH_RFC822_SIZE;
} else if (strcasecmp(tok, "MODSEQ") == 0) {
- /* RFC 7162 SS3.1.4.2 */
- attrs |= MBOX_FETCH_MODSEQ;
+ fa->attrs |= MBOX_FETCH_MODSEQ;
} else if ((sec = body_section(tok, &plain)) != NULL) {
d = 0;
if (strcasecmp(sec, "HEADER]") == 0)
- attrs |= MBOX_FETCH_BODY_HEADER;
+ fa->attrs |= MBOX_FETCH_BODY_HEADER;
else if (strncasecmp(sec, "HEADER.FIELDS",
strlen("HEADER.FIELDS")) == 0) {
- if (parse_body_peek_header_fields_tok(sec,
- &attrs, header_fields_not_out,
- header_fields_out, header_fields_outsize,
- header_fields_label_out,
- header_fields_label_outsize,
+ if (parse_body_peek_header_fields_tok(sec, fa,
errmsg) == -1)
return (-1);
- } else if (parse_body_peek_section_tok(sec, &attrs,
- section_part_out, section_part_outsize,
- &has_partial, &partial_start, &partial_count,
- &d, errmsg) == -1)
+ } else if (parse_body_peek_section_tok(sec, fa, &d,
+ errmsg) == -1)
return (-1);
if (plain && !d)
- attrs |= MBOX_FETCH_SET_SEEN;
- degraded |= d;
+ fa->attrs |= MBOX_FETCH_SET_SEEN;
+ fa->degraded |= d;
} else if (strcasecmp(tok, "ENVELOPE") == 0) {
- /* SS7.5.2; no .PEEK, no \Seen effect */
- attrs |= MBOX_FETCH_ENVELOPE;
+ /* section 7.5.2; no .PEEK, no \Seen effect */
+ fa->attrs |= MBOX_FETCH_ENVELOPE;
} else if (strcasecmp(tok, "BODY") == 0 ||
strcasecmp(tok, "BODYSTRUCTURE") == 0) {
- attrs |= MBOX_FETCH_BODYSTRUCTURE;
- *bodystructure_full_out =
+ fa->attrs |= MBOX_FETCH_BODYSTRUCTURE;
+ fa->bodystructure_full =
(strcasecmp(tok, "BODYSTRUCTURE") == 0);
} else if (strncasecmp(tok, "BODY", 4) == 0 ||
strcasecmp(tok, "RFC822") == 0 ||
strcasecmp(tok, "RFC822.HEADER") == 0 ||
strcasecmp(tok, "RFC822.TEXT") == 0) {
/* other BODY forms and RFC822 items: unimplemented */
- degraded = 1;
+ fa->degraded = 1;
} else {
*errmsg = "unknown message data item";
return (-1);
}
}
- if (attrs == 0) {
+ if (fa->attrs == 0) {
*errmsg = "cannot fetch that message content yet, "
"supported: FLAGS/UID/INTERNALDATE/RFC822.SIZE/MODSEQ/"
"ENVELOPE/(BODY|BODYSTRUCTURE)/BODY[...]/BODY.PEEK[...]";
return (-2);
}
- /* RFC 9051 SS6.4.5: HEADER wins over HEADER.FIELDS */
- if ((attrs & MBOX_FETCH_BODY_HEADER) &&
- (attrs & MBOX_FETCH_HEADER_FIELDS))
- attrs &= ~MBOX_FETCH_HEADER_FIELDS;
+ /* RFC 9051 section 6.4.5: HEADER wins over HEADER.FIELDS */
+ if ((fa->attrs & MBOX_FETCH_BODY_HEADER) &&
+ (fa->attrs & MBOX_FETCH_HEADER_FIELDS))
+ fa->attrs &= ~MBOX_FETCH_HEADER_FIELDS;
- *attrs_out = attrs;
- *degraded_out = degraded;
- *has_partial_out = has_partial;
- *partial_start_out = partial_start;
- *partial_count_out = partial_count;
return (0);
}
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
-/* RFC 9051 SS9 date-time, always "+0000": no tzdata in the chroot */
+/* RFC 9051 section 9 date-time, always "+0000": no tzdata in the chroot */
void
format_internaldate(int64_t ts, char *out, size_t outsize)
{
time_t t = (time_t)ts;
if (gmtime_r(&t, &tm) == NULL) {
- if (strlcpy(out, "01-Jan-1970 00:00:00 +0000", outsize) >=
- outsize)
- log_warnx("format_internaldate: fallback string "
- "truncated, caller's buffer too small");
+ strlcpy(out, "01-Jan-1970 00:00:00 +0000", outsize);
return;
}
}
}
-/* RFC 9051 SS7.5.2 */
void
session_send_fetch_response(struct session *s,
struct imsg_mbox_fetch_meta *meta)
fetch_append(buf, sizeof(buf), &len, "%u FETCH (", meta->seqno);
- /* RFC 9051 SS6.4.5, RFC 7162 SS3.2.4: report a \Seen this set */
+ /* RFC 9051 section 6.4.5, RFC 7162 section 3.2.4: FLAGS if \Seen set */
if ((s->fetch_attrs & MBOX_FETCH_FLAGS) || meta->seen_set) {
fetch_append(buf, sizeof(buf), &len, "FLAGS (%s)", meta->flags);
need_sp = 1;
need_sp = 1;
}
if ((s->fetch_attrs & MBOX_FETCH_MODSEQ) || cs) {
- /* RFC 7162 SS3.1.4.2 fetch-mod-resp */
+ /* RFC 7162 section 3.1.4.2 fetch-mod-resp */
fetch_append(buf, sizeof(buf), &len, "%sMODSEQ (%llu)",
need_sp ? " " : "", (unsigned long long)meta->modseq);
need_sp = 1;
}
}
-/* RFC 9051 SS6.4.6 STORE echo */
+/* RFC 9051 section 6.4.6 STORE echo */
void
session_send_store_fetch_response(struct session *s,
const struct imsg_mbox_fetch_meta *meta)
char buf[MBOX_FLAGS_MAX + 96];
size_t len;
- /* RFC 9051 SS6.4.9: UID STORE echoes UID after FLAGS */
+ /* RFC 9051 section 6.4.9: UID STORE echoes UID after FLAGS */
len = (size_t)snprintf(buf, sizeof(buf), "%u FETCH (FLAGS (%s)",
meta->seqno, meta->flags);
if (s->cmd_by_uid && len < sizeof(buf))
session_untagged(s, buf);
}
-/* RFC 4466, RFC 7162 SS3.1.4.1 and SS3.2.6 modifiers */
+/* RFC 4466, RFC 7162 section 3.1.4.1 and section 3.2.6 modifiers */
int
parse_fetch_modifiers(char *modspec, struct imsg_mbox_fetch *req,
const struct session *s, int by_uid, int *want_vanished,
for (tok = strtok_r(p, " ", &save); tok != NULL;
tok = strtok_r(NULL, " ", &save)) {
if (strcasecmp(tok, "CHANGEDSINCE") == 0) {
- const char *valtok = strtok_r(NULL, " ", &save);
- char *ep;
+ const char *valtok, *errstr;
+ valtok = strtok_r(NULL, " ", &save);
if (valtok == NULL) {
*errmsg = "CHANGEDSINCE requires a "
"mod-sequence value";
return (-1);
}
- /* strtoull(3) accepts a sign */
if (*valtok < '0' || *valtok > '9') {
*errmsg = "invalid CHANGEDSINCE mod-sequence";
return (-1);
}
- errno = 0;
- req->changedsince = strtoull(valtok, &ep, 10);
- if (*ep != '\0' || errno != 0 ||
- req->changedsince == 0 ||
- req->changedsince > MODSEQ_MAX) {
+ req->changedsince = strtonum(valtok, 1, MODSEQ_MAX,
+ &errstr);
+ if (errstr != NULL) {
*errmsg = "invalid CHANGEDSINCE mod-sequence";
return (-1);
}
req->attrs |= MBOX_FETCH_MODSEQ;
} else if (strcasecmp(tok, "VANISHED") == 0) {
if (!by_uid) {
- /* RFC 7162 SS3.2.6: BAD */
+ /* RFC 7162 section 3.2.6: BAD */
*errmsg = "VANISHED is only valid as a UID "
- "FETCH modifier (RFC 7162 SS3.2.6)";
+ "FETCH modifier";
return (-1);
}
if (!s->qresync_enabled) {
*errmsg = "VANISHED requires ENABLE QRESYNC "
- "first (RFC 7162 SS3.2.6)";
+ "first";
return (-1);
}
*want_vanished = 1;
return (0);
}
-/* RFC 9051 SS4.3: FETCH's literals, header-fld-names, as quoted strings */
+/* RFC 9051 section 4.3: FETCH's literals (header-fld-names) as quoted */
static int
fetch_unliteral(char *spec, char *out, size_t outsize, const char **errmsg)
{
int
cmd_fetch(struct session *s, const char *tag, char *args)
{
- return fetch_dispatch(s, tag, args, 0);
+ return (fetch_dispatch(s, tag, args, 0));
}
-/* RFC 9051 SS6.4.9: UID FETCH forces UID */
+/* RFC 9051 section 6.4.9: UID FETCH forces UID */
int
fetch_dispatch(struct session *s, const char *tag, char *args, int by_uid)
{
struct imsg_mbox_fetch req;
- const char *seqtok;
- char *attspec, *modspec;
+ struct fetch_atts fa;
struct seq_range ranges[SEQSET_MAX_RANGES];
- uint32_t nranges, attrs;
- int rc, want_vanished = 0, degraded;
- int header_fields_not = 0;
- char header_fields[HEADER_FIELDS_MAX];
- char header_fields_label[HEADER_FIELDS_LABEL_MAX];
- char unlit[SESSION_INBUF_MAX];
- int bodystructure_full = 0;
- char section_part[SECTION_PART_MAX];
- int has_partial = 0;
- uint32_t partial_start = 0, partial_count = 0;
- const char *errmsg;
+ const char *seqtok, *errmsg;
const char *cmdname = by_uid ? "UID FETCH" : "FETCH";
+ char *attspec, *modspec;
+ char unlit[SESSION_INBUF_MAX];
+ uint32_t nranges;
+ int rc, want_vanished = 0;
if (args == NULL) {
session_reply(s, tag, "BAD",
if (*modspec == '\0')
modspec = NULL;
- rc = parse_fetch_atts(attspec, &attrs, °raded, &header_fields_not,
- header_fields, sizeof(header_fields), header_fields_label,
- sizeof(header_fields_label), &bodystructure_full, section_part,
- sizeof(section_part), &has_partial, &partial_start,
- &partial_count, &errmsg);
+ rc = parse_fetch_atts(attspec, &fa, &errmsg);
if (rc == -1) {
session_reply(s, tag, "BAD", errmsg);
return (1);
session_reply(s, tag, "NO", errmsg);
return (1);
}
- /* RFC 9051 SS6.3.3: EXAMINE permits no change, \Seen included */
+ /* RFC 9051 section 6.3.3: EXAMINE permits no change, \Seen included */
if (s->mbox_readonly)
- attrs &= ~MBOX_FETCH_SET_SEEN;
- if (degraded)
- log_debug("session %u: %s: one or more unsupported message "
- "data items not returned (a part number before "
- "HEADER/TEXT/MIME, RFC822[.HEADER/.TEXT]), "
- "answering the rest, then NO", s->id, cmdname);
+ fa.attrs &= ~MBOX_FETCH_SET_SEEN;
+ if (fa.degraded)
+ log_debug("session %u: %s: unsupported item skipped", s->id,
+ cmdname);
memset(&req, 0, sizeof(req));
- req.attrs = attrs;
+ req.attrs = fa.attrs;
req.by_uid = by_uid;
- req.header_fields_not = header_fields_not;
+ req.header_fields_not = fa.header_fields_not;
+ req.has_partial = fa.has_partial;
+ req.partial_start = fa.partial_start;
+ req.partial_count = fa.partial_count;
+ strlcpy(req.header_fields, fa.header_fields, sizeof(req.header_fields));
+ strlcpy(req.section_part, fa.section_part, sizeof(req.section_part));
- if (strlcpy(req.header_fields, header_fields,
- sizeof(req.header_fields)) >= sizeof(req.header_fields) ||
- strlcpy(req.section_part, section_part, sizeof(req.section_part))
- >= sizeof(req.section_part)) {
- session_reply(s, tag, "NO", "[SERVERBUG] internal error");
- return (1);
- }
- req.has_partial = has_partial;
- req.partial_start = partial_start;
- req.partial_count = partial_count;
+ if (fa.attrs & MBOX_FETCH_BODY_HEADER)
+ strlcpy(s->pending_header_label, "HEADER",
+ sizeof(s->pending_header_label));
+ else if (fa.attrs & MBOX_FETCH_HEADER_FIELDS)
+ strlcpy(s->pending_header_label, fa.header_fields_label,
+ sizeof(s->pending_header_label));
- if (attrs & MBOX_FETCH_BODY_HEADER) {
- if (strlcpy(s->pending_header_label, "HEADER",
- sizeof(s->pending_header_label)) >=
- sizeof(s->pending_header_label)) {
- session_reply(s, tag, "NO",
- "[SERVERBUG] internal error");
- return (1);
- }
- } else if (attrs & MBOX_FETCH_HEADER_FIELDS) {
- if (strlcpy(s->pending_header_label, header_fields_label,
- sizeof(s->pending_header_label)) >=
- sizeof(s->pending_header_label)) {
- session_reply(s, tag, "NO",
- "[SERVERBUG] internal error");
- return (1);
- }
- }
-
- if (attrs & MBOX_FETCH_BODY_WHOLE) {
+ if (fa.attrs & MBOX_FETCH_BODY_WHOLE)
s->pending_body_label[0] = '\0';
- } else if (attrs & MBOX_FETCH_BODY_TEXT) {
- if (strlcpy(s->pending_body_label, "TEXT",
- sizeof(s->pending_body_label)) >=
- sizeof(s->pending_body_label)) {
- session_reply(s, tag, "NO",
- "[SERVERBUG] internal error");
- return (1);
- }
- } else if (attrs & MBOX_FETCH_BODY_PART) {
- if (strlcpy(s->pending_body_label, section_part,
- sizeof(s->pending_body_label)) >=
- sizeof(s->pending_body_label)) {
- session_reply(s, tag, "NO",
- "[SERVERBUG] internal error");
- return (1);
- }
- }
- s->pending_body_has_partial = has_partial;
- s->pending_body_partial_origin = partial_start;
+ else if (fa.attrs & MBOX_FETCH_BODY_TEXT)
+ strlcpy(s->pending_body_label, "TEXT",
+ sizeof(s->pending_body_label));
+ else if (fa.attrs & MBOX_FETCH_BODY_PART)
+ strlcpy(s->pending_body_label, fa.section_part,
+ sizeof(s->pending_body_label));
+ s->pending_body_has_partial = fa.has_partial;
+ s->pending_body_partial_origin = fa.partial_start;
- if (attrs & MBOX_FETCH_BODYSTRUCTURE) {
- if (strlcpy(s->pending_bodystructure_label,
- bodystructure_full ? "BODYSTRUCTURE" : "BODY",
- sizeof(s->pending_bodystructure_label)) >=
- sizeof(s->pending_bodystructure_label)) {
- session_reply(s, tag, "NO",
- "[SERVERBUG] internal error");
- return (1);
- }
- }
+ if (fa.attrs & MBOX_FETCH_BODYSTRUCTURE)
+ strlcpy(s->pending_bodystructure_label,
+ fa.bodystructure_full ? "BODYSTRUCTURE" : "BODY",
+ sizeof(s->pending_bodystructure_label));
if (modspec != NULL) {
if (parse_fetch_modifiers(modspec, &req, s, by_uid,
}
if (want_vanished && !req.has_changedsince) {
- /* RFC 7162 SS3.2.6: VANISHED needs CHANGEDSINCE */
+ /* RFC 7162 section 3.2.6: VANISHED needs CHANGEDSINCE */
session_reply(s, tag, "BAD",
- "VANISHED requires CHANGEDSINCE also be specified "
- "(RFC 7162 SS3.2.6)");
+ "VANISHED requires CHANGEDSINCE");
return (1);
}
req.want_vanished = want_vanished;
req.nranges = nranges;
- /* RFC 7162 SS3.1: both enable CONDSTORE */
+ /* RFC 7162 section 3.1: both enable CONDSTORE */
if (req.attrs & MBOX_FETCH_MODSEQ)
session_condstore_enable(s);
}
s->fetch_attrs = req.attrs;
s->cmd_by_uid = by_uid;
- /* RFC 9051 SS6.4.5: "NO - fetch error: can't fetch that data" */
- s->fetch_incomplete = degraded;
+ /* RFC 9051 section 6.4.5: "NO - fetch error: can't fetch that data" */
+ s->fetch_incomplete = fa.degraded;
s->state = SESSION_FETCHING;
- if (!send_mbox_request(s, IMSG_MBOX_FETCH, cmdname, "IMSG_MBOX_FETCH",
- &req, sizeof(req), ranges, nranges, sizeof(struct seq_range))) {
+ if (send_mbox_request(s, IMSG_MBOX_FETCH, "IMSG_MBOX_FETCH", &req,
+ sizeof(req), ranges, nranges, sizeof(struct seq_range)) == -1) {
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
s->state = SESSION_SELECTED;
return (1);
blob - 4e44803fb3a69f2c0741294274aac08ab9941e6a
blob + 778057317fadc0ccefd39083d8134d5be49d653c
--- src/imapd.8
+++ src/imapd.8
.\" Written for the OpenIMAPD project. Public domain / no rights reserved,
.\" matching the project's ports-oriented, OpenBSD-base-inclusion goal.
.\"
-.Dd $Mdocdate: October 3 2026 $
+.Dd $Mdocdate: October 5 2026 $
.Dt IMAPD 8
.Os
.Sh NAME
.Nd Internet Message Access Protocol (IMAP) daemon
.Sh SYNOPSIS
.Nm
-.Op Fl dVv
+.Op Fl dnVv
.Op Fl D Ar macro Ns = Ns Ar value
.Op Fl f Ar file
.Sh DESCRIPTION
and rewrites each new message with the CRLF line endings RFC 5322
requires before any client sees it.
.Pp
+Message files that other software places in a mailbox's
+.Pa cur
+or
+.Pa new
+directory, such as a maildir brought from another server, are given
+UIDs the next time the mailbox is selected or its status is read,
+keeping the flags in their names.
+A mailbox whose
+.Pa imapd.index
+is lost is rebuilt the same way, with a new
+.Li UIDVALIDITY .
+A file found in
+.Pa cur
+with the deleted flag
+.Pq Sq T
+is left out unless the mailbox has no index, since it may be a
+message that was expunged but could not be removed.
+.Pp
By default,
.Nm
listens on port 143, where clients may upgrade to TLS with
.Ar file
as the configuration file, instead of the default
.Pa /etc/imapd.conf .
+.It Fl n
+Configtest mode.
+Only check the configuration file for validity.
.It Fl V
Print the version and exit.
.It Fl v
Produce more verbose logging.
+Multiple
+.Fl v
+options increase the verbosity.
.El
.Sh FILES
.Bl -tag -width "/etc/ssl/private/imapd.keyXX" -compact
Highest
.Li UIDVALIDITY
issued, at the root of each maildir.
+If it cannot be read or written, a value already issued may be issued
+again.
.El
.Sh SEE ALSO
.Xr imapd.conf 5 ,
polls the selected mailbox, so new mail is reported within one
.Ic idle poll
interval.
+Files placed directly in
+.Pa cur
+are not noticed during
+.Li IDLE
+until something else changes the mailbox.
.Pp
One process serves all sessions of an account, so a
.Li STORE ,
blob - a4b13bb83eaccee953142bf9ebf1432fe2958254
blob + 3ddc28bc90525e93fd6ccfd5d0d24f281d248de7
--- src/imapd.conf.5
+++ src/imapd.conf.5
.\" Written for the OpenIMAPD project. Public domain / no rights reserved,
.\" matching the project's ports-oriented, OpenBSD-base-inclusion goal.
.\"
-.Dd $Mdocdate: October 4 2026 $
+.Dd $Mdocdate: October 6 2026 $
.Dt IMAPD.CONF 5
.Os
.Sh NAME
.Li NO
with the
.Li LIMIT
-response code and the connection is closed.
+response code, after an untagged
+.Li BYE
+with the same code, and the connection is closed.
A client that vanishes without closing its connection holds its
session until TCP keepalive notices, which by default is 2 hours 10
minutes; see
.Ic listen on
is given,
.Xr imapd 8
-listens on 0.0.0.0 port 143 and 0.0.0.0 tls port 993.
+listens on
+.Sq *
+port 143 and
+.Sq *
+tls port 993.
If any is given, only the listeners named are bound.
.It Ic lock timeout Ar seconds
How long a command waits for a mailbox lock held by another process
.Pa ..
component.
A line of 1023 or more characters causes every login to be refused.
+A login is also refused if the account's mailbox process is not running
+in the
+.Ar maildir
+within 10 seconds, as when the directory does not exist or its
+filesystem is not mounted.
+The client is sent an untagged
+.Li BYE
+and then
+.Li NO ,
+both with the
+.Li UNAVAILABLE
+response code, and the cause is logged.
.Xr imapduser 8
adds and removes entries.
.Pp
blob - ba320d62cfd97a54d1625488669effc1319157c3
blob + 5e7347935a2ef25064d6c7a8e50def90a4decbe3
--- src/imapd.conf.example
+++ src/imapd.conf.example
# && doas chmod 600 /etc/imapd.conf)
#
-# Listen on all IPv4 interfaces: cleartext/STARTTLS on port 143, implicit
-# TLS (RFC 8314) on port 993. Both listeners must share the same address.
-# These are also the defaults if "listen on" is omitted entirely.
-listen on 0.0.0.0 port 143
-listen on 0.0.0.0 tls port 993
+# Listen on all IPv4 and IPv6 interfaces: cleartext/STARTTLS on port 143,
+# implicit TLS (RFC 8314) on port 993. Both listeners must share the same
+# address. These are also the defaults if "listen on" is omitted entirely.
+listen on * port 143
+listen on * tls port 993
-# The address may also be "::" (all IPv6 interfaces) or "*" (both IPv4 and
-# IPv6, binds two sockets per listener, one of each family, matching
-# httpd.conf(5)'s own "*" convention), or a single literal IPv4/IPv6
-# address. Hostnames are not accepted. For example, to listen on both
-# address families:
-#listen on * port 143
-#listen on * tls port 993
+# The address may also be "0.0.0.0" (all IPv4 interfaces), "::" (all IPv6
+# interfaces), or a single literal IPv4/IPv6 address. Hostnames are not
+# accepted. "*" binds two sockets per listener, one of each family, as
+# httpd.conf(5)'s "*" does. For example, IPv4 only:
+#listen on 0.0.0.0 port 143
+#listen on 0.0.0.0 tls port 993
# Mail spool root, chrooted into by the per-session store child.
# Defaults to /var/mail/imapd.
blob - ad6ba2b57e341a039b4ee83e8985f20671d4446f
blob + 585519020ecf17b7299167f9eaaf557d89299509
--- src/imapd.h
+++ src/imapd.h
#include <imsg.h>
#include <stdint.h>
-#define IMAPD_VERSION "0.2.2"
+#define IMAPD_VERSION "0.2.3"
#define IMAPD_USER "_imapd"
#define IMAPD_AUTH_USER "_imapauth"
IMSG_MBOX_SELECT_VANISHED,
IMSG_MBOX_STORE_MODIFIED,
- /* RFC 9051 SS6.3.13 (IDLE); the VIEW ones go with any command */
+ /* RFC 9051 section 6.3.13 (IDLE); the VIEW ones go with any command */
IMSG_MBOX_IDLE_REFRESH,
IMSG_MBOX_VIEW_EXPUNGE,
IMSG_MBOX_VIEW_FETCH,
IMSG_MBOX_VIEW_EXISTS,
IMSG_MBOX_IDLE_REFRESHED,
- /* RFC 9051 SS6.3.9 (LIST) */
+ /* RFC 9051 section 6.3.9 (LIST) */
IMSG_MBOX_LIST_ITEM,
- /* RFC 9051 SS6.3.7/SS6.3.8 (SUBSCRIBE/UNSUBSCRIBE) */
+ /* RFC 9051 section 6.3.7/section 6.3.8 (SUBSCRIBE/UNSUBSCRIBE) */
IMSG_MBOX_SUBSCRIBE,
IMSG_MBOX_UNSUBSCRIBE,
IMSG_MBOX_SAVED_CLEAR
#define MBOX_NAME_MAX 256
-/* RFC 5530 SS3 response codes, and RFC 9051 NOTSAVED */
+/* RFC 5530 section 3 response codes, and RFC 9051 NOTSAVED */
enum mbox_op_error {
MBOX_ERR_UNSET = 0,
MBOX_OP_OK,
MBOX_OP_ERR_OVERQUOTA,
};
-/* QRESYNC select-param (RFC 7162 SS3.2.5). */
+/* QRESYNC select-param (RFC 7162 section 3.2.5). */
struct imsg_mbox_select {
char mailbox[MBOX_NAME_MAX];
int readonly; /* 1 = EXAMINE, 0 = SELECT */
uint32_t uidnext;
uint64_t highestmodseq;
- /* preceded by VANISHED, then FETCH_META (RFC 7162 SS3.2.6) */
+ /* preceded by VANISHED, then FETCH_META (RFC 7162 section 3.2.6) */
};
-/* RFC 9051 SS6.3.11, plus RFC 7162 SS3.1.7 HIGHESTMODSEQ */
+/* RFC 9051 section 6.3.11, plus RFC 7162 section 3.1.7 HIGHESTMODSEQ */
#define STATUS_ATT_MESSAGES (1U << 0)
#define STATUS_ATT_UIDNEXT (1U << 1)
#define STATUS_ATT_UIDVALIDITY (1U << 2)
#define STATUS_ATT_HIGHESTMODSEQ (1U << 6)
#define STATUS_ATT_RECENT (1U << 7) /* answered "0" */
-/* RFC 9051 SS6.3.11 STATUS; the selection is left alone */
+/* RFC 9051 section 6.3.11 STATUS; the selection is left alone */
struct imsg_mbox_status {
char mailbox[MBOX_NAME_MAX];
uint32_t attrs;
uint64_t size;
};
-/* one gap [uid_lo, uid_hi]; RFC 7162 SS5.1 minimal state */
+/* one gap [uid_lo, uid_hi]; RFC 7162 section 5.1 minimal state */
struct imsg_mbox_select_vanished {
uint32_t uid_lo;
uint32_t uid_hi;
#define MBOX_FETCH_UID (1U << 1)
#define MBOX_FETCH_INTERNALDATE (1U << 2)
#define MBOX_FETCH_RFC822_SIZE (1U << 3)
-#define MBOX_FETCH_MODSEQ (1U << 4) /* RFC 7162 SS3.1.4.2 */
+#define MBOX_FETCH_MODSEQ (1U << 4) /* RFC 7162 section 3.1.4.2 */
#define MBOX_FETCH_BODY_HEADER (1U << 5)
#define MBOX_FETCH_BODY_WHOLE (1U << 6)
#define MBOX_FETCH_BODY_TEXT (1U << 7)
#define IDLE_POLL_DEFAULT 5
#define IDLE_POLL_MAX 300 /* seconds; 0 disables polling */
-/* RFC 9051 SS5.4 permits a short pre-authentication timer */
+/* RFC 9051 section 5.4 permits a short pre-authentication timer */
#define LOGIN_GRACE_DEFAULT 60
#define LOGIN_GRACE_MAX 3600 /* seconds; 0 disables the timer */
-/* lock wait, then NO [INUSE] (RFC 9051 SS7.1) */
+/* lock wait, then NO [INUSE] (RFC 9051 section 7.1) */
#define LOCK_TIMEOUT_DEFAULT 120
#define LOCK_TIMEOUT_MAX 3600 /* seconds; 0 disables the bound */
uint32_t poollen;
};
-/* RFC 9051 SS6.4.4 content key; strings are offsets into the pool */
+/* RFC 9051 section 6.4.4 content key; strings are offsets into the pool */
struct imsg_parser_leaf {
int32_t op;
uint32_t str_off;
uint32_t retiring;
};
-/* RFC 9051 SS9 sequence-set range; the store resolves "*" */
+/* RFC 9051 section 9 sequence-set range; the store resolves "*" */
struct seq_range {
uint32_t lo;
uint32_t hi;
int hi_is_star;
};
-/* RFC 9051 SS9 seq-last-command, "$": the all-zero range */
+/* RFC 9051 section 9 seq-last-command, "$": the all-zero range */
#define SEQ_RANGE_SAVED(r) ((r)->lo == 0 && !(r)->lo_is_star)
/* keeps the trailing array under MAX_IMSGSIZE */
uint32_t nranges;
uint32_t attrs;
- /* RFC 7162 SS3.1.4.1 CHANGEDSINCE */
+ /* RFC 7162 section 3.1.4.1 CHANGEDSINCE */
int has_changedsince;
uint64_t changedsince;
- /* RFC 9051 SS6.4.9 UID FETCH; RFC 7162 SS3.2.6 VANISHED */
+ /* RFC 9051 section 6.4.9 UID FETCH; RFC 7162 section 3.2.6 VANISHED */
int by_uid;
int want_vanished;
uint32_t count;
uint64_t highestmodseq;
- /* RFC 9051 SS7.1 COPYUID; COPY and MOVE only */
+ /* RFC 9051 section 7.1 COPYUID; COPY and MOVE only */
uint32_t uidvalidity;
};
uint32_t sysflags;
char keywords[MBOX_FLAGS_MAX];
- /* RFC 7162 SS3.1.3 UNCHANGEDSINCE */
+ /* RFC 7162 section 3.1.3 UNCHANGEDSINCE */
int has_unchangedsince;
uint64_t unchangedsince;
int by_uid;
};
-/* RFC 7162 SS3.1.3 MODIFIED */
+/* RFC 7162 section 3.1.3 MODIFIED */
struct imsg_mbox_store_modified {
uint32_t seqno;
uint32_t uid;
};
-/* RFC 9051 SS6.4.3; lowest-numbered first (SS7.5.1) */
+/* RFC 9051 section 6.4.3; lowest-numbered first (section 7.5.1) */
struct imsg_mbox_expunge {
int silent; /* 1 for CLOSE, 0 for a real EXPUNGE command */
};
struct imsg_mbox_expunged {
- uint32_t seqno; /* after earlier EXPUNGEs (RFC 9051 SS7.5.1) */
- uint32_t uid; /* for VANISHED (RFC 7162 SS3.2.10.2) */
+ uint32_t seqno; /* RFC 9051 section 7.5.1: after EXPUNGEs */
+ uint32_t uid; /* for VANISHED (RFC 7162 section 3.2.10.2) */
};
-/* RFC 9051 SS6.4.7 COPY and SS6.4.8 MOVE */
+/* RFC 9051 section 6.4.7 COPY and section 6.4.8 MOVE */
struct imsg_mbox_copy {
int by_uid;
uint32_t nranges;
char destname[MBOX_NAME_MAX];
};
-/* COPYUID pairs, sent before EXPUNGE for MOVE (RFC 9051 SS6.4.8) */
+/* COPYUID pairs, sent before EXPUNGE for MOVE (RFC 9051 section 6.4.8) */
struct imsg_mbox_copy_mapping {
uint32_t src_uid;
uint32_t dest_uid;
uint32_t uid; /* APPENDUID, with uidvalidity */
};
-/* RFC 9051 SS6.4.4 search-program, as a postfix array of nodes */
+/* RFC 9051 section 6.4.4 search-program, as a postfix array of nodes */
#define SEARCH_PROGRAM_MAX_NODES 100
#define SEARCH_KEYWORD_MAX 64 /* one keyword, not a list */
-/* operand pool; RFC 9051 SS4.3's non-synchronizing literal cap */
+/* operand pool; RFC 9051 section 4.3's non-synchronizing literal cap */
#define SEARCH_OPERANDS_MAX 4096
#define SEARCH_OP_ALL 0
#define SEARCH_OP_AND 20
#define SEARCH_OP_OR 21
#define SEARCH_OP_NOT 22
-#define SEARCH_OP_MODSEQ 23 /* RFC 7162 SS3.1.5 */
-/* RFC 9051 SS6.4.4 content keys, answered by the parser-worker */
+#define SEARCH_OP_MODSEQ 23 /* RFC 7162 section 3.1.5 */
+/* RFC 9051 section 6.4.4 content keys, answered by the parser-worker */
#define SEARCH_OP_SUBJECT 24
#define SEARCH_OP_HEADER 25
#define SEARCH_OP_SENTBEFORE 26
uint32_t name_len;
};
-/* RFC 9051 SS6.4.4 search-return-opt */
+/* RFC 9051 section 6.4.4 search-return-opt */
#define SEARCH_RETURN_MIN (1U << 0)
#define SEARCH_RETURN_MAX (1U << 1)
#define SEARCH_RETURN_ALL (1U << 2)
struct imsg_mbox_search_match {
uint32_t seqno;
uint32_t uid;
- uint64_t modseq; /* RFC 7162 SS3.1.6 */
+ uint64_t modseq; /* RFC 7162 section 3.1.6 */
};
-/* RFC 9051 SS6.3.13 IDLE, SS6.1.2 NOOP; EXPUNGE is imsg_mbox_expunged */
+/* RFC 9051 sections 6.3.13 IDLE, 6.1.2 NOOP; EXPUNGE is imsg_mbox_expunged */
struct imsg_mbox_idle_refreshed {
int ok;
int unchanged; /* the probe saw no change */
uint32_t exists;
};
-/* RFC 9051 SS6.3.4/SS6.3.5; store.c re-validates the name */
+/* RFC 9051 section 6.3.4/section 6.3.5; store.c re-validates the name */
struct imsg_mbox_create {
char mailbox[MBOX_NAME_MAX];
};
char mailbox[MBOX_NAME_MAX];
};
-/* RFC 9051 SS6.3.6 RENAME */
+/* RFC 9051 section 6.3.6 RENAME */
struct imsg_mbox_rename {
char oldname[MBOX_NAME_MAX];
char newname[MBOX_NAME_MAX];
};
-/* RFC 9051 SS6.3.9 LIST; unordered, INBOX excluded */
+/* RFC 9051 section 6.3.9 LIST; unordered, INBOX excluded */
struct imsg_mbox_list {
int subscribed_only;
};
-/* exists 0: subscribed, no mailbox (RFC 9051 SS6.3.9.6) */
+/* exists 0: subscribed, no mailbox (RFC 9051 section 6.3.9.6) */
struct imsg_mbox_list_item {
char mailbox[MBOX_NAME_MAX];
int exists;
};
-/* RFC 9051 SS6.3.7 SUBSCRIBE and SS6.3.8 UNSUBSCRIBE */
+/* RFC 9051 section 6.3.7 SUBSCRIBE and section 6.3.8 UNSUBSCRIBE */
struct imsg_mbox_subscribe {
char mailbox[MBOX_NAME_MAX];
};
/* a dispatch handler must call this before returning */
void imsgev_rearm_read(struct imsgev *);
+int recv_fixed(struct imsg *, void *, size_t, const char *);
+int recv_header(struct imsg *, void *, size_t, const char *);
int setup_recv_one_peer(struct imsgbuf *);
int setup_recv_one_peer_id(struct imsgbuf *, uint32_t *);
blob - 4abc10cf7e54c09814b8651cf7a107c6d0ec0c43
blob + 2ef6ced46ea7c66ecc9add39ef9bc3cf9c0faff9
--- src/imsgev.c
+++ src/imsgev.c
* Copyright (c) 2004 Esben Norby <norby@openbsd.org>
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
*
- * This file's name and its "struct imsgev" wrapper-around-imsgbuf+
- * event(3) concept match Eric Faurot's imsgev.c in OpenBSD's ldapd
- * (usr.sbin/ldapd/imsgev.c), not a generic/obvious name, so his
- * copyright is carried forward here even though this file's actual
- * function signatures and dispatch design (a caller-supplied raw
- * libevent handler, vs. ldapd's callback+needfd model) were written
- * independently and differ from his implementation.
- *
- * imsgev_add() below is the exception: it is a verbatim copy (up to
- * whitespace and the choice of iev vs. iev->data as event_set()'s last
- * argument) of the imsg_event_add() idiom shared across OpenBSD privsep
- * daemons, traceable to usr.sbin/ospfd/ospfd.c:525-534 (Claudio Jeker
- * 2005, Esben Norby 2004, Henning Brauer 2003-2004) and copied with
- * only that one-argument variation into dvmrpd.c, npppd.c, and rad.c.
- * Their copyright is carried forward for that function specifically,
- * same rationale as log.c's and parse.y's shared-idiom copyright chains.
- *
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
{
struct imsgev *iev = arg;
- (void)ibuf;
-
if (event_pending(&iev->ev, EV_WRITE, NULL))
return;
imsgev_add(iev);
}
/* EV_READ lacks EV_PERSIST: every handler must re-arm it */
+/* a fixed-size message, or 0 after a log line */
+int
+recv_fixed(struct imsg *imsg, void *data, size_t len, const char *what)
+{
+ if (imsg_get_data(imsg, data, len) == -1) {
+ log_warnx("bad %s", what);
+ return (0);
+ }
+ return (1);
+}
+
+int
+recv_header(struct imsg *imsg, void *data, size_t len, const char *what)
+{
+ if (imsg_get_buf(imsg, data, len) == -1) {
+ log_warnx("bad %s (header)", what);
+ return (0);
+ }
+ return (1);
+}
+
void
imsgev_rearm_read(struct imsgev *iev)
{
blob - 68126e28fffe04e0f26c21b5e2882ed2b8e61d41
blob + 14c1f133989ed4d5fd815ac332d079572515d5f9
--- src/index.c
+++ src/index.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include "log.h"
#include "store_internal.h"
-/* no ':', CR or LF in any index field */
-/* RFC 7162 SS7 */
+/* RFC 7162 section 7 */
#define INDEX_MODSEQ_MAX INT64_MAX
#define CRLF_BLOCK 65536
index_load(int fd, struct mbox_index *idx)
{
FILE *fp;
- char line[STORE_INDEX_LINE_MAX];
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t linelen;
int dupfd;
int first = 1;
return (-1);
}
- while (fgets(line, sizeof(line), fp) != NULL) {
- /* fgets(3) splits an over-long line */
- if (strchr(line, '\n') == NULL &&
- strlen(line) == sizeof(line) - 1) {
- int c = fgetc(fp);
-
- if (c != EOF && c != '\n') {
- log_warnx("session %u: over-long line in %s, "
- "refusing to parse the index", session_id,
- STORE_INDEX_NAME);
- goto fail;
- }
+ while ((linelen = getline(&line, &linesize, fp)) != -1) {
+ if (linelen >= STORE_INDEX_LINE_MAX) {
+ log_warnx("session %u: %s: line too long, index not "
+ "parsed", session_id, STORE_INDEX_NAME);
+ goto fail;
}
line[strcspn(line, "\n")] = '\0';
if (line[0] == '\0')
continue;
if (first) {
- char *colon, *colon2, *ep;
- unsigned long parsed;
+ char *colon, *colon2;
+ const char *errstr;
first = 0;
if ((colon = strchr(line, ':')) == NULL) {
goto fail;
}
*colon = '\0';
- /* strtoul(3) accepts leading space and a sign */
if (line[0] < '0' || line[0] > '9') {
log_warnx("session %u: malformed "
"UIDVALIDITY: %s", session_id, line);
goto fail;
}
- errno = 0;
- parsed = strtoul(line, &ep, 10);
- if (*ep != '\0' || errno != 0 ||
- parsed > UINT32_MAX) {
+ idx->uidvalidity = strtonum(line, 0, UINT32_MAX,
+ &errstr);
+ if (errstr != NULL) {
log_warnx("session %u: malformed "
"UIDVALIDITY: %s", session_id, line);
goto fail;
}
- idx->uidvalidity = (uint32_t)parsed;
/* RFC 7162: optional HIGHESTMODSEQ; absent means 1 */
if ((colon2 = strchr(colon + 1, ':')) != NULL)
session_id, colon + 1);
goto fail;
}
- errno = 0;
- parsed = strtoul(colon + 1, &ep, 10);
- if (*ep != '\0' || errno != 0 ||
- parsed > UINT32_MAX) {
+ idx->uidnext = strtonum(colon + 1, 0, UINT32_MAX,
+ &errstr);
+ if (errstr != NULL) {
log_warnx("session %u: malformed UIDNEXT: %s",
session_id, colon + 1);
goto fail;
}
- idx->uidnext = (uint32_t)parsed;
if (colon2 != NULL) {
if (colon2[1] < '0' || colon2[1] > '9') {
colon2 + 1);
goto fail;
}
- errno = 0;
- idx->highestmodseq = strtoull(colon2 + 1, &ep,
- 10);
- if (*ep != '\0' || errno != 0 ||
- idx->highestmodseq > INDEX_MODSEQ_MAX) {
+ idx->highestmodseq = strtonum(colon2 + 1, 0,
+ INDEX_MODSEQ_MAX, &errstr);
+ if (errstr != NULL) {
log_warnx("session %u: malformed "
"HIGHESTMODSEQ: %s", session_id,
colon2 + 1);
idx->nlines++;
}
if (ferror(fp)) {
- log_warn("session %u: fgets %s", session_id, STORE_INDEX_NAME);
+ log_warn("session %u: getline %s", session_id,
+ STORE_INDEX_NAME);
goto fail;
}
+ free(line);
fclose(fp);
if (first) {
return (0);
fail:
+ free(line);
index_free(idx);
fclose(fp);
return (-1);
int
index_parse_line(const char *line, struct index_rec *rec)
{
- const char *p, *q, *r;
+ const char *p, *q, *r, *errstr;
char *ep;
unsigned long parsed;
memcpy(rec->basename, p, (size_t)(q - p));
rec->basename[q - p] = '\0';
if (!index_basename_valid(rec->basename)) {
- log_warnx("session %u: refusing index line with unsafe "
- "basename (UID %u)", session_id, rec->uid);
+ log_warnx("session %u: index line uid %u: unsafe basename",
+ session_id, rec->uid);
return (-1);
}
"in index line: %s", session_id, line);
return (-1);
}
- errno = 0;
- rec->modseq = strtoull(r + 1, &ep, 10);
- if (*ep != '\0' || errno != 0 ||
- rec->modseq > INDEX_MODSEQ_MAX) {
+ rec->modseq = strtonum(r + 1, 0, INDEX_MODSEQ_MAX, &errstr);
+ if (errstr != NULL) {
log_warnx("session %u: malformed per-message MODSEQ "
"in index line: %s", session_id, line);
return (-1);
return (max);
}
-/* RFC 9051 SS6.4.9: a UID command's set is in UID space */
+/* RFC 9051 section 6.4.9: a UID command's set is in UID space */
enum seqset_pos
seqset_position(const struct seq_range *resolved, uint32_t nresolved,
uint32_t max_hi, int by_uid, uint32_t uid, uint32_t seqno)
return (SEQSET_MATCH);
}
-/* RFC 7162 SS3.2.6 */
+/* RFC 7162 section 3.2.6 */
void
send_vanished_range(const struct mbox_index *idx, uint32_t lo, uint32_t hi,
struct imsgev *iev)
char line[STORE_INDEX_LINE_MAX];
int len;
- /* RFC 9051 SS9 forbids UID 0; refuse rather than wrap */
+ /* RFC 9051 section 9 forbids UID 0; refuse rather than wrap */
if (uid == 0) {
- log_warnx("session %u: refusing index entry with UID 0 "
- "(uidnext exhausted or index header corrupt)", session_id);
+ log_warnx("session %u: index entry with UID 0, refused",
+ session_id);
return (-1);
}
if (strpbrk(basename, ":\r\n") != NULL) {
- log_warnx("session %u: refusing index entry with unsafe "
- "basename: %s", session_id, basename);
+ log_warnx("session %u: index entry %s: unsafe basename",
+ session_id, basename);
return (-1);
}
return (-1);
}
-
void
index_free(struct mbox_index *idx)
{
memset(idx, 0, sizeof(*idx));
}
-/* RFC 7162 SS3.2.5.1 QRESYNC resync; "$" is the one before SELECT */
+/* RFC 7162 section 3.2.5.1 QRESYNC resync; "$" is the one before SELECT */
void
qresync_send_resync(const struct imsg_mbox_select *req,
const struct seq_range *ranges, uint32_t nranges,
nresolved += ss->nsaved;
}
} else {
- /* RFC 7162 SS3.2.5.1: no known-uids means 1:uidnext-1 */
+ /* RFC 7162 section 3.2.5.1: no known-uids means 1:uidnext-1 */
if (idx->uidnext <= 1)
return;
resolved[0].lo = 1;
}
}
-/* RFC 9051 SS2.3.1.1: it must increase, so time is only a floor */
+/* RFC 9051 section 2.3.1.1: it must increase, so time is only a floor */
uint32_t
uidvalidity_next(void)
{
- const char *path;
+ const char *path, *errstr;
char buf[32];
struct stat st;
time_t now;
- char *ep;
- unsigned long parsed;
uint32_t floor = 0, val;
ssize_t n;
int fd, writeback = 1;
if ((fd = openat(maildir_root_fd, path, O_RDWR | O_CREAT,
0600)) == -1) {
- log_warn("session %u: open %s (UIDVALIDITY floor); falling "
- "back to a bare timestamp", session_id, path);
+ log_warn("session %u: open %s, using a bare timestamp",
+ session_id, path);
return (val != 0 ? val : 1);
}
if (flock(fd, LOCK_EX) == -1) {
- log_warn("session %u: flock %s (UIDVALIDITY floor); falling "
- "back to a bare timestamp", session_id, path);
+ log_warn("session %u: flock %s, using a bare timestamp",
+ session_id, path);
close(fd);
return (val != 0 ? val : 1);
}
} else {
buf[n] = '\0';
buf[strcspn(buf, "\r\n")] = '\0';
- errno = 0;
- parsed = strtoul(buf, &ep, 10);
- if (buf[0] < '0' || buf[0] > '9' || *ep != '\0' ||
- errno != 0 || parsed > UINT32_MAX) {
- log_warnx("session %u: %s holds no usable "
- "UIDVALIDITY floor (%s); leaving it alone "
- "and falling back to a bare timestamp",
- session_id, path, buf);
+ errstr = "not a decimal number";
+ if (buf[0] >= '0' && buf[0] <= '9')
+ floor = strtonum(buf, 0, UINT32_MAX, &errstr);
+ if (errstr != NULL) {
+ log_warnx("session %u: %s: no usable "
+ "UIDVALIDITY floor (%s), using a bare "
+ "timestamp", session_id, path, buf);
writeback = 0;
- } else
- floor = (uint32_t)parsed;
+ floor = 0;
+ }
}
}
lseek(fd, 0, SEEK_SET) == -1 ||
ftruncate(fd, 0) == -1 ||
write(fd, buf, (size_t)n) != n)
- log_warn("session %u: could not record the "
- "UIDVALIDITY floor in %s; the value issued now "
- "may be issued again", session_id, path);
+ log_warn("session %u: %s: UIDVALIDITY floor not "
+ "recorded", session_id, path);
}
- log_debug("session %u: UIDVALIDITY: floor %u in %s -> issued %u%s",
- session_id, floor, path, val,
- writeback ? "" : " (floor NOT updated)");
+ log_debug("session %u: UIDVALIDITY floor %u in %s, issued %u%s",
+ session_id, floor, path, val, writeback ? "" : " (floor kept)");
close(fd); /* releases the flock(2) */
return (val);
}
-/* RFC 5322 SS2.3: CR and LF only as CRLF; mail.maildir(8) writes LF alone */
+/* RFC 5322 section 2.3 wants CRLF only; mail.maildir(8) writes LF alone */
static int
-new_to_crlf(int dfd, const char *name)
+to_crlf(int dfd, const char *dir, const char *name)
{
struct stat st;
char src[PATH_MAX], tmp[PATH_MAX];
int ifd = -1, ofd = -1, made = 0, cr = 0, changed = 0;
int rc = -1;
- if (snprintf(src, sizeof(src), "new/%s", name) >= (int)sizeof(src) ||
+ if (snprintf(src, sizeof(src), "%s/%s", dir, name) >=
+ (int)sizeof(src) ||
snprintf(tmp, sizeof(tmp), "tmp/%s.crlf", name) >=
(int)sizeof(tmp)) {
- log_warnx("session %u: new/%s: name too long for CRLF, "
- "indexed as it is", session_id, name);
+ log_warnx("session %u: %s/%s: name too long for CRLF, "
+ "indexed as it is", session_id, dir, name);
return (-1);
}
ifd = openat(dfd, src, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
return (rc);
}
+static void
+new_to_cur(int dfd, const char *name)
+{
+ struct stat st;
+ char from[PATH_MAX], to[PATH_MAX];
+
+ if (snprintf(from, sizeof(from), "new/%s", name) >=
+ (int)sizeof(from) ||
+ snprintf(to, sizeof(to), "cur/%s", name) >= (int)sizeof(to)) {
+ log_warnx("session %u: new/%s: name too long to move to cur/",
+ session_id, name);
+ return;
+ }
+ if (fstatat(dfd, to, &st, AT_SYMLINK_NOFOLLOW) == 0) {
+ log_warnx("session %u: %s: not moved, %s already exists",
+ session_id, from, to);
+ return;
+ }
+ if (errno != ENOENT || renameat(dfd, from, dfd, to) == -1)
+ log_warn("session %u: moving %s to cur/", session_id, from);
+}
+
static int
index_scan_new(int dfd, struct mbox_index *idx, int mutate)
{
DIR *dp;
struct dirent *de;
+ const char *info;
int added = 0, converted = 0;
- {
- int newfd;
-
- dp = NULL;
- newfd = openat(dfd, "new", O_RDONLY | O_DIRECTORY);
- if (newfd != -1 && (dp = fdopendir(newfd)) == NULL)
- close(newfd);
- }
- if (dp == NULL) {
+ if ((dp = opendirat(dfd, "new")) == NULL) {
if (errno == ENOENT)
/* no new/ yet on a never-used mailbox, not an error */
return (0);
if (de->d_name[0] == '.')
/* ".", "..", and dotfiles */
continue;
+ /* maildir info ":2,": the cur/ scan indexes it there */
+ if (strpbrk(de->d_name, "\r\n") == NULL &&
+ (info = strchr(de->d_name, ':')) != NULL &&
+ strncmp(info, ":2,", 3) == 0) {
+ if (!mutate) {
+ closedir(dp);
+ return (1);
+ }
+ new_to_cur(dfd, de->d_name);
+ continue;
+ }
/* ':' or a newline would corrupt the index line */
if (strpbrk(de->d_name, ":\r\n") != NULL) {
if (mutate)
- log_warnx("session %u: skipping new/ file "
- "with unsafe name (contains ':' or "
- "newline): %s", session_id, de->d_name);
+ log_warnx("session %u: new/%s: unsafe name, "
+ "skipped", session_id, de->d_name);
continue;
}
if (index_has_basename(idx, de->d_name))
closedir(dp);
return (1);
}
- /* RFC 9051 SS2.3.1.1: convert before the UID is given */
- if (new_to_crlf(dfd, de->d_name) == 1)
+ /* RFC 9051 section 2.3.1.1: convert before the UID is given */
+ if (to_crlf(dfd, "new", de->d_name) == 1)
converted = 1;
if (index_append(idx, idx->uidnext, de->d_name) == -1) {
closedir(dp);
return (added);
}
+struct base_set {
+ const char **name;
+ size_t *len;
+ size_t mask;
+};
+
+static size_t
+base_slot(const struct base_set *bs, const char *s, size_t len)
+{
+ uint32_t h = 2166136261U; /* FNV-1a */
+ size_t i, k;
+
+ for (i = 0; i < len; i++)
+ h = (h ^ (unsigned char)s[i]) * 16777619U;
+ for (k = h & bs->mask; bs->name[k] != NULL; k = (k + 1) & bs->mask)
+ if (bs->len[k] == len && memcmp(bs->name[k], s, len) == 0)
+ break;
+ return (k);
+}
+
+static int
+name_cmp(const void *a, const void *b)
+{
+ return (strcmp(*(char * const *)a, *(char * const *)b));
+}
+
+static int
+index_scan_cur(int dfd, struct mbox_index *idx, int mutate)
+{
+ struct base_set bs = { NULL, NULL, 0 };
+ struct stat st;
+ DIR *dp = NULL;
+ struct dirent *de;
+ char **found = NULL, **grown, *info, *name;
+ char base[NAME_MAX + 1], from[PATH_MAX], to[PATH_MAX];
+ const char *p, *prev = NULL;
+ size_t nfound = 0, cap = 0, size = 1, i, k, len;
+ size_t prevlen = 0, added = 0;
+ int synced = 0, rv = -1;
+
+ while (size < 2 * idx->nlines + 1)
+ size <<= 1;
+ bs.mask = size - 1;
+ if ((bs.name = calloc(size, sizeof(*bs.name))) == NULL ||
+ (bs.len = calloc(size, sizeof(*bs.len))) == NULL) {
+ log_warn("session %u: scanning cur", session_id);
+ goto done;
+ }
+ for (i = 0; i < idx->nlines; i++) {
+ if ((p = strchr(idx->lines[i], ':')) == NULL)
+ continue;
+ len = strcspn(++p, ":");
+ k = base_slot(&bs, p, len);
+ bs.name[k] = p;
+ bs.len[k] = len;
+ }
+
+ if ((dp = opendirat(dfd, "cur")) == NULL) {
+ if (errno == ENOENT)
+ rv = 0;
+ else
+ log_warn("session %u: opendir cur", session_id);
+ goto done;
+ }
+ while ((de = readdir(dp)) != NULL) {
+ if (de->d_name[0] == '.' ||
+ strpbrk(de->d_name, "\r\n") != NULL)
+ continue;
+ len = strcspn(de->d_name, ":");
+ if (bs.name[base_slot(&bs, de->d_name, len)] != NULL)
+ continue;
+ info = de->d_name[len] == ':' ? de->d_name + len : NULL;
+ if (len >= sizeof(base) ||
+ (info != NULL && strncmp(info, ":2,", 3) != 0)) {
+ if (mutate)
+ log_warnx("session %u: cur/%s: not indexed, "
+ "not a maildir name", session_id,
+ de->d_name);
+ continue;
+ }
+ /* RFC 9051 section 6.4.3: likely a failed EXPUNGE unlink */
+ if (info != NULL && !idx->fresh &&
+ strchr(info + 3, 'T') != NULL) {
+ if (mutate)
+ log_warnx("session %u: cur/%s: not indexed, "
+ "flagged deleted", session_id, de->d_name);
+ continue;
+ }
+ memcpy(base, de->d_name, len);
+ base[len] = '\0';
+ if (!index_basename_valid(base))
+ continue;
+ if (!mutate) {
+ rv = 1;
+ goto done;
+ }
+ if (nfound == cap) {
+ cap = cap != 0 ? cap * 2 : 64;
+ if ((grown = reallocarray(found, cap,
+ sizeof(*found))) == NULL) {
+ log_warn("session %u: scanning cur",
+ session_id);
+ goto done;
+ }
+ found = grown;
+ }
+ if ((found[nfound] = strdup(de->d_name)) == NULL) {
+ log_warn("session %u: scanning cur", session_id);
+ goto done;
+ }
+ nfound++;
+ }
+ if (nfound > 1)
+ qsort(found, nfound, sizeof(*found), name_cmp);
+
+ for (i = 0; i < nfound; i++) {
+ name = found[i];
+ len = strcspn(name, ":");
+ if (prev != NULL && len == prevlen &&
+ memcmp(prev, name, len) == 0) {
+ log_warnx("session %u: cur/%s: not indexed, another "
+ "file has its base name", session_id, name);
+ continue;
+ }
+ if (name[len] == '\0') {
+ if (snprintf(from, sizeof(from), "cur/%s", name) >=
+ (int)sizeof(from) ||
+ snprintf(to, sizeof(to), "cur/%s:2,", name) >=
+ (int)sizeof(to) ||
+ fstatat(dfd, to, &st, AT_SYMLINK_NOFOLLOW) == 0) {
+ log_warnx("session %u: cur/%s: not indexed, "
+ "cannot add \":2,\"", session_id, name);
+ continue;
+ }
+ if (errno != ENOENT ||
+ renameat(dfd, from, dfd, to) == -1) {
+ log_warn("session %u: cur/%s: not indexed",
+ session_id, name);
+ continue;
+ }
+ synced = 1;
+ name = to + strlen("cur/");
+ }
+ prev = found[i];
+ prevlen = len;
+ /* RFC 9051 section 2.3.1.1: convert before the UID is given */
+ if (to_crlf(dfd, "cur", name) == 1)
+ synced = 1;
+ memcpy(base, name, len);
+ base[len] = '\0';
+ if (index_append(idx, idx->uidnext, base) == -1)
+ goto done;
+ idx->uidnext++;
+ added++;
+ }
+ if (synced && fsync(dirfd(dp)) == -1)
+ log_warn("session %u: fsync cur", session_id);
+ if (added > 0)
+ log_info("session %u: indexed %zu file(s) found in cur/",
+ session_id, added);
+ rv = added > 0;
+
+done:
+ if (dp != NULL)
+ closedir(dp);
+ for (i = 0; i < nfound; i++)
+ free(found[i]);
+ free(found);
+ free(bs.name);
+ free(bs.len);
+ if (rv == -1 && mutate)
+ index_free(idx);
+ return (rv);
+}
+
+static int
+index_scan(int dfd, struct mbox_index *idx, int mutate)
+{
+ int in_new, in_cur;
+
+ if ((in_new = index_scan_new(dfd, idx, mutate)) == -1)
+ return (-1);
+ if (in_new && !mutate)
+ return (1);
+ if ((in_cur = index_scan_cur(dfd, idx, mutate)) == -1)
+ return (-1);
+ return (in_new || in_cur);
+}
+
int
refresh_index(int dfd, struct mbox_index *idx, int fd)
{
if (index_load(fd, idx) == -1)
return (-1);
- if ((added = index_scan_new(dfd, idx, 1)) == -1)
- return (-1); /* index_scan_new() has already freed idx */
+ if ((added = index_scan(dfd, idx, 1)) == -1)
+ return (-1); /* index_scan() has already freed idx */
/* a fresh UIDVALIDITY must still be written down */
if (!added && !idx->fresh)
return ((lo < n && list[lo] == uid) ? lo : n);
}
-/* RFC 9051 SS7.5.1: once told, never again, so drop it from the baseline */
+/* RFC 9051 section 7.5.1: told once, never again; drop it from the baseline */
int
send_expunged(struct store_session *ss, uint32_t seqno, uint32_t uid)
{
return (0);
}
-/* RFC 2180 SS4.1.2: does the set name a message expunged, not yet told? */
+/* RFC 2180 section 4.1.2: does the set name an expunge not yet told? */
int
view_names_ghost(const struct store_session *ss, const struct mbox_index *idx,
const struct seq_range *uids, uint32_t nuids)
return (0);
}
-/* RFC 9051 SS7.5.1: each EXPUNGE renumbers those above it */
+/* RFC 9051 section 7.5.1: each EXPUNGE renumbers those above it */
static size_t
view_merge(const uint32_t *old, size_t oldn, const uint32_t *cur,
size_t curn, int expunge_ok, uint32_t *view, size_t *viewn,
return (gone);
}
-/* RFC 9051 SS6.3.13: flag changes, as FETCH with UID */
+/* RFC 9051 section 6.3.13: flag changes, as FETCH with UID */
static size_t
view_send_flag_fetches(const struct mbox_index *idx, const uint32_t *old,
size_t oldn, uint64_t since, const uint32_t *view, size_t viewn,
if (locate_message_file(&ss->cur_snap, ss->mailbox_dir_fd,
rec.basename, &size, suffix, sizeof(suffix)) == -1) {
log_warnx("session %u: message %s (uid %u) indexed "
- "but missing on disk, no IDLE flag push",
+ "but missing on disk, not pushed",
session_id, rec.basename, rec.uid);
continue;
}
base->valid = 1;
}
-/* RFC 9051 SS5.2, SS7.5.1: what changed; EXPUNGE only if expunge_ok */
+/* RFC 9051 sections 5.2, 7.5.1: what changed; EXPUNGE only if expunge_ok */
int
view_sync(struct store_session *ss, const struct mbox_index *idx,
int expunge_ok)
&viewn, &ss->iev);
(void)view_send_flag_fetches(idx, base->uids, base->n, base->modseq,
view, viewn, ss);
- /* after EXPUNGEs too, as RFC 9051 SS6.3.13's example does */
+ /* after EXPUNGEs too, as RFC 9051 section 6.3.13's example does */
if (gone > 0 || viewn != base->n) {
memset(&ve, 0, sizeof(ve));
ve.exists = (uint32_t)viewn;
struct store_idle_baseline *base = &ss->idle_baseline;
struct imsgev *iev = &ss->iev;
struct mbox_index idx;
- struct imsg_mbox_idle_refreshed reply;
+ struct imsg_mbox_idle_refreshed reply;
struct index_lock il = INDEX_LOCK_INIT;
int pending = 0, seeded, locked;
index_lock_release(&il);
goto send;
}
- if ((pending = index_scan_new(ss->mailbox_dir_fd, &idx, 0)) == -1) {
+ if ((pending = index_scan(ss->mailbox_dir_fd, &idx, 0)) == -1) {
index_free(&idx);
index_lock_release(&il);
goto send;
goto send;
busy:
- /* RFC 9051 SS7.5.1: a seed cannot skip, or EXPUNGEs repeat */
+ /* RFC 9051 section 7.5.1: a seed cannot skip, or EXPUNGEs repeat */
reply.busy = 1;
if (seeded && idle_seed_unlocked(ss) == 0) {
reply.ok = 1;
blob - d1f56a65c1e211d40c9520058aff64700ffd3ce8
blob + ccbf8986e244971ab893be8b94ac99c6eaa7fcc9
--- src/keymgr.c
+++ src/keymgr.c
* Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
* Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org>
*
- * This file's overall architecture -- a dedicated process holding the
- * real TLS private key, a "fake private key" installed in the
- * TLS-terminating process instead, and a process-wide OpenSSL
- * RSA_METHOD/EC_KEY_METHOD engine override that forwards every
- * private-key operation here as a synchronous imsg round-trip -- is
- * smtpd's ca.c (Reyk Floeter, Gilles Chehade), ported to imapd's own
- * imsg/privsep conventions rather than copied verbatim. The RSA_METHOD/
- * EC_KEY_METHOD engine-override code itself (the code this file's
- * request/reply pair answers) lives in listener.c, not here -- see that
- * file's header comment for the matching attribution.
- *
- * keymgr_pubkey_hash() below additionally replicates, byte-for-byte,
- * smtpd's ssl.c hash_x509()/ssl_pubkey_hash() algorithm (Pierre-Yves
- * Ritschard, Reyk Floeter, Gilles Chehade): SHA256 of the certificate's
- * DER-encoded SubjectPublicKeyInfo, formatted "SHA256:" plus lowercase
- * hex. That exact format is what libtls's own (unexported)
- * tls_cert_pubkey_hash() tags onto the fake key's OpenSSL ex_data slot 0
- * at TLS-config time (lib/libtls/tls.c, confirmed by direct reading, not
- * assumed) -- this process's key lookup only works if it derives the
- * identical string from the same certificate, so the exact byte shape
- * of hash_x509() is load-bearing here, not just a convenient precedent.
- *
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
imsg_free(&imsg);
}
- /* a bad key is not fatal: its operations fail instead */
if (cert_len == 0 || key_len == 0)
- log_warnx("no usable TLS cert/key at boot, TLS "
- "private-key operations will fail until the next "
- "SIGHUP reload supplies valid material");
+ log_warnx("no usable TLS cert/key, handshakes fail until a "
+ "reload");
else if (keymgr_load(cert_buf, cert_len, key_buf, key_len) == -1)
- log_warnx("failed to load TLS cert/key at boot "
- "(see above), TLS private-key operations will fail "
- "until the next SIGHUP reload supplies valid material");
+ log_warnx("TLS cert/key not loaded, handshakes fail until a "
+ "reload");
explicit_bzero(key_buf, sizeof(key_buf));
keymgr_got_init = 1;
imsgev_init_from_ibuf(&iev_parent, &ibuf3, keymgr_dispatch_parent,
NULL);
-#ifdef __OpenBSD__
/* recvfd only: peers arrive, nothing is sent */
if (pledge("stdio recvfd", NULL) == -1)
fatal("pledge");
-#endif
event_dispatch();
fatalx("exited event loop");
}
-/* as smtpd's ssl.c hash_x509() */
+/* as smtpd's ssl.c hash_x509(); libtls tags the key with this exact string */
static int
keymgr_pubkey_hash(X509 *cert, char *hash, size_t hashlen)
{
X509 *cert = NULL;
EVP_PKEY *pkey = NULL;
char hash[KEYMGR_HASH_MAX];
+ int rc = -1;
if (cert_len == 0 || key_len == 0) {
log_warnx("empty cert or key, not (re)loading");
(void)strlcpy(keymgr_hash, hash, sizeof(keymgr_hash));
log_info("TLS key loaded (%s)", keymgr_hash);
+ rc = 0;
+fail:
BIO_free(cert_bio);
BIO_free(key_bio);
X509_free(cert);
- return (0);
-
-fail:
- if (cert_bio != NULL)
- BIO_free(cert_bio);
- if (key_bio != NULL)
- BIO_free(key_bio);
- if (cert != NULL)
- X509_free(cert);
- if (pkey != NULL)
- EVP_PKEY_free(pkey);
- return (-1);
+ EVP_PKEY_free(pkey);
+ return (rc);
}
static void
if (keymgr_load(reload_cert_buf, reload_cert_len, reload_key_buf,
reload_key_len) == -1)
- log_warnx("SIGHUP reload: keeping previous key, see above");
+ log_warnx("SIGHUP: keeping the previous key");
explicit_bzero(reload_key_buf, sizeof(reload_key_buf));
reload_got_cert = reload_got_key = 0;
}
keymgr_key_free();
exit(0);
case IMSG_SETUP_PEER: {
- uint32_t sess_id = imsg_get_id(&imsg);
- int peer_fd = imsg_get_fd(&imsg);
+ uint32_t sess_id;
+ int peer_fd;
struct keymgr_peer *kp;
int flags;
+ sess_id = imsg_get_id(&imsg);
+ peer_fd = imsg_get_fd(&imsg);
if (peer_fd == -1) {
log_warnx("IMSG_SETUP_PEER carried no fd");
break;
break;
}
case IMSG_TLS_CERT: {
- size_t len = imsg_get_len(&imsg);
+ size_t len;
+ len = imsg_get_len(&imsg);
if (len > sizeof(reload_cert_buf)) {
log_warnx("SIGHUP reload: TLS cert "
"too large (%zu > %zu)", len,
break;
}
case IMSG_KEYMGR_INIT: {
- size_t len = imsg_get_len(&imsg);
+ size_t len;
+ len = imsg_get_len(&imsg);
if (len > sizeof(reload_key_buf)) {
log_warnx("SIGHUP reload: TLS key "
"too large (%zu > %zu)", len,
imsg_free(&imsg);
}
imsgev_rearm_read(iev);
- (void)fd;
}
static void
imsg_free(&imsg);
}
imsgev_rearm_read(iev);
- (void)fd;
}
static void
keymgr_recv_trailing(struct imsg *imsg, uint32_t len, unsigned char *buf,
size_t bufsize)
{
- size_t bodylen = imsg_get_len(imsg);
+ size_t bodylen;
+ bodylen = imsg_get_len(imsg);
if (len > bufsize || bodylen != (size_t)len) {
- log_warnx("trailing data length mismatch (header "
- "says %u, imsg has %zu bytes, buffer holds %zu)", len,
- bodylen, bufsize);
- return (0);
+ log_warnx("trailing data length mismatch (header %u, imsg "
+ "%zu, buffer %zu)", len, bodylen, bufsize);
+ return (-1);
}
if (bodylen == 0)
- return (1);
+ return (0);
if (imsg_get_buf(imsg, buf, bodylen) == -1) {
log_warnx("bad trailing data");
- return (0);
+ return (-1);
}
- return (1);
+ return (0);
}
/* the reply reuses the request's type, correlated by id */
KEYMGR_DATA_MAX];
if (ok && tolen > KEYMGR_DATA_MAX) {
- log_warnx("keymgr_reply: %zu-byte result exceeds "
- "KEYMGR_DATA_MAX (%d), refusing", tolen,
- KEYMGR_DATA_MAX);
+ log_warnx("keymgr_reply: %zu bytes over KEYMGR_DATA_MAX %d",
+ tolen, KEYMGR_DATA_MAX);
ok = 0;
}
/* imsg_get_buf() does not NUL-terminate */
req.hash[sizeof(req.hash) - 1] = '\0';
- if (!keymgr_recv_trailing(imsg, req.fromlen, from, sizeof(from))) {
+ if (keymgr_recv_trailing(imsg, req.fromlen, from, sizeof(from)) == -1) {
keymgr_reply(iev, type, id, 0, NULL, 0);
return;
}
if (!keymgr_got_init) {
- log_warnx("%s request before IMSG_KEYMGR_INIT "
- "completed, refusing", opname);
+ log_warnx("%s before IMSG_KEYMGR_INIT", opname);
keymgr_reply(iev, type, id, 0, NULL, 0);
return;
}
if (keymgr_pkey == NULL || strcmp(req.hash, keymgr_hash) != 0) {
- log_warnx("%s request for unrecognized key hash, "
- "refusing", opname);
+ log_warnx("%s: unknown key hash", opname);
keymgr_reply(iev, type, id, 0, NULL, 0);
return;
}
explicit_bzero(to, sizeof(to));
}
-/* as smtpd's ca.c ca_imsg() */
static void
keymgr_handle_ecdsa(struct imsgev *iev, struct imsg *imsg, uint32_t id)
{
}
req.hash[sizeof(req.hash) - 1] = '\0';
- if (!keymgr_recv_trailing(imsg, req.fromlen, dgst, sizeof(dgst))) {
+ if (keymgr_recv_trailing(imsg, req.fromlen, dgst, sizeof(dgst)) == -1) {
keymgr_reply(iev, IMSG_KEYMGR_ECDSA_SIGN, id, 0, NULL, 0);
return;
}
if (!keymgr_got_init) {
- log_warnx("ECDSA_SIGN request before "
- "IMSG_KEYMGR_INIT completed, refusing");
+ log_warnx("ECDSA_SIGN before IMSG_KEYMGR_INIT");
keymgr_reply(iev, IMSG_KEYMGR_ECDSA_SIGN, id, 0, NULL, 0);
return;
}
if (keymgr_pkey == NULL || strcmp(req.hash, keymgr_hash) != 0) {
- log_warnx("ECDSA_SIGN request for unrecognized key "
- "hash, refusing");
+ log_warnx("ECDSA_SIGN: unknown key hash");
keymgr_reply(iev, IMSG_KEYMGR_ECDSA_SIGN, id, 0, NULL, 0);
return;
}
blob - 72cd56b418ec77a0a000346f1b2af0a4aa3ff452
blob + 5a6b4b20eaf510e140a2221587e287c2228b1633
--- src/listener.c
+++ src/listener.c
* Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
* Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
*
- * The RSA_METHOD/EC_KEY_METHOD engine override below (keymgr_engine_
- * init() and everything it installs) adapts smtpd's ca.c
- * (rsa_engine_init()/ecdsa_engine_init()/rsae_priv_enc()/rsae_priv_
- * dec()/ecdsae_do_sign(), ca.c:289-558) to imapd's own imsg
- * conventions: the OpenSSL API shape leaves little room for
- * independent structure, and this project's own licensing
- * precedent (log.c, imsgev.c) already treats a borrow this close as
- * needing the original author's copyright even where the
- * implementation differs. keymgr_use_fake_private_key()'s two-line call
- * shape is lifted from smtpd's smtp.c:187-193 (Gilles Chehade,
- * Pierre-Yves Ritschard, Jacek Masiulaniec); see that function's own
- * comment.
- *
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
+#include <sys/param.h>
#include <sys/types.h>
-#include <sys/queue.h>
#include <sys/socket.h>
+#include <sys/uio.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <grp.h>
#include <imsg.h>
+#include <limits.h>
#include <netdb.h>
#include <poll.h>
#include <pwd.h>
#include "log.h"
#include "listener.h"
-struct session_list sessions = TAILQ_HEAD_INITIALIZER(sessions);
+static struct session sess; /* one process, one session */
struct imsgev iev_auth;
struct imsgev iev_parent;
(ST_NOTAUTH | (1U << SESSION_AUTHENTICATING) | \
(1U << SESSION_STORE_PENDING))
-/* RFC 9051 SS9; excludes transient states, which have their own pending_tag. */
+/* RFC 9051 section 9; the transient states have their own pending_tag */
#define ST_AUTH \
((1U << SESSION_AUTHENTICATED) | (1U << SESSION_SELECTED))
#define ST_SELECTED (1U << SESSION_SELECTED)
{ "STARTTLS", ST_NOTAUTH, cmd_starttls },
{ "AUTHENTICATE", ST_NOTAUTH, cmd_authenticate },
- /* command-auth, RFC 9051 SS6.3 order */
+ /* command-auth, RFC 9051 section 6.3 order */
{ "ENABLE", ST_AUTH, cmd_enable },
{ "SELECT", ST_AUTH, cmd_select },
{ "EXAMINE", ST_AUTH, cmd_examine },
{ "APPEND", ST_AUTH, cmd_append },
{ "IDLE", ST_AUTH, cmd_idle },
- /* command-select, RFC 9051 SS6.4 order */
+ /* command-select, RFC 9051 section 6.4 order */
{ "CLOSE", ST_SELECTED, cmd_close },
{ "UNSELECT", ST_SELECTED, cmd_unselect },
{ "EXPUNGE", ST_SELECTED, cmd_expunge },
{ "MOVE", ST_SELECTED, cmd_move },
{ "UID", ST_SELECTED, cmd_uid },
};
-#define NUM_IMAP_CMDS (sizeof(imap_cmds) / sizeof(imap_cmds[0]))
/* internal to libtls, declared as smtpd/smtp.c does */
void tls_config_use_fake_private_key(struct tls_config *);
size_t cert_len)
{
tls_config_use_fake_private_key(config);
- return tls_config_set_keypair_mem(config, (const uint8_t *)cert_buf,
- cert_len, NULL, 0);
+ return (tls_config_set_keypair_mem(config,
+ (const uint8_t *)cert_buf, cert_len, NULL, 0));
}
/* private-key operations go to keymgr, after smtpd's ca.c */
/* check A: the placeholder key has no private part */
if (priv != NULL)
- fatalx("%s: key object carries a private component -- "
- "libtls is no longer using a placeholder key, and this "
- "process is not separated from the TLS private key", op);
+ fatalx("%s: key object carries a private component", op);
/* check B: one keypair, so a missing tag is a regression */
if (hash == NULL)
- fatalx("%s: no pubkey-hash tag on the key object -- libtls's "
- "ex_data slot 0 tagging has changed", op);
+ fatalx("%s: no pubkey-hash tag on the key object", op);
/* check C1: NUL-terminated within KEYMGR_HASH_MAX */
for (i = 0; i < KEYMGR_HASH_MAX; i++)
if (hash[i] == '\0')
return;
- fatalx("%s: pubkey-hash tag is not a NUL-terminated string within "
- "%d bytes -- refusing to read it", op, KEYMGR_HASH_MAX);
+ fatalx("%s: pubkey-hash tag not NUL-terminated within %d bytes",
+ op, KEYMGR_HASH_MAX);
}
static int
keymgr_rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to,
RSA *rsa, int padding)
{
- const char *hash = RSA_get_ex_data(rsa, 0);
+ const char *hash;
+ hash = RSA_get_ex_data(rsa, 0);
keymgr_assert_fake_key(hash, RSA_get0_d(rsa), "RSA_PRIVENC");
return (keymgr_forward_rsa(IMSG_KEYMGR_RSA_PRIVENC, hash,
from, flen, to, (size_t)RSA_size(rsa), padding));
keymgr_rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to,
RSA *rsa, int padding)
{
- const char *hash = RSA_get_ex_data(rsa, 0);
+ const char *hash;
+ hash = RSA_get_ex_data(rsa, 0);
keymgr_assert_fake_key(hash, RSA_get0_d(rsa), "RSA_PRIVDEC");
return (keymgr_forward_rsa(IMSG_KEYMGR_RSA_PRIVDEC, hash,
from, flen, to, (size_t)RSA_size(rsa), padding));
keymgr_ecdsa_do_sign(const unsigned char *dgst, int dgst_len,
const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey)
{
- const char *hash = EC_KEY_get_ex_data(eckey, 0);
+ const char *hash;
- (void)inv;
- (void)rp;
-
+ hash = EC_KEY_get_ex_data(eckey, 0);
keymgr_assert_fake_key(hash, EC_KEY_get0_private_key(eckey),
"ECDSA_SIGN");
return (keymgr_forward_ecdsa(hash, dgst, dgst_len));
}
switch (imsg_get_type(&imsg)) {
case IMSG_SETUP_PEER: {
- uint32_t id = imsg_get_id(&imsg);
- int peer_fd = imsg_get_fd(&imsg);
+ uint32_t id;
+ int peer_fd;
+ id = imsg_get_id(&imsg);
+ peer_fd = imsg_get_fd(&imsg);
if (peer_fd == -1) {
log_warnx("listener: IMSG_SETUP_PEER "
"carried no fd");
NULL);
else {
iev_auth.ibuf.fd = -1;
- log_warnx("session %u: no auth-worker was spawned for this "
- "connection, AUTHENTICATE/LOGIN will fail until a new "
- "connection gets one", sinit.session_id);
+ log_warnx("session %u: no auth-worker for this connection",
+ sinit.session_id);
}
if (imsgbuf_init(&keymgr_ibuf, keymgr_peer_fd) == -1)
sinit.implicit_tls, &sinit.remote_ss, sinit.remote_sslen);
/* no "inet": getnameinfo(3) only formats numeric bytes */
-#ifdef __OpenBSD__
if (pledge("stdio recvfd", NULL) == -1)
fatal("pledge");
-#endif
event_dispatch();
fatalx("listener: exited event loop");
{
struct session *s = arg;
- (void)fd;
- (void)event;
-
log_info("session %u: closing peer=%.200s, no authentication within "
"%u seconds", s->id, s->remote_addr, listener_login_grace_secs);
session_teardown(s, "login-grace");
tv.tv_sec = (time_t)listener_login_grace_secs;
tv.tv_usec = 0;
if (evtimer_add(&s->grace_ev, &tv) == -1)
- log_warnx("session %u: evtimer_add (login grace); this "
- "session will not be closed if it never authenticates",
- s->id);
+ log_warnx("session %u: evtimer_add (login grace)", s->id);
}
void
listener_start_session(uint32_t session_id, int client_fd, int implicit_tls,
const struct sockaddr_storage *ss, socklen_t sslen)
{
- struct session *s;
+ struct session *s = &sess;
+ char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
- s = calloc(1, sizeof(*s));
- if (s == NULL) {
- log_warn("calloc");
- close(client_fd);
- exit(1);
- }
- s->pending_body_fd = -1; /* calloc(3)'s 0 is a real descriptor */
+ s->pending_body_fd = -1; /* 0 is a real descriptor */
session_idle_poll_init(s); /* before anything can tear s down */
s->id = session_id;
s->client_fd = client_fd;
session_login_grace_init(s);
if (clock_gettime(CLOCK_MONOTONIC, &s->connected_at) == -1)
log_warn("session %u: clock_gettime", s->id);
- TAILQ_INSERT_TAIL(&sessions, s, entry);
- {
- char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+ /* numeric: no resolver or network I/O */
+ if (getnameinfo((const struct sockaddr *)ss, sslen, hbuf, sizeof(hbuf),
+ sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == 0)
+ snprintf(s->remote_addr, sizeof(s->remote_addr),
+ ss->ss_family == AF_INET6 ? "[%s]:%s" : "%s:%s", hbuf,
+ sbuf);
+ else
+ strlcpy(s->remote_addr, "?", sizeof(s->remote_addr));
- /* numeric: no resolver or network I/O */
- if (getnameinfo((const struct sockaddr *)ss, sslen, hbuf,
- sizeof(hbuf), sbuf, sizeof(sbuf),
- NI_NUMERICHOST | NI_NUMERICSERV) == 0)
- snprintf(s->remote_addr, sizeof(s->remote_addr),
- ss->ss_family == AF_INET6 ? "[%s]:%s" : "%s:%s",
- hbuf, sbuf);
- else
- strlcpy(s->remote_addr, "?", sizeof(s->remote_addr));
- }
-
/* %.200s bounds untrusted text, the way sshd's auth.c does */
/* tls= is how it was accepted; the close line has the final state */
log_info("session %u: connected peer=%.200s tls=%s", s->id,
struct session *s = arg;
int ret;
- (void)fd;
- (void)event;
-
ret = tls_handshake(s->tls_ctx);
if (ret == 0) {
s->tls_active = 1;
session_teardown(s, "tls-error");
}
-/* RFC 9051 SS7.1.1's example OK-response text, used verbatim. */
+/* RFC 9051 section 7.1.1's example OK-response text, used verbatim. */
void
session_send_greeting(struct session *s)
{
static int session_is_busy(const struct session *);
static int session_enqueue_cmd(struct session *, const char *);
-/* RFC 9051 SS4.3 hard cap on a non-synchronizing literal. */
+/* RFC 9051 section 4.3 hard cap on a non-synchronizing literal. */
#define IMAP_NONSYNC_LITERAL_MAX 4096
-/* RFC 9051 SS4.3 "{n}" or "{n+}" at p; *endp is past its "}" */
+/* RFC 9051 section 4.3 "{n}" or "{n+}" at p; *endp is past its "}" */
static int
literal_count(const char *p, uint64_t *lenp, int *nonsyncp, const char **endp)
{
- char digits[24];
- size_t dlen;
- unsigned long long v;
+ char digits[24];
+ const char *errstr;
+ size_t dlen;
+ long long v;
if (*p++ != '{')
return (0);
return (0);
memcpy(digits, p, dlen);
digits[dlen] = '\0';
- errno = 0;
- v = strtoull(digits, NULL, 10);
- if (errno == ERANGE)
+ v = strtonum(digits, 0, LLONG_MAX, &errstr);
+ if (errstr != NULL)
return (0);
p += dlen;
*nonsyncp = (*p == '+');
return (1);
}
-/* RFC 9051 SS4.3: run a command, re-parsed as each literal arrives */
+/* RFC 9051 section 4.3: run a command, re-parsed as each literal arrives */
static int
session_run_cmd(struct session *s, char *cmd)
{
return (0);
}
-/* RFC 9051 SS9: a tag may not contain "+" */
+/* RFC 9051 section 9: a tag may not contain "+" */
static int
tag_is_valid(const char *tag)
{
return (1);
}
-void
-session_dispatch_client(int fd, short event, void *arg)
+/* octets read into inbuf, or -1: retry later, or the session is gone */
+static ssize_t
+session_read_client(struct session *s, int fd, size_t *tls_wantp)
{
- struct session *s = arg;
- ssize_t n;
- char *crlf;
- size_t tls_want = 0;
+ ssize_t n;
- (void)event;
-
- if (s->write_failed) {
- session_teardown(s, "io-error");
- return;
- }
-
+ *tls_wantp = 0;
if (s->tls_active) {
- tls_want = sizeof(s->inbuf) - s->inbuflen;
- n = tls_read(s->tls_ctx, s->inbuf + s->inbuflen, tls_want);
+ *tls_wantp = sizeof(s->inbuf) - s->inbuflen;
+ n = tls_read(s->tls_ctx, s->inbuf + s->inbuflen, *tls_wantp);
if (n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT) {
/* tls_read() may want to write */
event_del(&s->client_ev);
(n == TLS_WANT_POLLIN) ? EV_READ : EV_WRITE,
session_dispatch_client, s);
event_add(&s->client_ev, NULL);
- return;
+ return (-1);
}
if (n == -1) {
log_warnx("session %u: tls_read: %s", s->id,
tls_error(s->tls_ctx));
session_teardown(s, "tls-error");
- return;
+ return (-1);
}
session_arm_client_read(s);
} else {
if (n == -1) {
if (errno == EINTR || errno == EAGAIN ||
errno == EWOULDBLOCK)
- return;
+ return (-1);
log_warn("session %u: read", s->id);
session_teardown(s, "io-error");
- return;
+ return (-1);
}
}
-
if (n == 0) {
log_debug("session %u: client closed connection", s->id);
session_teardown(s, "client-closed");
- return;
+ return (-1);
}
- s->inbuflen += (size_t)n;
+ return (n);
+}
- for (;;) {
- uint64_t nonsync_len;
- size_t consumed, linelen;
- int alive, resume;
+/* consumption steps: 1 consumed, 0 wants more input, -1 session gone */
+static int
+session_discard_literal(struct session *s)
+{
+ uint64_t take;
- if (s->literal_discard > 0) {
- uint64_t take;
+ take = (uint64_t)s->inbuflen < s->literal_discard ?
+ (uint64_t)s->inbuflen : s->literal_discard;
+ if (take > 0) {
+ memmove(s->inbuf, s->inbuf + take, s->inbuflen - (size_t)take);
+ s->inbuflen -= (size_t)take;
+ s->literal_discard -= take;
+ }
+ if (s->literal_discard > 0)
+ return (0);
+ s->literal_skipline = 1;
+ return (1);
+}
- take = (uint64_t)s->inbuflen < s->literal_discard ?
- (uint64_t)s->inbuflen : s->literal_discard;
- if (take > 0) {
- memmove(s->inbuf, s->inbuf + take,
- s->inbuflen - (size_t)take);
- s->inbuflen -= (size_t)take;
- s->literal_discard -= take;
- }
- if (s->literal_discard > 0)
- break;
- s->literal_skipline = 1;
- continue;
- }
+/* RFC 9051 section 4.3 literal; may contain a CRLF */
+static int
+session_stream_append(struct session *s)
+{
+ uint64_t want, take;
- /* RFC 9051 SS4.3 literal; may contain a CRLF */
- if (s->literal_pending) {
- uint64_t want, take;
-
- want = s->literal_remaining;
- take = (uint64_t)s->inbuflen < want ?
- (uint64_t)s->inbuflen : want;
-
- if (take > 0) {
- if (imsg_compose(&s->store_iev->ibuf,
- IMSG_MBOX_APPEND_DATA, 0, 0, -1, s->inbuf,
- (size_t)take) == -1) {
- log_warn("session %u: imsg_compose "
- "IMSG_MBOX_APPEND_DATA", s->id);
- session_teardown(s, "io-error");
- return;
- }
- s->literal_remaining -= take;
- memmove(s->inbuf, s->inbuf + take,
- s->inbuflen - (size_t)take);
- s->inbuflen -= (size_t)take;
- }
-
- if (s->literal_remaining > 0)
- break;
-
- if (s->inbuflen < 2)
- break;
- if (s->inbuf[0] != '\r' || s->inbuf[1] != '\n') {
- /* no reliable resync point, give up */
- log_warnx("session %u: expected CRLF after "
- "literal data, closing", s->id);
- session_teardown(s, "protocol-error");
- return;
- }
- memmove(s->inbuf, s->inbuf + 2, s->inbuflen - 2);
- s->inbuflen -= 2;
-
- s->literal_pending = 0;
- alive = session_finish_append(s);
- if (!alive)
- return;
- continue;
+ want = s->literal_remaining;
+ take = (uint64_t)s->inbuflen < want ? (uint64_t)s->inbuflen : want;
+ if (take > 0) {
+ if (imsg_compose(&s->store_iev->ibuf, IMSG_MBOX_APPEND_DATA,
+ 0, 0, -1, s->inbuf, (size_t)take) == -1) {
+ log_warn("session %u: imsg_compose "
+ "IMSG_MBOX_APPEND_DATA", s->id);
+ session_teardown(s, "io-error");
+ return (-1);
}
+ s->literal_remaining -= take;
+ memmove(s->inbuf, s->inbuf + take, s->inbuflen - (size_t)take);
+ s->inbuflen -= (size_t)take;
+ }
+ if (s->literal_remaining > 0)
+ return (0);
- if (s->cmd_gather && s->cmd_octets > 0) {
- size_t take;
+ if (s->inbuflen < 2)
+ return (0);
+ if (s->inbuf[0] != '\r' || s->inbuf[1] != '\n') {
+ /* no reliable resync point, give up */
+ log_warnx("session %u: expected CRLF after literal data, "
+ "closing", s->id);
+ session_teardown(s, "protocol-error");
+ return (-1);
+ }
+ memmove(s->inbuf, s->inbuf + 2, s->inbuflen - 2);
+ s->inbuflen -= 2;
- take = s->inbuflen < s->cmd_octets ?
- s->inbuflen : s->cmd_octets;
- if (take == 0)
- break;
- if (memchr(s->inbuf, '\0', take) != NULL) {
- /* RFC 9051 SS9: CHAR8 excludes NUL */
- session_cmd_reply(s, "BAD", "NUL in a literal");
- s->literal_discard = s->cmd_octets;
- s->cmd_gather = 0;
- s->cmd_queued = 0;
- s->cmd_octets = 0;
- continue;
- }
- memcpy(s->cmdbuf + s->cmdlen, s->inbuf, take);
- s->cmdlen += take;
- s->cmd_octets -= take;
- memmove(s->inbuf, s->inbuf + take, s->inbuflen - take);
- s->inbuflen -= take;
- continue;
- }
+ s->literal_pending = 0;
+ if (!session_finish_append(s))
+ return (-1);
+ return (1);
+}
- crlf = memmem(s->inbuf, s->inbuflen, "\r\n", 2);
- if (crlf == NULL)
- break;
+static int
+session_gather_octets(struct session *s)
+{
+ size_t take;
- linelen = (size_t)(crlf - s->inbuf);
- consumed = linelen + 2;
+ take = s->inbuflen < s->cmd_octets ? s->inbuflen : s->cmd_octets;
+ if (take == 0)
+ return (0);
+ if (memchr(s->inbuf, '\0', take) != NULL) {
+ /* RFC 9051 section 9: CHAR8 excludes NUL */
+ session_cmd_reply(s, "BAD", "NUL in a literal");
+ s->literal_discard = s->cmd_octets;
+ s->cmd_gather = 0;
+ s->cmd_queued = 0;
+ s->cmd_octets = 0;
+ return (1);
+ }
+ memcpy(s->cmdbuf + s->cmdlen, s->inbuf, take);
+ s->cmdlen += take;
+ s->cmd_octets -= take;
+ memmove(s->inbuf, s->inbuf + take, s->inbuflen - take);
+ s->inbuflen -= take;
+ return (1);
+}
- /* RFC 9051 SS2.2/SS9: no CR, LF or NUL inside a line */
- if (memchr(s->inbuf, '\r', linelen) != NULL ||
- memchr(s->inbuf, '\n', linelen) != NULL ||
- memchr(s->inbuf, '\0', linelen) != NULL) {
- static const char bad[] = "* BAD bare CR, LF or NUL "
- "in command line, closing connection\r\n";
+static int
+session_route_line(struct session *s, size_t linelen, uint64_t nonsync_len)
+{
+ int alive, resume;
- log_warnx("session %u: bare CR/LF/NUL in command "
- "line, closing", s->id);
- session_write(s, bad, sizeof(bad) - 1);
- session_teardown(s, "protocol-error");
- return;
- }
-
- *crlf = '\0';
-
- nonsync_len = 0;
- if (line_nonsync_literal(s->inbuf, &nonsync_len) &&
- nonsync_len > IMAP_NONSYNC_LITERAL_MAX) {
- /* RFC 7888 SS4, SS5: BYE, with RFC 4469's TOOBIG */
- static const char bye[] = "* BYE [TOOBIG] "
- "non-synchronizing literal exceeds 4096 octets\r\n";
-
- log_warnx("session %u: oversized non-synchronizing "
- "literal (%llu), closing", s->id,
- (unsigned long long)nonsync_len);
- session_write(s, bye, sizeof(bye) - 1);
- session_teardown(s, "limit-exceeded");
- return;
- }
-
- resume = 0;
- if (s->literal_skipline) {
- s->literal_skipline = 0;
- alive = 1;
- resume = 1;
- } else if (s->cmd_gather) {
- s->cmd_gather = 0;
- alive = 1;
- if (linelen >= sizeof(s->cmdbuf) - s->cmdlen) {
- session_cmd_reply(s, "NO", cmd_too_long);
- s->cmd_queued = 0;
- } else {
- memcpy(s->cmdbuf + s->cmdlen, s->inbuf,
- linelen + 1);
- if (!s->cmd_queued)
- alive = session_run_cmd(s, s->cmdbuf);
- else if (nonsync_len > 0) {
- s->cmdlen += linelen;
- (void)session_gather(s, nonsync_len);
- } else {
- s->cmd_queued = 0;
- alive = session_queue(s, s->cmdbuf);
- }
- }
- resume = 1;
- } else if (s->auth_cont) {
- /* the line is the user's base64 password, see below */
- s->scrub_inbuf = 1;
- alive = session_handle_auth_continuation(s, s->inbuf);
- } else if (s->idling) {
- alive = session_handle_idle_continuation(s, s->inbuf);
- } else if (session_is_busy(s)) {
- /* RFC 9051 SS5.5: queue while one is in flight */
- alive = 1;
- if (nonsync_len > 0) {
- (void)strlcpy(s->cmdbuf, s->inbuf,
- sizeof(s->cmdbuf));
- s->cmdlen = strlen(s->cmdbuf);
- s->cmd_queued = 1;
- (void)session_gather(s, nonsync_len);
- } else
- alive = session_queue(s, s->inbuf);
+ resume = 0;
+ if (s->literal_skipline) {
+ s->literal_skipline = 0;
+ alive = 1;
+ resume = 1;
+ } else if (s->cmd_gather) {
+ s->cmd_gather = 0;
+ alive = 1;
+ if (linelen >= sizeof(s->cmdbuf) - s->cmdlen) {
+ session_cmd_reply(s, "NO", cmd_too_long);
+ s->cmd_queued = 0;
} else {
- alive = session_run_cmd(s, s->inbuf);
+ memcpy(s->cmdbuf + s->cmdlen, s->inbuf, linelen + 1);
+ if (!s->cmd_queued)
+ alive = session_run_cmd(s, s->cmdbuf);
+ else if (nonsync_len > 0) {
+ s->cmdlen += linelen;
+ (void)session_gather(s, nonsync_len);
+ } else {
+ s->cmd_queued = 0;
+ alive = session_queue(s, s->cmdbuf);
+ }
}
- if (alive && resume && nonsync_len == 0)
- alive = session_dequeue_next(s);
- if (alive == 0)
- return; /* s was torn down (LOGOUT), do not touch */
+ resume = 1;
+ } else if (s->auth_cont) {
+ /* the line is the user's base64 password, scrubbed after use */
+ s->scrub_inbuf = 1;
+ alive = session_handle_auth_continuation(s, s->inbuf);
+ } else if (s->idling) {
+ alive = session_handle_idle_continuation(s, s->inbuf);
+ } else if (session_is_busy(s)) {
+ /* RFC 9051 section 5.5: queue while one is in flight */
+ alive = 1;
+ if (nonsync_len > 0) {
+ (void)strlcpy(s->cmdbuf, s->inbuf, sizeof(s->cmdbuf));
+ s->cmdlen = strlen(s->cmdbuf);
+ s->cmd_queued = 1;
+ (void)session_gather(s, nonsync_len);
+ } else
+ alive = session_queue(s, s->inbuf);
+ } else {
+ alive = session_run_cmd(s, s->inbuf);
+ }
+ if (alive && resume && nonsync_len == 0)
+ alive = session_dequeue_next(s);
+ return (alive);
+}
- if (nonsync_len > 0 && !s->literal_pending &&
- !s->cmd_gather)
- s->literal_discard = nonsync_len;
+static int
+session_consume_line(struct session *s)
+{
+ uint64_t nonsync_len;
+ size_t consumed, linelen;
+ char *crlf;
- /* cmd_starttls() zeroes inbuflen */
- if (consumed > s->inbuflen)
- consumed = s->inbuflen;
+ crlf = memmem(s->inbuf, s->inbuflen, "\r\n", 2);
+ if (crlf == NULL)
+ return (0);
+ linelen = (size_t)(crlf - s->inbuf);
+ consumed = linelen + 2;
- /* don't leave a base64 password in inbuf */
- if (s->scrub_inbuf) {
- explicit_bzero(s->inbuf, consumed);
- s->scrub_inbuf = 0;
- }
+ /* RFC 9051 section 2.2/section 9: no CR, LF or NUL inside a line */
+ if (memchr(s->inbuf, '\r', linelen) != NULL ||
+ memchr(s->inbuf, '\n', linelen) != NULL ||
+ memchr(s->inbuf, '\0', linelen) != NULL) {
+ static const char bad[] = "* BAD bare CR, LF or NUL "
+ "in command line, closing connection\r\n";
- memmove(s->inbuf, s->inbuf + consumed, s->inbuflen - consumed);
- s->inbuflen -= consumed;
+ log_warnx("session %u: bare CR/LF/NUL in command "
+ "line, closing", s->id);
+ session_write(s, bad, sizeof(bad) - 1);
+ session_teardown(s, "protocol-error");
+ return (-1);
}
+ *crlf = '\0';
+ nonsync_len = 0;
+ if (line_nonsync_literal(s->inbuf, &nonsync_len) &&
+ nonsync_len > IMAP_NONSYNC_LITERAL_MAX) {
+ /* RFC 7888 section 4, section 5: BYE, with RFC 4469's TOOBIG */
+ static const char bye[] = "* BYE [TOOBIG] "
+ "non-synchronizing literal exceeds 4096 octets\r\n";
+
+ log_warnx("session %u: oversized non-synchronizing "
+ "literal (%llu), closing", s->id,
+ (unsigned long long)nonsync_len);
+ session_write(s, bye, sizeof(bye) - 1);
+ session_teardown(s, "limit-exceeded");
+ return (-1);
+ }
+
+ if (!session_route_line(s, linelen, nonsync_len))
+ return (-1); /* s was torn down (LOGOUT), do not touch */
+
+ if (nonsync_len > 0 && !s->literal_pending && !s->cmd_gather)
+ s->literal_discard = nonsync_len;
+
+ /* cmd_starttls() zeroes inbuflen */
+ if (consumed > s->inbuflen)
+ consumed = s->inbuflen;
+
+ /* don't leave a base64 password in inbuf */
+ if (s->scrub_inbuf) {
+ explicit_bzero(s->inbuf, consumed);
+ s->scrub_inbuf = 0;
+ }
+
+ memmove(s->inbuf, s->inbuf + consumed, s->inbuflen - consumed);
+ s->inbuflen -= consumed;
+ return (1);
+}
+
+void
+session_dispatch_client(int fd, short event, void *arg)
+{
+ struct session *s = arg;
+ ssize_t n;
+ size_t tls_want;
+ int rc;
+
+ if (s->write_failed) {
+ session_teardown(s, "io-error");
+ return;
+ }
+ if ((n = session_read_client(s, fd, &tls_want)) == -1)
+ return;
+ s->inbuflen += (size_t)n;
+
+ for (;;) {
+ if (s->literal_discard > 0)
+ rc = session_discard_literal(s);
+ else if (s->literal_pending)
+ rc = session_stream_append(s);
+ else if (s->cmd_gather && s->cmd_octets > 0)
+ rc = session_gather_octets(s);
+ else
+ rc = session_consume_line(s);
+ if (rc == -1)
+ return;
+ if (rc == 0)
+ break;
+ }
+
if (s->inbuflen == sizeof(s->inbuf)) {
- /* RFC 9051 SS7.1.3's example text */
+ /* RFC 9051 section 7.1.3's example text */
static const char bad[] = "* BAD command line too long\r\n";
log_warnx("session %u: command line too long, closing",
}
/* libtls may hold bytes that EV_READ will not report */
- if (s->tls_active && n > 0 && (size_t)n == tls_want &&
+ if (s->tls_active && (size_t)n == tls_want &&
s->inbuflen < sizeof(s->inbuf))
event_active(&s->client_ev, EV_READ, 1);
}
-/* RFC 9051 SS5.4: a short timer before login, 30 minutes after */
+/* RFC 9051 section 5.4: a short timer before login, 30 minutes after */
#define SESSION_WRITE_PREAUTH_MS 5000
#define SESSION_WRITE_AUTH_MS (1800 * 1000)
event_active(&s->client_ev, EV_READ, 1);
}
-
/* on overflow the line still ends in CRLF */
static void session_writef(struct session *, const char *, ...)
__attribute__((__format__ (printf, 2, 3)));
session_writef(s, "%s %s %s\r\n", tag, status, text);
}
-
void
session_untagged(struct session *s, const char *text)
{
}
int
-send_mbox_request(struct session *s, int imsg_type, const char *what,
- const char *imsgname, const void *req, size_t reqlen, const void *elems,
- uint32_t nelems, size_t elemsize)
+send_mbox_request(struct session *s, int imsg_type, const char *imsgname,
+ const void *req, size_t reqlen, const void *elems, uint32_t nelems,
+ size_t elemsize)
{
- size_t bodylen = (size_t)nelems * elemsize;
- char *combined;
+ struct iovec iov[2];
- if (bodylen == 0) {
- if (imsg_compose(&s->store_iev->ibuf, imsg_type, 0, 0, -1,
- req, reqlen) == -1) {
- log_warn("session %u: imsg_compose %s", s->id,
- imsgname);
- return (0);
- }
- return (1);
+ iov[0].iov_base = (void *)req;
+ iov[0].iov_len = reqlen;
+ iov[1].iov_base = (void *)elems;
+ iov[1].iov_len = (size_t)nelems * elemsize;
+ if (imsg_composev(&s->store_iev->ibuf, imsg_type, 0, 0, -1, iov,
+ iov[1].iov_len > 0 ? 2 : 1) == -1) {
+ log_warn("session %u: imsg_composev %s", s->id, imsgname);
+ return (-1);
}
-
- if ((combined = malloc(reqlen + bodylen)) == NULL) {
- log_warn("session %u: malloc %s imsg buffer", s->id, what);
- return (0);
- }
- memcpy(combined, req, reqlen);
- memcpy(combined + reqlen, elems, bodylen);
-
- if (imsg_compose(&s->store_iev->ibuf, imsg_type, 0, 0, -1, combined,
- reqlen + bodylen) == -1) {
- log_warn("session %u: imsg_compose %s", s->id, imsgname);
- free(combined);
- return (0);
- }
- free(combined);
- return (1);
+ return (0);
}
void
return (1);
}
- /* RFC 4616 SS2: the initial response holds the password */
+ /* RFC 4616 section 2: the initial response holds the password */
if (name != NULL && (strcasecmp(name, "AUTHENTICATE") == 0 ||
strcasecmp(name, "LOGIN") == 0))
log_debug("session %u: <<< %s %s <redacted>", s->id, tag,
return (1);
}
- for (i = 0; i < NUM_IMAP_CMDS; i++) {
+ for (i = 0; i < nitems(imap_cmds); i++) {
if (strcasecmp(name, imap_cmds[i].name) == 0)
break;
}
- if (i == NUM_IMAP_CMDS) {
+ if (i == nitems(imap_cmds)) {
session_reply(s, tag, "BAD", "Unknown command");
return (1);
}
if (!(imap_cmds[i].states & (1U << s->state))) {
- /* RFC 9051 SS3 allows BAD or NO */
+ /* RFC 9051 section 3 allows BAD or NO */
session_reply(s, tag, "BAD",
"Command not permitted in this state");
return (1);
return (imap_cmds[i].handler(s, tag, args));
}
-
void
listener_dispatch_auth(int fd, short event, void *arg)
{
switch (imsg_get_type(&imsg)) {
case IMSG_AUTH_RESULT: {
struct imsg_auth_result res;
- struct session *s;
+ struct session *s = &sess;
if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) {
log_warnx("bad IMSG_AUTH_RESULT");
break;
}
- if ((s = session_find(res.session_id)) == NULL) {
- log_debug("IMSG_AUTH_RESULT for unknown "
- "session %u", res.session_id);
+ if (res.session_id != s->id) {
+ log_debug("IMSG_AUTH_RESULT for session %u, "
+ "not ours", res.session_id);
break;
}
if (!res.ok) {
imsg_free(&imsg);
}
imsgev_rearm_read(iev);
- (void)fd;
}
void
if ((n = imsgbuf_read(&iev->ibuf)) == -1)
fatal("imsgbuf_read");
if (n == 0) {
- struct session *s = TAILQ_FIRST(&sessions);
-
/* the parent is stopping us: end the session */
log_debug("listener-worker: parent closed "
"channel, shutting down");
- if (s != NULL)
- session_teardown(s, "shutdown");
- exit(0);
+ session_teardown(&sess, "shutdown");
}
}
switch (imsg_get_type(&imsg)) {
case IMSG_STORE_FORK: {
struct imsg_store_fork fail;
- struct session *s;
+ struct session *s = &sess;
if (imsg_get_data(&imsg, &fail, sizeof(fail)) == -1) {
log_warnx("bad IMSG_STORE_FORK reply");
break;
}
- if ((s = session_find(fail.session_id)) == NULL)
+ if (fail.session_id != s->id)
break;
+ /* RFC 9051 section 3.4: a BYE before the close */
if (fail.limit) {
+ session_untagged(s, "BYE [LIMIT] too many "
+ "sessions for this account");
session_reply(s, s->pending_tag, "NO",
"[LIMIT] too many sessions for this "
"account");
session_teardown(s, "limit-exceeded");
} else {
+ session_untagged(s, "BYE [UNAVAILABLE] "
+ "mailbox store unavailable");
session_reply(s, s->pending_tag, "NO",
- "authentication succeeded but mailbox "
- "store unavailable");
+ "[UNAVAILABLE] authentication "
+ "succeeded but mailbox store unavailable");
session_teardown(s, "io-error");
}
break;
}
case IMSG_SETUP_PEER: {
- uint32_t sess_id = imsg_get_id(&imsg);
- int store_fd = imsg_get_fd(&imsg);
- struct session *s;
+ struct session *s = &sess;
+ uint32_t sess_id;
+ int store_fd;
+ sess_id = imsg_get_id(&imsg);
+ store_fd = imsg_get_fd(&imsg);
if (store_fd == -1) {
log_warnx("IMSG_SETUP_PEER (store) carried "
"no fd");
break;
}
- if ((s = session_find(sess_id)) == NULL) {
+ if (sess_id != s->id) {
log_warnx("IMSG_SETUP_PEER (store) for "
- "unknown session %u", sess_id);
+ "session %u, not ours", sess_id);
close(store_fd);
break;
}
s->state = SESSION_AUTHENTICATED;
session_login_grace_disarm(s);
log_debug("session %u: store peer wired", sess_id);
- /* RFC 9051 SS6.2.2's PLAIN example text, verbatim. */
+ /* RFC 9051 section 6.2.2's PLAIN example, verbatim */
session_reply(s, s->pending_tag, "OK",
"Success (tls protection)");
break;
imsg_free(&imsg);
}
imsgev_rearm_read(iev);
- (void)fd;
}
-struct session *
-session_find(uint32_t id)
-{
- struct session *s;
- TAILQ_FOREACH(s, &sessions, entry) {
- if (s->id == id)
- return (s);
- }
- return (NULL);
-}
-
/* reason: one of eight fixed tokens, never NULL, never attacker text */
void
session_teardown(struct session *s, const char *reason)
{
+ struct timespec now;
+ const char *who = "-";
+ long long dur = -1;
+
if (s->store_iev != NULL) {
if (imsg_compose(&s->store_iev->ibuf, IMSG_STORE_SHUTDOWN,
0, 0, -1, NULL, 0) == -1)
event_del(&s->client_ev);
if (s->tls_ctx != NULL) {
- int ret = tls_close(s->tls_ctx);
+ int ret;
+ ret = tls_close(s->tls_ctx);
if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
close(s->client_fd);
/* debug: a missing close_notify is routine; httpd ignores it */
while (s->cmd_queue_n > 0)
free(s->cmd_queue[--s->cmd_queue_n]);
- TAILQ_REMOVE(&sessions, s, entry);
- {
- struct timespec now;
- const char *who = "-";
- long long dur = -1;
+ if (clock_gettime(CLOCK_MONOTONIC, &now) != -1 &&
+ s->connected_at.tv_sec != 0)
+ dur = (long long)(now.tv_sec - s->connected_at.tv_sec);
+ /* user= stays "-" until past authenticating; see listener.h */
+ if (s->user[0] != '\0' && s->state != SESSION_AUTHENTICATING)
+ who = s->user;
+ /* tls= is the final state, unlike the connect line's */
+ log_info("session %u: closed peer=%.200s user=%.64s reason=%s tls=%s "
+ "duration=%lld", s->id, s->remote_addr, who, reason,
+ s->tls_active ? "yes" : "no", dur);
- if (clock_gettime(CLOCK_MONOTONIC, &now) != -1 &&
- s->connected_at.tv_sec != 0)
- dur = (long long)(now.tv_sec -
- s->connected_at.tv_sec);
- /* user= stays "-" until past authenticating; see listener.h */
- if (s->user[0] != '\0' && s->state != SESSION_AUTHENTICATING)
- who = s->user;
- /* tls= is the final state, unlike the connect line's */
- log_info("session %u: closed peer=%.200s user=%.64s "
- "reason=%s tls=%s duration=%lld", s->id,
- s->remote_addr, who, reason,
- s->tls_active ? "yes" : "no", dur);
- }
-
/* inbuf may hold a base64 SASL response */
explicit_bzero(s, sizeof(*s));
- free(s);
log_debug("listener-worker: session closed, exiting");
exit(0);
blob - 14c2f93c66bdbc2b25143b61ec596f8d7db838fd
blob + 6b3f0d914c33d18b53a07299df430b5a8a5a5cac
--- src/listener.h
+++ src/listener.h
#define LISTENER_H
#include <sys/types.h>
-#include <sys/queue.h>
#include <event.h>
#include <stdint.h>
#include <time.h>
#include <tls.h>
+/*
+ * Returns: command handlers 1 while the session lives, 0 once torn down;
+ * parsers 0, -1 (BAD) or -2 (NO) with *errmsg set; the rest 0/-1 or 1/0.
+ */
+
enum session_state {
SESSION_NOT_AUTH,
SESSION_AUTHENTICATING,
SESSION_STATUSING,
SESSION_COPYING,
- /* RFC 9051 SS6.3.4-SS6.3.9; mbox_op_prev_state is restored after */
+ /* RFC 9051 sections 6.3.4-6.3.9; mbox_op_prev_state restored after */
SESSION_CREATING,
SESSION_DELETING,
SESSION_RENAMING,
/* RFC 9051 sets no limit; this bounds a client that never sends CRLF */
#define SESSION_INBUF_MAX 8192
-/* RFC 9051 SS9 sets no limit; a longer tag is truncated */
+/* RFC 9051 section 9 sets no limit; a longer tag is truncated */
#define IMAP_TAG_MAX 64
-/* pipelined lines (RFC 9051 SS5.5); past this the session is dropped */
+/* pipelined lines (RFC 9051 section 5.5); past this the session is dropped */
#define SESSION_CMD_QUEUE_MAX 8
#define HEADER_FIELDS_LABEL_MAX 288
/* two DQUOTEs, a NUL, and a backslash per byte */
#define MBOX_QUOTED_MAX ((2 * MBOX_NAME_MAX) + 3)
-/* RFC 7162 SS7: a mod-sequence is a positive 63-bit integer */
+/* RFC 7162 section 7: a mod-sequence is a positive 63-bit integer */
#define MODSEQ_MAX INT64_MAX
struct vanished_range {
/* inbuf held SASL credentials; explicit_bzero(3) it */
int scrub_inbuf;
int auth_cont;
- /* RFC 9051 SS6.3.13: the next line is DONE */
+ /* RFC 9051 section 6.3.13: the next line is DONE */
int idling;
- /* pipelined lines (RFC 9051 SS5.5), malloc(3)'d */
+ /* pipelined lines (RFC 9051 section 5.5), malloc(3)'d */
char *cmd_queue[SESSION_CMD_QUEUE_MAX];
uint32_t cmd_queue_n;
/* the tag points into inbuf, which the next read overwrites */
uint64_t pending_body_len;
int pending_body_found;
char pending_body_label[SECTION_PART_MAX];
- /* only the origin is echoed back (RFC 9051 SS6.4.5) */
+ /* only the origin is echoed back (RFC 9051 section 6.4.5) */
int pending_body_has_partial;
uint32_t pending_body_partial_origin;
char *pending_envelope_buf;
int pending_bodystructure_found;
char pending_bodystructure_label[16];
- /* tagged NO rather than OK (RFC 9051 SS6.4.5) */
+ /* tagged NO rather than OK (RFC 9051 section 6.4.5) */
int fetch_incomplete;
int close_after_expunge;
- /* RFC 9051 SS4.3 literal octets, checked before the CRLF search */
+ /* RFC 9051 section 4.3 literal octets, read before the CRLF search */
int literal_pending;
uint64_t literal_len; /* announced size, "{n}" */
uint64_t literal_remaining;
- /* RFC 7888 SS3: a refused literal's octets, then its command's line */
+ /* RFC 7888 section 3: a refused literal's octets, then its line */
uint64_t literal_discard;
int literal_skipline;
- /* RFC 9051 SS4.3: a command gathered with its literals */
+ /* RFC 9051 section 4.3: a command gathered with its literals */
char cmdbuf[SESSION_INBUF_MAX];
size_t cmdlen;
size_t cmd_octets;
int search_used_modseq;
uint64_t search_max_modseq;
- /* RFC 7162 SS3.1/SS3.2.3: sticky; QRESYNC implies CONDSTORE */
+ /* RFC 7162 sections 3.1, 3.2.3: sticky; QRESYNC implies CONDSTORE */
int condstore_enabled;
int qresync_enabled;
uint64_t mbox_highestmodseq;
- /* RFC 9051 SS6.3.3 EXAMINE */
+ /* RFC 9051 section 6.3.3 EXAMINE */
int mbox_readonly;
char selected_mailbox[MBOX_NAME_MAX];
- /* RFC 9051 SS6.3.13 IDLE; the store child keeps the UID list */
+ /* RFC 9051 section 6.3.13 IDLE; the store child keeps the UID list */
int idle_refresh_pending;
int idle_refresh_again;
/* the tagged reply IDLE or NOOP owes once the refresh is in */
struct event idle_ev;
struct event grace_ev;
- /* held until IMSG_MBOX_SELECTED (RFC 7162 SS3.2.6 ordering) */
+ /* held until IMSG_MBOX_SELECTED (RFC 7162 section 3.2.6 ordering) */
struct vanished_range *vanished_ranges;
uint32_t vanished_nranges;
uint32_t vanished_cap;
/* a dropped entry fails the SELECT: HIGHESTMODSEQ would hide it */
int qresync_alloc_failed;
- /* COPYUID precedes EXPUNGED (RFC 9051 SS6.4.8) */
+ /* COPYUID precedes EXPUNGED (RFC 9051 section 6.4.8) */
uint32_t *copy_src_uids;
uint32_t *copy_dest_uids;
uint32_t copy_n;
uint32_t move_expunged_n;
uint32_t move_expunged_cap;
- /* RFC 7162 SS3.1.3 MODIFIED */
+ /* RFC 7162 section 3.1.3 MODIFIED */
uint32_t *store_modified;
uint32_t store_modified_n;
uint32_t store_modified_cap;
/* MODIFIED must list every failure, so an incomplete set is refused */
int store_modified_alloc_failed;
- /* RFC 9051 SS6.4.9 UID <cmd> */
+ /* RFC 9051 section 6.4.9 UID <cmd> */
int cmd_by_uid;
-
- TAILQ_ENTRY(session) entry;
};
-/* named: declared and defined in different files */
-TAILQ_HEAD(session_list, session);
-
-extern struct session_list sessions;
extern struct imsgev iev_auth;
extern struct imsgev iev_parent;
extern struct tls *listener_tls_ctx;
-/* RFC 9051 SS7.1 INUSE, OVERQUOTA */
+/* RFC 9051 section 7.1 INUSE, OVERQUOTA */
#define IMAP_BUSY_TEXT "[INUSE] mailbox busy, try again"
#define IMAP_OVERQUOTA_TEXT "[OVERQUOTA] not enough free space"
int parse_sequence_set(const char *,
struct seq_range[SEQSET_MAX_RANGES], uint32_t *,
const char **);
-int parse_fetch_atts(char *, uint32_t *, int *, int *, char *,
- size_t, char *, size_t, int *, char *, size_t, int *,
- uint32_t *, uint32_t *, const char **);
+struct fetch_atts;
+int parse_fetch_atts(char *, struct fetch_atts *, const char **);
int parse_header_fields_att(const char *, int *, char *, size_t);
int section_part_valid(const char *);
int parse_partial_suffix(const char *, int *, uint32_t *,
struct imsg_mbox_result *);
void session_send_greeting(struct session *);
void session_teardown(struct session *, const char *);
-struct session *session_find(uint32_t);
/* RFC 7162 (CONDSTORE/QRESYNC) */
void session_condstore_enable(struct session *);
int parse_store_modifiers(char *, struct imsg_mbox_store *,
const char **);
-/* RFC 9051 SS6.4.9 (UID) */
+/* RFC 9051 section 6.4.9 (UID) */
int fetch_dispatch(struct session *, const char *, char *, int);
int store_do(struct session *, const char *, char *, int);
int search_dispatch(struct session *, const char *, char *, int);
void session_handle_fetch_vanished(struct session *,
const struct imsg_mbox_select_vanished *);
-/* RFC 9051 SS6.4.7/SS6.4.8 (COPY/MOVE) */
+/* RFC 9051 section 6.4.7/section 6.4.8 (COPY/MOVE) */
int copy_move_dispatch(struct session *, const char *, char *,
int, int);
int listener_mailbox_name_valid(const char *);
void session_reply(struct session *, const char *, const char *,
const char *);
void session_untagged(struct session *, const char *);
-int send_mbox_request(struct session *, int, const char *, const char *,
- const void *, size_t, const void *, uint32_t, size_t);
+int send_mbox_request(struct session *, int, const char *, const void *,
+ size_t, const void *, uint32_t, size_t);
int parse_command_line(char *, char **, char **, char **);
int session_handle_line(struct session *, char *);
int session_dequeue_next(struct session *);
int cmd_starttls(struct session *, const char *, char *);
int cmd_authenticate(struct session *, const char *, char *);
-/* command-auth (RFC 9051 SS6.3) */
+/* command-auth (RFC 9051 section 6.3) */
int stub_not_implemented(struct session *, const char *,
const char *);
int cmd_enable(struct session *, const char *, char *);
int cmd_append(struct session *, const char *, char *);
int cmd_idle(struct session *, const char *, char *);
-/* command-select (RFC 9051 SS6.4, valid only in Selected state). */
+/* command-select (RFC 9051 section 6.4, valid only in Selected state). */
int cmd_close(struct session *, const char *, char *);
int cmd_unselect(struct session *, const char *, char *);
int cmd_expunge(struct session *, const char *, char *);
blob - b2dad4bfd9b2765481ed2214adabfe90e4b5da87
blob + efaf76ace0f31de26d972dafffccbaabf3f683a4
--- src/log.c
+++ src/log.c
* Copyright (c) 2026 David Williams <dhw@openimapd.dev>
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
*
- * This file's log_init()/log_procinit()/log_setverbose()/log_getverbose()/
- * vlog()/logit()/log_warn()/log_warnx()/log_info()/log_debug()/fatal()/
- * fatalx() API is the same daemon-logging idiom Henning Brauer wrote and
- * that is carried, nearly verbatim, across essentially every privsep
- * daemon in the OpenBSD base system (smtpd, bgpd, and many others all
- * credit him in their own log.c). This file's implementation details
- * (log_procname storage, log_init()'s parameters) are this project's own,
- * but the API shape and name are that same shared idiom, so his copyright
- * is carried forward here too, same rationale as parse.y's copyright
- * chain.
- *
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
blob - f315b680566be25f414789461f75694af4c4c95e
blob + 5a9fbc87de9ea457a108331001578181b316af06
--- src/log.h
+++ src/log.h
* Copyright (c) 2026 David Williams <dhw@openimapd.dev>
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
*
- * This header's call signatures were deliberately matched to smtpd's own
- * log.h (see the comment below), that log_init()/fatal()/fatalx()/
- * log_warn()/log_debug() API is the same shared daemon-logging idiom
- * Henning Brauer wrote and that smtpd, bgpd, and most other privsep
- * daemons in OpenBSD base carry his copyright for. Same rationale as
- * log.c's copyright chain.
- *
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
blob - 7e8374d788e2868027027f32d27f31f14949c84f
blob + 8046d4740bbff21de11286d3b7566844c593d26d
--- src/mailbox_cmd.c
+++ src/mailbox_cmd.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
struct seq_range ranges[SEQSET_MAX_RANGES], uint32_t *nranges,
const char **errmsg)
{
- char *p;
- const char *tok;
- uint32_t uidvalidity;
+ char *p, *end;
+ const char *tok, *errstr;
+ uint32_t uidvalidity, i;
uint64_t modseq;
- char *ep;
+ int depth;
p = inner;
while (*p == ' ')
*errmsg = "QRESYNC requires uidvalidity and mod-sequence";
return (-1);
}
- /* RFC 7162: 1..2^63-1; strtoull(3) accepts a sign */
if (*tok < '0' || *tok > '9') {
*errmsg = "invalid QRESYNC mod-sequence";
return (-1);
}
- errno = 0;
- modseq = strtoull(tok, &ep, 10);
- if (*ep != '\0' || errno != 0 || modseq == 0 ||
- modseq > MODSEQ_MAX) {
+ modseq = strtonum(tok, 1, MODSEQ_MAX, &errstr);
+ if (errstr != NULL) {
*errmsg = "invalid QRESYNC mod-sequence";
return (-1);
}
*p = '\0';
p++;
}
- {
- uint32_t i;
-
- /* RFC 7162 SS3.2.5.1: "*" is not allowed */
- if (parse_sequence_set(tok, ranges, nranges, errmsg) == -1)
+ /* RFC 7162 section 3.2.5.1: "*" is not allowed */
+ if (parse_sequence_set(tok, ranges, nranges, errmsg) == -1)
+ return (-1);
+ for (i = 0; i < *nranges; i++) {
+ if (ranges[i].lo_is_star || ranges[i].hi_is_star) {
+ *errmsg = "invalid known-uids, '*' is not "
+ "allowed here";
return (-1);
- for (i = 0; i < *nranges; i++) {
- if (ranges[i].lo_is_star || ranges[i].hi_is_star) {
- *errmsg = "invalid known-uids, '*' is not "
- "allowed here (RFC 7162 SS3.2.5.1)";
- return (-1);
- }
}
- req->qresync_has_uids = 1;
- req->qresync_nranges = *nranges;
}
+ req->qresync_has_uids = 1;
+ req->qresync_nranges = *nranges;
while (*p == ' ')
p++;
*errmsg = "expected seq-match-data";
return (-1);
}
- {
- char *end = p;
- int depth = 0;
-
- for (;;) {
- if (*end == '(')
- depth++;
- else if (*end == ')') {
- depth--;
- if (depth == 0)
- break;
- } else if (*end == '\0') {
- *errmsg = "unterminated seq-match-data";
- return (-1);
- }
- end++;
+ depth = 0;
+ for (end = p;; end++) {
+ if (*end == '(')
+ depth++;
+ else if (*end == ')') {
+ if (--depth == 0)
+ break;
+ } else if (*end == '\0') {
+ *errmsg = "unterminated seq-match-data";
+ return (-1);
}
- p = end + 1;
}
+ p = end + 1;
while (*p == ' ')
p++;
return (-1);
}
if (!s->qresync_enabled) {
- *errmsg = "QRESYNC select-param requires "
- "ENABLE QRESYNC first (RFC 7162 SS3.2.5)";
+ *errmsg = "QRESYNC requires ENABLE QRESYNC "
+ "first";
return (-1);
}
return (-1);
req->qresync = 1;
- /* RFC 7162 SS3.2.3: QRESYNC implies CONDSTORE */
+ /* RFC 7162 section 3.2.3: QRESYNC implies CONDSTORE */
*want_condstore = 1;
p = end + 1;
continue;
return (0);
}
-/* RFC 9051 SS6.3.2/SS6.3.3 */
+/* RFC 9051 section 6.3.2/section 6.3.3 */
static int
select_or_examine(struct session *s, const char *tag, char *args, int readonly)
{
p++;
params = (*p != '\0') ? p : NULL;
- /* "" parses as RFC 9051 SS9 quoted, so this is NO, not BAD */
+ /* "" parses as RFC 9051 section 9 quoted, so this is NO, not BAD */
if (mailbox[0] == '\0') {
session_reply(s, tag, "NO", "[NONEXISTENT] no such mailbox");
return (1);
req.readonly = readonly;
if (params != NULL) {
- size_t plen = strlen(params);
+ size_t plen;
+ plen = strlen(params);
if (plen < 2 || params[0] != '(' || params[plen - 1] != ')') {
session_reply(s, tag, "BAD",
"malformed select-param list");
}
}
- /* RFC 7162 SS3.1.8/SS3.2.3 */
+ /* RFC 7162 section 3.1.8/section 3.2.3 */
if (want_condstore)
s->condstore_enabled = 1;
- /* RFC 9051 SS6.3.2: SELECT auto-deselects the current mailbox. */
+ /* RFC 9051 section 6.3.2: SELECT auto-deselects the current mailbox. */
if (s->state == SESSION_SELECTED)
session_untagged(s,
"OK [CLOSED] Previous mailbox is now closed");
s->state = SESSION_SELECTING;
s->mbox_readonly = readonly;
- if (!send_mbox_request(s, IMSG_MBOX_SELECT, cmdname, "IMSG_MBOX_SELECT",
- &req, sizeof(req), ranges, nranges, sizeof(struct seq_range))) {
+ if (send_mbox_request(s, IMSG_MBOX_SELECT, "IMSG_MBOX_SELECT", &req,
+ sizeof(req), ranges, nranges, sizeof(struct seq_range)) == -1) {
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
s->state = SESSION_AUTHENTICATED;
return (1);
return (1);
}
-
int
cmd_select(struct session *s, const char *tag, char *args)
{
- return select_or_examine(s, tag, args, 0);
+ return (select_or_examine(s, tag, args, 0));
}
-
int
cmd_examine(struct session *s, const char *tag, char *args)
{
- return select_or_examine(s, tag, args, 1);
+ return (select_or_examine(s, tag, args, 1));
}
/* checked here to save a round trip; the store checks again */
if (utf8_mailbox_ok(name))
return (0);
- log_debug("session %u: mailbox name is not valid UTF-8, refusing "
- "(RFC 9051 SS5.1)", s->id);
+ log_debug("session %u: mailbox name not valid UTF-8", s->id);
session_reply(s, tag, "NO",
"[CANNOT] mailbox name is not valid UTF-8");
return (1);
}
-/* RFC 9051 SS6.3.4 CREATE */
+/* RFC 9051 section 6.3.4 CREATE */
int
cmd_create(struct session *s, const char *tag, char *args)
{
}
if (listener_reject_bad_utf8(s, tag, mailbox))
return (1);
- /* a name the server refuses is RFC 5530 SS3 NO [CANNOT], not BAD */
+ /* a refused name is RFC 5530 section 3 NO [CANNOT], not BAD */
if (!listener_mailbox_name_valid(mailbox)) {
session_reply(s, tag, "NO", "[CANNOT] invalid mailbox name");
return (1);
s->state = SESSION_CREATING;
/* a failed compose must not leave the session busy */
- if (!send_mbox_request(s, IMSG_MBOX_CREATE, "CREATE",
- "IMSG_MBOX_CREATE", &req, sizeof(req), NULL, 0, 0)) {
+ if (send_mbox_request(s, IMSG_MBOX_CREATE,
+ "IMSG_MBOX_CREATE", &req, sizeof(req), NULL, 0, 0) == -1) {
s->state = s->mbox_op_prev_state;
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
return (1);
return (1);
}
-/* RFC 9051 SS6.3.5 DELETE */
+/* RFC 9051 section 6.3.5 DELETE */
int
cmd_delete(struct session *s, const char *tag, char *args)
{
return (1);
}
- /* no arguments may follow the name */
while (*p == ' ')
p++;
if (*p != '\0') {
s->mbox_op_prev_state = s->state;
s->state = SESSION_DELETING;
- if (!send_mbox_request(s, IMSG_MBOX_DELETE, "DELETE",
- "IMSG_MBOX_DELETE", &req, sizeof(req), NULL, 0, 0)) {
+ if (send_mbox_request(s, IMSG_MBOX_DELETE,
+ "IMSG_MBOX_DELETE", &req, sizeof(req), NULL, 0, 0) == -1) {
s->state = s->mbox_op_prev_state;
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
return (1);
return (1);
}
-/* RFC 9051 SS6.3.6 RENAME */
+/* RFC 9051 section 6.3.6 RENAME */
int
cmd_rename(struct session *s, const char *tag, char *args)
{
return (session_arg_error(s, tag, errmsg));
if (mailbox_name_is_inbox(oldname)) {
- /* RFC 9051 SS6.3.6 allows refusing this */
+ /* RFC 9051 section 6.3.6 allows refusing this */
session_reply(s, tag, "NO", "[CANNOT] cannot rename INBOX");
return (1);
}
}
if (listener_reject_bad_utf8(s, tag, newname))
return (1);
- /* a name the server refuses is RFC 5530 SS3 NO [CANNOT], not BAD */
if (mailbox_name_is_inbox(newname) ||
!listener_mailbox_name_valid(newname)) {
session_reply(s, tag, "NO", "[CANNOT] invalid mailbox name");
return (1);
}
- /* no arguments may follow the name */
while (*p == ' ')
p++;
if (*p != '\0') {
s->mbox_op_prev_state = s->state;
s->state = SESSION_RENAMING;
- if (!send_mbox_request(s, IMSG_MBOX_RENAME, "RENAME",
- "IMSG_MBOX_RENAME", &req, sizeof(req), NULL, 0, 0)) {
+ if (send_mbox_request(s, IMSG_MBOX_RENAME,
+ "IMSG_MBOX_RENAME", &req, sizeof(req), NULL, 0, 0) == -1) {
s->state = s->mbox_op_prev_state;
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
return (1);
return (1);
}
-
-/* RFC 9051 SS6.3.7 SUBSCRIBE and SS6.3.8 UNSUBSCRIBE */
+/* RFC 9051 section 6.3.7 SUBSCRIBE and section 6.3.8 UNSUBSCRIBE */
static int
subscribe_dispatch(struct session *s, const char *tag, char *args,
int is_unsub)
if (listener_reject_bad_utf8(s, tag, mailbox))
return (1);
- /* RFC 9051 SS5.1: INBOX is always subscribed */
+ /* RFC 9051 section 5.1: INBOX is always subscribed */
if (mailbox_name_is_inbox(mailbox)) {
if (is_unsub) {
session_reply(s, tag, "NO",
session_reply(s, tag, "OK", "SUBSCRIBE completed");
return (1);
}
- /* a name the server refuses is RFC 5530 SS3 NO [CANNOT], not BAD */
if (!listener_mailbox_name_valid(mailbox)) {
session_reply(s, tag, "NO", "[CANNOT] invalid mailbox name");
return (1);
s->mbox_op_prev_state = s->state;
s->state = is_unsub ? SESSION_UNSUBSCRIBING : SESSION_SUBSCRIBING;
- if (!send_mbox_request(s, is_unsub ? IMSG_MBOX_UNSUBSCRIBE :
- IMSG_MBOX_SUBSCRIBE, cmdname, imsgname, &req, sizeof(req), NULL,
- 0, 0)) {
+ if (send_mbox_request(s, is_unsub ? IMSG_MBOX_UNSUBSCRIBE :
+ IMSG_MBOX_SUBSCRIBE, imsgname, &req, sizeof(req), NULL,
+ 0, 0) == -1) {
s->state = s->mbox_op_prev_state;
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
return (1);
return (1);
}
-
int
cmd_subscribe(struct session *s, const char *tag, char *args)
{
- return subscribe_dispatch(s, tag, args, 0);
+ return (subscribe_dispatch(s, tag, args, 0));
}
-
int
cmd_unsubscribe(struct session *s, const char *tag, char *args)
{
- return subscribe_dispatch(s, tag, args, 1);
+ return (subscribe_dispatch(s, tag, args, 1));
}
-/* RFC 9051 SS6.3.9 wildcards; a flat namespace */
+/* RFC 9051 section 6.3.9 wildcards; a flat namespace */
int
list_pattern_match(const char *pat, const char *name, int ci)
{
return (*p == '\0');
}
-/* RFC 9051 SS4.3 quoted string */
+/* RFC 9051 section 4.3 quoted string */
int
quote_mailbox(char *out, size_t outsize, const char *name)
{
char c = name[i];
size_t need;
- /* RFC 9051 SS4.3: no NUL, CR or LF */
+ /* RFC 9051 section 4.3: no NUL, CR or LF */
if (c == '\r' || c == '\n')
c = ' ';
return (1);
}
-/* RFC 9051 SS9 astring, or list-mailbox with wildcards */
+/* RFC 9051 section 9 astring, or list-mailbox with wildcards */
static int
parse_mailbox_arg(char **pp, char *out, size_t outsize, int wildcards,
const char **errmsg)
break;
}
if (*p == '\\') {
- /* RFC 9051 SS9 QUOTED-CHAR */
+ /* RFC 9051 section 9 QUOTED-CHAR */
p++;
if (*p == '\0') {
/* a trailing backslash */
return (parse_mailbox_arg(pp, out, outsize, 1, errmsg));
}
-/* RFC 9051 SS6.3.9 basic syntax; LSUB varies only the output */
+/* RFC 9051 section 6.3.9 basic syntax; LSUB varies only the output */
static int
list_dispatch(struct session *s, const char *tag, char *args, int is_lsub)
{
struct imsg_mbox_list req;
- char reference[MBOX_NAME_MAX];
- char pattern[MBOX_NAME_MAX];
- char canon[2 * MBOX_NAME_MAX];
- char *p;
- const char *errmsg;
- const char *cmdname = is_lsub ? "LSUB" : "LIST";
- const char *kw = is_lsub ? "LSUB" : "LIST";
- char text[64];
- int subscribed_only = 0;
+ char reference[MBOX_NAME_MAX];
+ char pattern[MBOX_NAME_MAX];
+ char canon[2 * MBOX_NAME_MAX];
+ char text[64];
+ char *p;
+ const char *errmsg;
+ const char *cmdname = is_lsub ? "LSUB" : "LIST";
+ size_t n;
+ int subscribed_only = 0;
if (args == NULL) {
snprintf(text, sizeof(text), "%s requires two arguments",
p++;
if (*p == '(') {
- /* RFC 9051 SS6.3.9 condition 1: list-select-opts */
+ /* RFC 9051 section 6.3.9 condition 1: list-select-opts */
if (is_lsub) {
session_reply(s, tag, "BAD",
"LSUB takes no selection options");
p++;
if (*p == '(') {
- /* RFC 9051 SS6.3.9 condition 2 */
+ /* RFC 9051 section 6.3.9 condition 2 */
snprintf(text, sizeof(text),
"extended %s mailbox-pattern lists not supported",
cmdname);
while (*p == ' ')
p++;
if (*p != '\0') {
- /* RFC 9051 SS6.3.9 condition 3 */
+ /* RFC 9051 section 6.3.9 condition 3 */
snprintf(text, sizeof(text),
"extended %s return options not supported", cmdname);
session_reply(s, tag, "NO", text);
return (1);
}
- /* RFC 9051 SS6.3.9: an empty pattern asks for the delimiter */
+ /* RFC 9051 section 6.3.9: an empty pattern asks for the delimiter */
if (pattern[0] == '\0') {
- snprintf(text, sizeof(text), "%s (\\Noselect) \"/\" \"\"", kw);
+ snprintf(text, sizeof(text), "%s (\\Noselect) \"/\" \"\"",
+ cmdname);
session_untagged(s, text);
snprintf(text, sizeof(text), "%s completed", cmdname);
session_reply(s, tag, "OK", text);
return (1);
}
- {
- size_t n;
-
- n = strlcpy(canon, reference, sizeof(canon));
- if (n < sizeof(canon))
- n = strlcat(canon, pattern, sizeof(canon));
- if (n >= sizeof(canon)) {
- session_reply(s, tag, "BAD",
- "combined reference and pattern too long");
- return (1);
- }
+ n = strlcpy(canon, reference, sizeof(canon));
+ if (n < sizeof(canon))
+ n = strlcat(canon, pattern, sizeof(canon));
+ if (n >= sizeof(canon)) {
+ session_reply(s, tag, "BAD",
+ "combined reference and pattern too long");
+ return (1);
}
- /* RFC 9051 SS6.3.9: INBOX is answered here */
+ /* RFC 9051 section 6.3.9: INBOX is answered here */
if (list_pattern_match(canon, "INBOX", 1)) {
- /* RFC 9051 SS7.3.1: attributes are optional */
- snprintf(text, sizeof(text), "%s (%s) \"/\" \"INBOX\"", kw,
+ /* RFC 9051 section 7.3.1: attributes are optional */
+ snprintf(text, sizeof(text), "%s (%s) \"/\" \"INBOX\"", cmdname,
subscribed_only ? "\\Subscribed" : "");
session_untagged(s, text);
}
return (1);
}
s->list_is_lsub = is_lsub;
- /* LSUB lists subscribed names (RFC 3501 SS6.3.9) */
+ /* LSUB lists subscribed names (RFC 3501 section 6.3.9) */
s->list_subscribed_only = subscribed_only || is_lsub;
s->mbox_op_prev_state = s->state;
s->state = SESSION_LISTING;
memset(&req, 0, sizeof(req));
req.subscribed_only = s->list_subscribed_only;
- if (!send_mbox_request(s, IMSG_MBOX_LIST, cmdname, "IMSG_MBOX_LIST",
- &req, sizeof(req), NULL, 0, 0)) {
+ if (send_mbox_request(s, IMSG_MBOX_LIST, "IMSG_MBOX_LIST",
+ &req, sizeof(req), NULL, 0, 0) == -1) {
s->state = s->mbox_op_prev_state;
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
return (1);
return (1);
}
-
int
cmd_list(struct session *s, const char *tag, char *args)
{
- return list_dispatch(s, tag, args, 0);
+ return (list_dispatch(s, tag, args, 0));
}
-
int
cmd_lsub(struct session *s, const char *tag, char *args)
{
- return list_dispatch(s, tag, args, 1);
+ return (list_dispatch(s, tag, args, 1));
}
-/* RFC 9051 SS6.3.10 NAMESPACE */
+/* RFC 9051 section 6.3.10 NAMESPACE */
int
cmd_namespace(struct session *s, const char *tag, char *args)
{
- (void)args;
session_untagged(s, "NAMESPACE ((\"\" \"/\")) NIL NIL");
session_reply(s, tag, "OK", "NAMESPACE command completed");
return (1);
}
-/* RFC 9051 SS6.3.11 STATUS */
+/* RFC 9051 section 6.3.11 STATUS */
int
cmd_status(struct session *s, const char *tag, char *args)
{
else if (strcasecmp(tok, "HIGHESTMODSEQ") == 0)
attrs |= STATUS_ATT_HIGHESTMODSEQ;
else if (strcasecmp(tok, "RECENT") == 0)
- /* RFC 9051 SS2.3.2 dropped it; clients ask */
+ /* RFC 9051 section 2.3.2 dropped it; clients ask */
attrs |= STATUS_ATT_RECENT;
else {
session_reply(s, tag, "BAD", "unknown status-att");
}
}
- /* RFC 9051 SS9: at least one status-att */
+ /* RFC 9051 section 9: at least one status-att */
if (attrs == 0) {
session_reply(s, tag, "BAD",
"STATUS requires at least one status-att");
return (1);
}
- /* RFC 7162 SS3.1: STATUS HIGHESTMODSEQ enables CONDSTORE */
+ /* RFC 7162 section 3.1: STATUS HIGHESTMODSEQ enables CONDSTORE */
if (attrs & STATUS_ATT_HIGHESTMODSEQ)
session_condstore_enable(s);
s->status_prev_state = s->state;
s->state = SESSION_STATUSING;
- if (!send_mbox_request(s, IMSG_MBOX_STATUS, "STATUS",
- "IMSG_MBOX_STATUS", &req, sizeof(req), NULL, 0, 0)) {
+ if (send_mbox_request(s, IMSG_MBOX_STATUS,
+ "IMSG_MBOX_STATUS", &req, sizeof(req), NULL, 0, 0) == -1) {
s->state = s->status_prev_state;
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
return (1);
blob - 328b47f988740d0a5fe9f0f6576e7df5ddcd1803
blob + 5b09cc8f6e152a8199588a67814172051a4eb00c
--- src/main.c
+++ src/main.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <sys/param.h>
#include <sys/types.h>
#include <err.h>
#include "imapd.h"
#include "log.h"
-__dead static void usage(void);
+static __dead void usage(void);
static const struct {
const char *name;
}
}
-__dead static void
+static __dead void
usage(void)
{
fprintf(stderr,
- "usage: %s [-dVv] [-D macro=value] [-f file]\n",
+ "usage: %s [-dnVv] [-D macro=value] [-f file]\n",
getprogname());
exit(1);
}
main(int argc, char *argv[])
{
int ch;
- int debug = 0, verbose = 0;
+ int debug = 0, noaction = 0, verbose = 0;
const char *conffile = "/etc/imapd.conf";
const char *rolearg = NULL;
enum openimap_proc_type role = PROC_PARENT;
/* before any fork(2), so every role inherits SIG_IGN */
signal(SIGPIPE, SIG_IGN);
- while ((ch = getopt(argc, argv, "D:df:Vvx:")) != -1) {
+ while ((ch = getopt(argc, argv, "D:df:nVvx:")) != -1) {
switch (ch) {
case 'V':
printf("%s %s\n", getprogname(), IMAPD_VERSION);
case 'f':
conffile = optarg;
break;
+ case 'n':
+ noaction = 1;
+ break;
case 'v':
- verbose = 1;
+ verbose++;
break;
case 'x':
rolearg = optarg;
}
if (rolearg != NULL) {
- for (i = 0; i < sizeof(proc_table) / sizeof(proc_table[0]);
- i++) {
+ for (i = 0; i < nitems(proc_table); i++) {
if (strcmp(rolearg, proc_table[i].name) == 0) {
role = proc_table[i].type;
break;
}
}
- if (i == sizeof(proc_table) / sizeof(proc_table[0]))
+ if (i == nitems(proc_table))
usage();
}
switch (role) {
case PROC_PARENT:
- /* before config_load(): imapd.conf is root-only */
+ if (config_load(conffile, &conf) == -1)
+ fatalx("config_load: %s", conffile);
+ if (noaction) {
+ fprintf(stderr, "configuration OK\n");
+ return (0);
+ }
if (geteuid() != 0)
fatalx("parent must start as root (running as "
"uid %u)", (unsigned int)geteuid());
fatalx("unknown user %s", IMAPD_AUTH_USER);
if (getpwnam(IMAPD_KEY_USER) == NULL)
fatalx("unknown user %s", IMAPD_KEY_USER);
- if (config_load(conffile, &conf) == -1)
- fatalx("config_load: %s", conffile);
parent_main(conffile, argc, argv, &conf);
case PROC_LISTENER:
listener_main();
blob - 00ffff1b63663f3599aa5c68bef8d0360370412d
blob + 3097d348370917895540dacc8eb070368430406e
--- src/mbox_copy.c
+++ src/mbox_copy.c
if (locate_message_file(&ss->cur_snap, ss->mailbox_dir_fd,
rec.basename, &size, suffix, sizeof(suffix)) == -1) {
log_warnx("session %u: COPY: message %s indexed but "
- "missing on disk, failing whole COPY (partial "
- "copy not permitted, RFC 9051 SS6.4.7)",
+ "missing on disk, COPY failed",
session_id, rec.basename);
goto fail;
}
goto fail;
}
if (!index_basename_valid(cs.basename)) {
- log_warnx("session %u: COPY: generated basename is "
- "unsafe for the index, failing COPY", session_id);
+ log_warnx("session %u: COPY: unsafe generated basename",
+ session_id);
goto fail;
}
if (nstaged == stagedcap) {
size_t newcap = (stagedcap == 0) ? 8 :
stagedcap * 2;
- struct copy_staged *newstaged = reallocarray(staged,
- newcap, sizeof(*staged));
+ struct copy_staged *newstaged;
+ newstaged = reallocarray(staged, newcap,
+ sizeof(*staged));
if (newstaged == NULL) {
log_warn("session %u: COPY: reallocarray "
"staged", session_id);
if (!index_basename_valid(staged[i].basename) ||
!index_field_valid(staged[i].keywords)) {
log_warnx("session %u: COPY: unsafe field in staged "
- "message, refusing to update the index",
- session_id);
+ "message", session_id);
goto rollback;
}
return (1);
rollback:
- /* RFC 9051 SS6.4.7: no partial copy */
+ /* RFC 9051 section 6.4.7: no partial copy */
while (ncommitted > 0) {
char curpath[320];
char letters[8];
return (0);
}
+/* a COPY or MOVE's two mailboxes, the selected one and the destination */
+struct copy_move {
+ struct mbox_index idx_a, idx_b;
+ struct mbox_index *srcidx, *destidx;
+ struct index_lock il_a, il_b;
+ struct imsg_mbox_result result;
+ char desttarget[MBOX_NAME_MAX];
+ int destfd;
+ int cross_mailbox;
+};
+
+/* 1 with both indexes locked and loaded; 2 lock busy; 0 failed, result set */
static int
-lock_copy_move_mailboxes(struct store_session *ss, const char *what,
- const char *destname, int *destfd_out, struct mbox_index *idx_a,
- struct mbox_index *idx_b, struct mbox_index **srcidx_out,
- struct mbox_index **destidx_out, char *desttarget, size_t desttargetlen,
- int *cross_mailbox_out, struct index_lock *il_a, struct index_lock *il_b,
- struct imsg_mbox_result *result)
+copy_move_lock(struct store_session *ss, const char *what,
+ const char *destname, struct copy_move *cm)
{
- int firstfd, secondfd, first_is_dest, locked;
+ static const struct index_lock il_init = INDEX_LOCK_INIT;
+ int firstfd, secondfd, first_is_dest;
+ int locked;
- if (resolve_mailbox_target(destname, desttarget, desttargetlen) ==
- -1) {
+ memset(cm, 0, sizeof(*cm));
+ cm->il_a = il_init;
+ cm->il_b = il_init;
+ cm->destfd = -1;
+
+ if (resolve_mailbox_target(destname, cm->desttarget,
+ sizeof(cm->desttarget)) == -1) {
log_debug("session %u: %s %s: invalid destination mailbox "
"name", session_id, what, destname);
- result->error = MBOX_OP_ERR_NO_SUCH_MAILBOX;
+ cm->result.error = MBOX_OP_ERR_NO_SUCH_MAILBOX;
return (0);
}
- *cross_mailbox_out = (strcmp(ss->selected_mailbox, desttarget) != 0);
+ cm->cross_mailbox = (strcmp(ss->selected_mailbox, cm->desttarget) != 0);
- if (!*cross_mailbox_out) {
- locked = index_lock_acquire(ss->mailbox_dir_fd, il_a,
+ if (!cm->cross_mailbox) {
+ locked = index_lock_acquire(ss->mailbox_dir_fd, &cm->il_a,
LOCK_EX | LOCK_NB);
if (locked == 1)
return (2);
if (locked == -1)
return (0);
- if (index_load(il_a->fd, idx_a) == -1)
+ if (index_load(cm->il_a.fd, &cm->idx_a) == -1)
return (0);
- *srcidx_out = idx_a;
- *destidx_out = idx_a;
+ cm->srcidx = &cm->idx_a;
+ cm->destidx = &cm->idx_a;
return (1);
}
- if ((*destfd_out = mailbox_open_dir(desttarget)) == -1) {
+ if ((cm->destfd = mailbox_open_dir(cm->desttarget)) == -1) {
log_debug("session %u: %s: no such mailbox %s", session_id,
- what, desttarget);
- result->error = MBOX_OP_ERR_NO_SUCH_MAILBOX;
+ what, cm->desttarget);
+ cm->result.error = MBOX_OP_ERR_NO_SUCH_MAILBOX;
return (0);
}
/* both locked at once: take them in name order, against AB-BA */
- first_is_dest = strcmp(ss->selected_mailbox, desttarget) > 0;
- firstfd = first_is_dest ? *destfd_out : ss->mailbox_dir_fd;
- secondfd = first_is_dest ? ss->mailbox_dir_fd : *destfd_out;
+ first_is_dest = strcmp(ss->selected_mailbox, cm->desttarget) > 0;
+ firstfd = first_is_dest ? cm->destfd : ss->mailbox_dir_fd;
+ secondfd = first_is_dest ? ss->mailbox_dir_fd : cm->destfd;
- locked = index_lock_acquire(firstfd, il_a, LOCK_EX | LOCK_NB);
+ locked = index_lock_acquire(firstfd, &cm->il_a, LOCK_EX | LOCK_NB);
if (locked != 0)
goto fail;
- if (index_load(il_a->fd, idx_a) == -1)
+ if (index_load(cm->il_a.fd, &cm->idx_a) == -1)
goto fail;
- locked = index_lock_acquire(secondfd, il_b, LOCK_EX | LOCK_NB);
+ locked = index_lock_acquire(secondfd, &cm->il_b, LOCK_EX | LOCK_NB);
if (locked != 0)
goto fail;
- if (index_load(il_b->fd, idx_b) == -1)
+ if (index_load(cm->il_b.fd, &cm->idx_b) == -1)
goto fail;
- *srcidx_out = first_is_dest ? idx_b : idx_a;
- *destidx_out = first_is_dest ? idx_a : idx_b;
+ cm->srcidx = first_is_dest ? &cm->idx_b : &cm->idx_a;
+ cm->destidx = first_is_dest ? &cm->idx_a : &cm->idx_b;
return (1);
fail:
- close(*destfd_out);
- *destfd_out = -1;
+ close(cm->destfd);
+ cm->destfd = -1;
if (locked == 1) {
/* busy: the first lock goes too, so a re-run starts clean */
- index_lock_release(il_a);
- index_free(idx_a);
- memset(idx_a, 0, sizeof(*idx_a));
+ index_lock_release(&cm->il_a);
+ index_free(&cm->idx_a);
+ memset(&cm->idx_a, 0, sizeof(cm->idx_a));
return (2);
}
return (0);
}
static void
-finish_copy_move(struct mbox_index *idx_a, struct mbox_index *idx_b,
- struct index_lock *il_a, struct index_lock *il_b, int ok,
- struct imsg_mbox_result *result, struct imsgev *iev)
+copy_move_finish(struct copy_move *cm, int ok, struct imsgev *iev)
{
- index_lock_release(il_a);
- index_lock_release(il_b);
+ if (cm->destfd != -1)
+ close(cm->destfd);
+ index_lock_release(&cm->il_a);
+ index_lock_release(&cm->il_b);
- if (result->error != MBOX_OP_ERR_NO_SUCH_MAILBOX &&
- result->error != MBOX_OP_ERR_LIMIT &&
- result->error != MBOX_OP_ERR_OVERQUOTA)
- result->error = ok ? MBOX_OP_OK : MBOX_OP_ERR_GENERIC;
- index_free(idx_a);
- index_free(idx_b);
+ if (cm->result.error != MBOX_OP_ERR_NO_SUCH_MAILBOX &&
+ cm->result.error != MBOX_OP_ERR_LIMIT &&
+ cm->result.error != MBOX_OP_ERR_OVERQUOTA)
+ cm->result.error = ok ? MBOX_OP_OK : MBOX_OP_ERR_GENERIC;
+ index_free(&cm->idx_a);
+ index_free(&cm->idx_b);
- if (imsg_compose(&iev->ibuf, IMSG_MBOX_RESULT, 0, 0, -1, result,
- sizeof(*result)) == -1)
+ if (imsg_compose(&iev->ibuf, IMSG_MBOX_RESULT, 0, 0, -1, &cm->result,
+ sizeof(cm->result)) == -1)
log_warn("session %u: imsg_compose IMSG_MBOX_RESULT",
session_id);
}
-/* RFC 9051 SS6.4.7 COPY */
-
+/* RFC 9051 section 6.4.7 COPY */
int
handle_mbox_copy(struct imsg_mbox_copy *req, const struct seq_range *ranges,
uint32_t nranges, struct store_session *ss)
{
- struct imsgev *iev = &ss->iev;
- struct mbox_index idx_a, idx_b;
- struct mbox_index *srcidx = NULL, *destidx = NULL;
- struct imsg_mbox_result result;
- struct copy_staged *staged = NULL;
- size_t nstaged = 0, i;
- struct index_lock il_a = INDEX_LOCK_INIT;
- struct index_lock il_b = INDEX_LOCK_INIT;
- struct seq_range vr[SEQSET_MAX_RANGES];
- uint32_t nvr;
- int ok = 1;
- char desttarget[MBOX_NAME_MAX];
- int cross_mailbox;
- int destfd = -1, dfd, got, used;
+ struct copy_move cm;
+ struct copy_staged *staged = NULL;
+ struct seq_range vr[SEQSET_MAX_RANGES];
+ size_t nstaged = 0, i;
+ uint32_t nvr;
+ int ok = 1, dfd, got, used;
- memset(&idx_a, 0, sizeof(idx_a));
- memset(&idx_b, 0, sizeof(idx_b));
- memset(&result, 0, sizeof(result));
-
- got = lock_copy_move_mailboxes(ss, "COPY", req->destname, &destfd,
- &idx_a, &idx_b, &srcidx, &destidx, desttarget,
- sizeof(desttarget), &cross_mailbox, &il_a, &il_b, &result);
+ got = copy_move_lock(ss, "COPY", req->destname, &cm);
if (got == 2)
return (1);
if (got == 0) {
ok = 0;
goto done;
}
- dfd = cross_mailbox ? destfd : ss->mailbox_dir_fd;
+ dfd = cm.cross_mailbox ? cm.destfd : ss->mailbox_dir_fd;
if (!store_space_ok(dfd)) {
- result.error = MBOX_OP_ERR_OVERQUOTA;
+ cm.result.error = MBOX_OP_ERR_OVERQUOTA;
ok = 0;
- goto close_dest;
+ goto done;
}
- /* RFC 2180 SS4.4.2: the numbers are those before the EXPUNGEs */
+ /* RFC 2180 section 4.4.2: the numbers are those before the EXPUNGEs */
if ((got = saved_resolve(ss, ranges, nranges, vr, &nvr,
&req->by_uid, &used)) != 0) {
if (got == -1)
- result.error = MBOX_OP_ERR_LIMIT;
+ cm.result.error = MBOX_OP_ERR_LIMIT;
ok = 0;
- goto close_dest;
+ goto done;
}
- (void)view_sync(ss, srcidx, 1);
+ (void)view_sync(ss, cm.srcidx, 1);
- if (!stage_copy_messages(ss, srcidx, req, vr, nvr, &staged,
+ if (!stage_copy_messages(ss, cm.srcidx, req, vr, nvr, &staged,
&nstaged)) {
ok = 0;
- goto close_dest;
+ goto done;
}
-
if (nstaged > 0) {
- if (ensure_maildir_dirs(dfd, "") == -1) {
+ if (ensure_maildir_dirs(dfd, "") == -1)
ok = 0;
- goto close_dest;
- }
- if (!commit_copy_messages(ss, dfd, destidx, staged, nstaged))
+ else if (!commit_copy_messages(ss, dfd, cm.destidx, staged,
+ nstaged))
ok = 0;
}
-
if (ok) {
- result.count = (uint32_t)nstaged;
- result.uidvalidity = destidx->uidvalidity;
- result.highestmodseq = destidx->highestmodseq;
+ cm.result.count = (uint32_t)nstaged;
+ cm.result.uidvalidity = cm.destidx->uidvalidity;
+ cm.result.highestmodseq = cm.destidx->highestmodseq;
}
-close_dest:
- if (destfd != -1)
- close(destfd);
-
done:
for (i = 0; i < nstaged; i++)
free(staged[i].srcpath);
free(staged);
-
- finish_copy_move(&idx_a, &idx_b, &il_a, &il_b, ok, &result, iev);
+ copy_move_finish(&cm, ok, &ss->iev);
return (0);
}
return (out);
}
-/* test the read position, not the write index */
-
static int
move_same_mailbox(struct imsg_mbox_copy *req, const struct seq_range *ranges,
uint32_t nranges, struct mbox_index *idx, uint32_t *nmoved_out,
if (nmoved == movedcap) {
size_t newcap = (movedcap == 0) ? 8 :
movedcap * 2;
- struct moved *newmoved = reallocarray(moved, newcap,
- sizeof(*moved));
+ struct moved *newmoved;
+ newmoved = reallocarray(moved, newcap, sizeof(*moved));
if (newmoved == NULL) {
log_warn("session %u: MOVE: reallocarray "
"moved", session_id);
if (!index_basename_valid(moved[i].basename) ||
!index_field_valid(moved[i].keywords)) {
log_warnx("session %u: MOVE: unsafe field in moved "
- "message, refusing to update the index",
- session_id);
+ "message", session_id);
ok = 0;
goto done;
}
goto done;
}
- /* COPYUID mapping first */
for (i = 0; i < nmoved; i++) {
struct imsg_mbox_copy_mapping mapping;
log_warn("session %u: imsg_compose "
"IMSG_MBOX_COPY_MAPPING", session_id);
}
- /* then EXPUNGE (RFC 9051 SS6.4.8) */
+ /* RFC 9051 section 6.4.8: COPYUID before EXPUNGE */
for (i = 0; i < nmoved; i++) {
pcache_drop_uid(ss->selected_mailbox, moved[i].old_uid);
(void)send_expunged(ss, moved[i].old_seqno, moved[i].old_uid);
return (ok);
}
-/* RFC 9051 SS6.4.8: dest commits before removal */
-
+/* RFC 9051 section 6.4.8: dest commits before removal */
static int
move_cross_mailbox(struct imsg_mbox_copy *req, const struct seq_range *ranges,
- uint32_t nranges, struct mbox_index *srcidx, struct mbox_index *destidx,
- int destfd, uint32_t *nmoved_out, struct store_session *ss)
+ uint32_t nranges, struct copy_move *cm, uint32_t *nmoved_out,
+ struct store_session *ss)
{
+ struct mbox_index *srcidx = cm->srcidx;
struct copy_staged *staged = NULL;
size_t nstaged = 0, i;
int ok = 1, any_removed = 0;
if (nstaged == 0)
return (1);
- if (ensure_maildir_dirs(destfd, "") == -1) {
+ if (ensure_maildir_dirs(cm->destfd, "") == -1) {
ok = 0;
goto cleanup;
}
- if (!commit_copy_messages(ss, destfd, destidx, staged, nstaged)) {
+ if (!commit_copy_messages(ss, cm->destfd, cm->destidx, staged,
+ nstaged)) {
ok = 0;
goto cleanup;
}
(void)send_expunged(ss, old_seqno, staged[i].src_uid);
}
- /* RFC 7162 SS3.1 */
+ /* RFC 7162 section 3.1 */
if (any_removed)
srcidx->highestmodseq++;
if (index_save(ss->mailbox_dir_fd, srcidx) == -1) {
- log_warnx("session %u: MOVE: destination commit succeeded "
- "but saving the source's compacted index failed, "
- "message(s) may remain duplicated", session_id);
+ log_warnx("session %u: MOVE: source index not saved after "
+ "commit, duplicates possible", session_id);
ok = 0;
goto cleanup;
}
return (ok);
}
-/* RFC 9051 SS6.4.8 MOVE */
-
+/* RFC 9051 section 6.4.8 MOVE */
int
handle_mbox_move(struct imsg_mbox_copy *req, const struct seq_range *ranges,
uint32_t nranges, struct store_session *ss)
{
- struct imsgev *iev = &ss->iev;
- struct mbox_index idx_a, idx_b;
- struct mbox_index *srcidx = NULL, *destidx = NULL;
- struct imsg_mbox_result result;
- uint32_t nmoved = 0;
- struct index_lock il_a = INDEX_LOCK_INIT;
- struct index_lock il_b = INDEX_LOCK_INIT;
- int ok = 1;
- char desttarget[MBOX_NAME_MAX];
- int cross_mailbox;
- int destfd = -1, got, used;
- struct seq_range vr[SEQSET_MAX_RANGES];
- uint32_t nvr;
+ struct copy_move cm;
+ struct seq_range vr[SEQSET_MAX_RANGES];
+ uint32_t nmoved = 0, nvr;
+ int ok = 1, got, used;
- memset(&idx_a, 0, sizeof(idx_a));
- memset(&idx_b, 0, sizeof(idx_b));
- memset(&result, 0, sizeof(result));
-
- got = lock_copy_move_mailboxes(ss, "MOVE", req->destname, &destfd,
- &idx_a, &idx_b, &srcidx, &destidx, desttarget,
- sizeof(desttarget), &cross_mailbox, &il_a, &il_b, &result);
+ got = copy_move_lock(ss, "MOVE", req->destname, &cm);
if (got == 2)
return (1);
if (got == 0) {
ok = 0;
goto done;
}
- if (cross_mailbox && !store_space_ok(destfd)) {
- result.error = MBOX_OP_ERR_OVERQUOTA;
+ if (cm.cross_mailbox && !store_space_ok(cm.destfd)) {
+ cm.result.error = MBOX_OP_ERR_OVERQUOTA;
ok = 0;
- close(destfd);
goto done;
}
-
if ((got = saved_resolve(ss, ranges, nranges, vr, &nvr,
&req->by_uid, &used)) != 0) {
if (got == -1)
- result.error = MBOX_OP_ERR_LIMIT;
+ cm.result.error = MBOX_OP_ERR_LIMIT;
ok = 0;
- if (destfd != -1)
- close(destfd);
goto done;
}
- (void)view_sync(ss, srcidx, 1);
- if (!cross_mailbox) {
- if (!move_same_mailbox(req, vr, nvr, srcidx, &nmoved, ss))
+ (void)view_sync(ss, cm.srcidx, 1);
+ if (!cm.cross_mailbox) {
+ if (!move_same_mailbox(req, vr, nvr, cm.srcidx, &nmoved, ss))
ok = 0;
- } else {
- if (!move_cross_mailbox(req, vr, nvr, srcidx, destidx,
- destfd, &nmoved, ss))
- ok = 0;
- }
+ } else if (!move_cross_mailbox(req, vr, nvr, &cm, &nmoved, ss))
+ ok = 0;
if (ok) {
- result.count = nmoved;
- result.uidvalidity = destidx->uidvalidity;
- result.highestmodseq = destidx->highestmodseq;
+ cm.result.count = nmoved;
+ cm.result.uidvalidity = cm.destidx->uidvalidity;
+ cm.result.highestmodseq = cm.destidx->highestmodseq;
}
- if (destfd != -1)
- close(destfd);
-
done:
- finish_copy_move(&idx_a, &idx_b, &il_a, &il_b, ok, &result, iev);
+ copy_move_finish(&cm, ok, &ss->iev);
return (0);
}
blob - 4a85168d6e91df742c5d7a3a265c65d69f6f2598
blob + b7b8bb19580947ffe0a98e1aef02f008ac413bdb
--- src/mbox_fetch.c
+++ src/mbox_fetch.c
#include "log.h"
#include "store_internal.h"
-static void fetch_walk_step(struct store_session *);
-static void fetch_walk_finish(struct store_session *, int);
-static const char *fetch_cached(struct store_session *,
- const struct index_rec *, int, int *, uint32_t *);
+static void fetch_walk_step(struct store_session *);
+static void fetch_walk_finish(struct store_session *, int);
+static const char *fetch_cached(struct store_session *,
+ const struct index_rec *, int, int *, uint32_t *);
static void
fetch_send_part(struct store_session *ss, int imsg_type, const char *what,
free(combined);
}
-/* RFC 9051 SS6.4.5: BODY[...] sets \Seen, with STORE's own code */
+/* RFC 9051 section 6.4.5: BODY[...] sets \Seen, with STORE's own code */
static int
fetch_set_seen(struct store_session *ss, int synced)
{
return (1);
if (fw->active) {
- log_warnx("session %u: FETCH arrived during a paused FETCH, "
- "abandoning the earlier walk", session_id);
+ log_warnx("session %u: FETCH during a paused FETCH, earlier "
+ "walk abandoned", session_id);
index_free(&fw->idx);
}
memset(fw, 0, sizeof(*fw));
fetch_walk_finish(ss, 0);
return (0);
}
- /* RFC 9051 SS6.4.9 */
+ /* RFC 9051 section 6.4.9 */
fw->nresolved = seqset_resolve(vr, nvr, fw->req.by_uid ?
index_max_uid(idx) : (uint32_t)idx->nlines, !fw->req.by_uid,
fw->resolved);
}
}
- /* RFC 7162 SS3.2.6: VANISHED (EARLIER) first */
+ /* RFC 7162 section 3.2.6: VANISHED (EARLIER) first */
if (req->by_uid && req->want_vanished) {
for (i = 0; i < fw->nresolved; i++)
send_vanished_range(idx, fw->resolved[i].lo,
return (text);
}
+static int
+fetch_meta(struct store_session *ss, const struct index_rec *rec, uint32_t i,
+ struct imsg_mbox_fetch_meta *meta, int *have_file)
+{
+ struct store_fetch_walk *fw = &ss->fetch;
+ struct imsg_mbox_fetch *req = &fw->req;
+ off_t size = 0;
+ char suffix[64];
+
+ memset(meta, 0, sizeof(*meta));
+ meta->seqno = view_seqno(ss, rec->uid, i);
+ meta->uid = rec->uid;
+ meta->modseq = rec->modseq;
+ meta->seen_set = fw->seen_modseq != 0 && rec->modseq == fw->seen_modseq;
+
+ if (meta->seen_set ||
+ (req->attrs & (MBOX_FETCH_RFC822_SIZE | MBOX_FETCH_FLAGS))) {
+ if (locate_message_file(&ss->cur_snap, ss->mailbox_dir_fd,
+ rec->basename, &size, suffix, sizeof(suffix)) == -1) {
+ log_warnx("session %u: message %s (uid %u) indexed but "
+ "missing on disk, skipped", session_id,
+ rec->basename, meta->uid);
+ return (-1);
+ }
+ *have_file = 1;
+ }
+
+ if (req->attrs & MBOX_FETCH_RFC822_SIZE)
+ meta->size = (uint64_t)size;
+ if ((req->attrs & MBOX_FETCH_FLAGS) || meta->seen_set)
+ build_flags_string(*have_file ? suffix : "", rec->keywords,
+ meta->flags, sizeof(meta->flags));
+ if (req->attrs & MBOX_FETCH_INTERNALDATE)
+ meta->internaldate = parse_maildir_timestamp(rec->basename);
+ return (0);
+}
+
+static void
+fetch_send_header(struct store_session *ss, const struct index_rec *rec,
+ uint32_t seqno)
+{
+ struct imsg_mbox_fetch *req = &ss->fetch.req;
+ struct imsg_mbox_fetch_header hdrmeta;
+ struct imsg_parser_req preq;
+ struct imsg_parser_rep prep;
+ char *hdrbuf = NULL;
+ uint32_t hdrlen = 0;
+ int mfd, rc;
+
+ memset(&hdrmeta, 0, sizeof(hdrmeta));
+ hdrmeta.seqno = seqno;
+ hdrmeta.uid = rec->uid;
+ if (req->attrs & MBOX_FETCH_BODY_HEADER)
+ rc = read_message_header(&ss->cur_snap, ss->mailbox_dir_fd,
+ rec->basename, &hdrbuf, &hdrlen);
+ else if ((mfd = open_message_file(&ss->cur_snap, ss->mailbox_dir_fd,
+ rec->basename)) == -1)
+ rc = -1;
+ else {
+ memset(&preq, 0, sizeof(preq));
+ /* same size, and terminated on arrival */
+ (void)strlcpy(preq.fields, req->header_fields,
+ sizeof(preq.fields));
+ preq.fields_not = req->header_fields_not;
+ rc = parser_request(IMSG_PARSER_HEADER_FIELDS, "HEADER.FIELDS",
+ mfd, rec->basename, &preq, NULL, 0, FETCH_HEADER_MAX, &prep,
+ &hdrbuf);
+ hdrlen = prep.len;
+ close(mfd);
+ }
+ if (rc == 0) {
+ hdrmeta.found = 1;
+ hdrmeta.hdrlen = hdrlen;
+ }
+
+ fetch_send_part(ss, IMSG_MBOX_FETCH_HEADER, "IMSG_MBOX_FETCH_HEADER",
+ &hdrmeta, sizeof(hdrmeta), hdrmeta.found, hdrbuf, hdrlen);
+ free(hdrbuf);
+}
+
+/* WHOLE wins over TEXT and PART; -1 when out of descriptors */
+static int
+fetch_send_body(struct store_session *ss, const struct index_rec *rec,
+ uint32_t seqno)
+{
+ struct store_fetch_walk *fw = &ss->fetch;
+ struct imsg_mbox_fetch *req = &fw->req;
+ struct imsg_mbox_fetch_body bodymeta;
+ struct imsg_parser_req preq;
+ struct imsg_parser_rep prep;
+ uint64_t off = 0, len = 0;
+ int fd = -1, fdbusy = 0;
+ int want_whole, want_part, want_text;
+
+ want_whole = (req->attrs & MBOX_FETCH_BODY_WHOLE) != 0;
+ want_part = !want_whole && (req->attrs & MBOX_FETCH_BODY_PART) != 0 &&
+ !(req->attrs & MBOX_FETCH_BODY_TEXT);
+ want_text = !want_whole && !want_part;
+
+ memset(&bodymeta, 0, sizeof(bodymeta));
+ bodymeta.seqno = seqno;
+ bodymeta.uid = rec->uid;
+
+ if (want_part) {
+ memset(&preq, 0, sizeof(preq));
+ preq.pathlen = parse_section_part(req->section_part, preq.path,
+ MIME_MAX_DEPTH);
+ if (preq.pathlen != -1 &&
+ (fd = open_message_file(&ss->cur_snap, ss->mailbox_dir_fd,
+ rec->basename)) == -1)
+ fdbusy = errno == EMFILE || errno == ENFILE;
+ else if (preq.pathlen != -1 &&
+ parser_request(IMSG_PARSER_PART, "BODY[<part>]", fd,
+ rec->basename, &preq, NULL, 0, 0, &prep, NULL) == 0) {
+ bodymeta.found = 1;
+ off = prep.part_off;
+ len = prep.part_len;
+ }
+ /* the fd the extent was checked on */
+ if (fd != -1 && (!bodymeta.found || len == 0)) {
+ close(fd);
+ fd = -1;
+ }
+ } else {
+ fd = message_body_range(&ss->cur_snap, ss->mailbox_dir_fd,
+ rec->basename, want_text, &off, &len, &fdbusy);
+ if (fd != -1)
+ bodymeta.found = 1;
+ }
+
+ /* out of descriptors: retry once sent */
+ if (fdbusy && fw->fds > 0)
+ return (-1);
+
+ if (bodymeta.found) {
+ partial_range(req->has_partial, req->partial_start,
+ req->partial_count, &off, &len);
+ if (len == 0 && fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ bodymeta.offset = off;
+ bodymeta.length = len;
+ }
+
+ /* imsg_compose() owns fd only once it succeeds */
+ if (imsg_compose(&ss->iev.ibuf, IMSG_MBOX_FETCH_BODY, 0, 0, fd,
+ &bodymeta, sizeof(bodymeta)) == -1) {
+ log_warn("session %u: imsg_compose IMSG_MBOX_FETCH_BODY",
+ session_id);
+ if (fd != -1)
+ close(fd);
+ } else {
+ fw->composed += sizeof(bodymeta);
+ if (fd != -1)
+ fw->fds++;
+ }
+ return (0);
+}
+
+/* a cached ENVELOPE or BODYSTRUCTURE, else the parser's; NULL if neither */
+static const char *
+fetch_parsed_text(struct store_session *ss, const struct index_rec *rec,
+ int item, int *have_file, char **bufp, uint32_t *lenp)
+{
+ struct imsg_parser_rep prep;
+ const char *text, *what;
+ uint32_t max;
+ int mfd, type;
+
+ *bufp = NULL;
+ if ((text = fetch_cached(ss, rec, item, have_file, lenp)) != NULL)
+ return (text);
+ if ((mfd = open_message_file(&ss->cur_snap, ss->mailbox_dir_fd,
+ rec->basename)) == -1)
+ return (NULL);
+ if (item == PCACHE_ENVELOPE) {
+ type = IMSG_PARSER_ENVELOPE;
+ what = "ENVELOPE";
+ max = ENVELOPE_MAX;
+ } else {
+ type = IMSG_PARSER_BODYSTRUCTURE;
+ what = "BODYSTRUCTURE";
+ max = BODYSTRUCTURE_MAX;
+ }
+ if (parser_request(type, what, mfd, rec->basename, NULL, NULL, 0, max,
+ &prep, bufp) == 0) {
+ *lenp = prep.len;
+ pcache_put(ss->selected_mailbox, ss->fetch.idx.uidvalidity,
+ rec->uid, rec->basename, item, *bufp, prep.len);
+ text = *bufp;
+ }
+ close(mfd);
+ return (text);
+}
+
+static void
+fetch_send_envelope(struct store_session *ss, const struct index_rec *rec,
+ uint32_t seqno, int *have_file)
+{
+ struct imsg_mbox_fetch_envelope envmeta;
+ const char *env;
+ char *envbuf;
+
+ memset(&envmeta, 0, sizeof(envmeta));
+ envmeta.seqno = seqno;
+ envmeta.uid = rec->uid;
+ env = fetch_parsed_text(ss, rec, PCACHE_ENVELOPE, have_file, &envbuf,
+ &envmeta.envlen);
+ envmeta.found = env != NULL;
+ fetch_send_part(ss, IMSG_MBOX_FETCH_ENVELOPE,
+ "IMSG_MBOX_FETCH_ENVELOPE", &envmeta, sizeof(envmeta),
+ envmeta.found, env, envmeta.envlen);
+ free(envbuf);
+}
+
+static void
+fetch_send_bodystructure(struct store_session *ss,
+ const struct index_rec *rec, uint32_t seqno, int *have_file)
+{
+ struct imsg_mbox_fetch_bodystructure bsmeta;
+ const char *bs;
+ char *bsbuf;
+
+ memset(&bsmeta, 0, sizeof(bsmeta));
+ bsmeta.seqno = seqno;
+ bsmeta.uid = rec->uid;
+ bs = fetch_parsed_text(ss, rec, PCACHE_BODYSTRUCTURE, have_file, &bsbuf,
+ &bsmeta.bslen);
+ bsmeta.found = bs != NULL;
+ fetch_send_part(ss, IMSG_MBOX_FETCH_BODYSTRUCTURE,
+ "IMSG_MBOX_FETCH_BODYSTRUCTURE", &bsmeta, sizeof(bsmeta),
+ bsmeta.found, bs, bsmeta.bslen);
+ free(bsbuf);
+}
+
/* returns, still active, once FETCH_BATCH_MAX is queued */
static void
fetch_walk_step(struct store_session *ss)
{
- struct store_fetch_walk *fw = &ss->fetch;
- struct mbox_index *idx = &fw->idx;
- struct imsg_mbox_fetch *req = &fw->req;
- struct imsgev *iev = &ss->iev;
- struct seq_range *resolved = fw->resolved;
- uint32_t nresolved = fw->nresolved;
- uint32_t max_hi = fw->max_hi;
- uint32_t i;
+ struct store_fetch_walk *fw = &ss->fetch;
+ struct mbox_index *idx = &fw->idx;
+ struct imsg_mbox_fetch *req = &fw->req;
+ struct imsg_mbox_fetch_meta meta;
+ struct index_rec rec;
+ enum seqset_pos pos;
+ uint32_t i;
+ int have_file, ready;
for (i = fw->next; i <= (uint32_t)idx->nlines; i++) {
- struct imsg_mbox_fetch_meta meta;
- struct index_rec rec;
- enum seqset_pos pos;
- off_t size = 0;
- char suffix[64];
- int have_file = 0;
-
if (index_parse_line(idx->lines[i - 1], &rec) == -1)
continue;
- pos = seqset_position(resolved, nresolved, max_hi, req->by_uid,
- rec.uid, i);
+ pos = seqset_position(fw->resolved, fw->nresolved, fw->max_hi,
+ req->by_uid, rec.uid, i);
if (pos == SEQSET_PAST_END)
break;
if (pos == SEQSET_SKIP)
if (!fw->no_parser && fetch_needs_parser(req,
ss->selected_mailbox, idx->uidvalidity, &rec)) {
- int ready = parser_ready();
-
- if (ready == 0) {
+ if ((ready = parser_ready()) == 0) {
fw->next = i;
fw->wait_parser = 1;
return;
fw->no_parser = 1;
}
- memset(&meta, 0, sizeof(meta));
- meta.seqno = view_seqno(ss, rec.uid, i);
- meta.uid = rec.uid;
- meta.modseq = rec.modseq;
- meta.seen_set = fw->seen_modseq != 0 &&
- rec.modseq == fw->seen_modseq;
+ have_file = 0;
+ if (fetch_meta(ss, &rec, i, &meta, &have_file) == -1)
+ continue;
- if (meta.seen_set || (req->attrs &
- (MBOX_FETCH_RFC822_SIZE | MBOX_FETCH_FLAGS))) {
- if (locate_message_file(&ss->cur_snap,
- ss->mailbox_dir_fd, rec.basename, &size, suffix,
- sizeof(suffix)) == -1) {
- log_warnx("session %u: message %s (uid %u) "
- "indexed but missing on disk, skipped",
- session_id, rec.basename, meta.uid);
- continue;
- }
- have_file = 1;
- }
-
- if (req->attrs & MBOX_FETCH_RFC822_SIZE)
- meta.size = (uint64_t)size;
- if ((req->attrs & MBOX_FETCH_FLAGS) || meta.seen_set)
- build_flags_string(have_file ? suffix : "",
- rec.keywords, meta.flags, sizeof(meta.flags));
- if (req->attrs & MBOX_FETCH_INTERNALDATE)
- meta.internaldate = parse_maildir_timestamp(
- rec.basename);
-
/* FETCH_HEADER precedes FETCH_META */
if (req->attrs & (MBOX_FETCH_BODY_HEADER |
- MBOX_FETCH_HEADER_FIELDS)) {
- struct imsg_mbox_fetch_header hdrmeta;
- struct imsg_parser_req preq;
- struct imsg_parser_rep prep;
- char *hdrbuf = NULL;
- uint32_t hdrlen = 0;
- int mfd, rc;
+ MBOX_FETCH_HEADER_FIELDS))
+ fetch_send_header(ss, &rec, meta.seqno);
- memset(&hdrmeta, 0, sizeof(hdrmeta));
- hdrmeta.seqno = view_seqno(ss, rec.uid, i);
- hdrmeta.uid = rec.uid;
- if (req->attrs & MBOX_FETCH_BODY_HEADER)
- rc = read_message_header(&ss->cur_snap,
- ss->mailbox_dir_fd, rec.basename, &hdrbuf,
- &hdrlen);
- else if ((mfd = open_message_file(&ss->cur_snap,
- ss->mailbox_dir_fd, rec.basename)) == -1)
- rc = -1;
- else {
- memset(&preq, 0, sizeof(preq));
- /* same size, and terminated on arrival */
- (void)strlcpy(preq.fields, req->header_fields,
- sizeof(preq.fields));
- preq.fields_not = req->header_fields_not;
- rc = parser_request(IMSG_PARSER_HEADER_FIELDS,
- "HEADER.FIELDS", mfd, rec.basename, &preq,
- NULL, 0, FETCH_HEADER_MAX, &prep, &hdrbuf);
- hdrlen = prep.len;
- close(mfd);
- }
- if (rc == 0) {
- hdrmeta.found = 1;
- hdrmeta.hdrlen = hdrlen;
- }
-
- fetch_send_part(ss, IMSG_MBOX_FETCH_HEADER,
- "IMSG_MBOX_FETCH_HEADER", &hdrmeta, sizeof(hdrmeta),
- hdrmeta.found, hdrbuf, hdrlen);
- free(hdrbuf);
+ if ((req->attrs & (MBOX_FETCH_BODY_WHOLE |
+ MBOX_FETCH_BODY_TEXT | MBOX_FETCH_BODY_PART)) &&
+ fetch_send_body(ss, &rec, meta.seqno) == -1) {
+ fw->next = i;
+ return;
}
- /* WHOLE wins over TEXT and PART */
- if (req->attrs & (MBOX_FETCH_BODY_WHOLE | MBOX_FETCH_BODY_TEXT |
- MBOX_FETCH_BODY_PART)) {
- struct imsg_mbox_fetch_body bodymeta;
- uint64_t off = 0, len = 0;
- int fd = -1, fdbusy = 0;
- int want_whole =
- (req->attrs & MBOX_FETCH_BODY_WHOLE) != 0;
- int want_part =
- !want_whole &&
- (req->attrs & MBOX_FETCH_BODY_PART) != 0 &&
- !(req->attrs & MBOX_FETCH_BODY_TEXT);
- int want_text =
- !want_whole &&
- !want_part;
+ if (req->attrs & MBOX_FETCH_ENVELOPE)
+ fetch_send_envelope(ss, &rec, meta.seqno, &have_file);
+ if (req->attrs & MBOX_FETCH_BODYSTRUCTURE)
+ fetch_send_bodystructure(ss, &rec, meta.seqno,
+ &have_file);
- memset(&bodymeta, 0, sizeof(bodymeta));
- bodymeta.seqno = view_seqno(ss, rec.uid, i);
- bodymeta.uid = rec.uid;
-
- if (want_part) {
- struct imsg_parser_req preq;
- struct imsg_parser_rep prep;
-
- memset(&preq, 0, sizeof(preq));
- preq.pathlen = parse_section_part(
- req->section_part, preq.path,
- MIME_MAX_DEPTH);
- if (preq.pathlen != -1 &&
- (fd = open_message_file(&ss->cur_snap,
- ss->mailbox_dir_fd, rec.basename)) == -1)
- fdbusy = errno == EMFILE ||
- errno == ENFILE;
- else if (preq.pathlen != -1 &&
- parser_request(IMSG_PARSER_PART,
- "BODY[<part>]", fd, rec.basename, &preq,
- NULL, 0, 0, &prep, NULL) == 0) {
- bodymeta.found = 1;
- off = prep.part_off;
- len = prep.part_len;
- }
- /* the fd the extent was checked on */
- if (fd != -1 && (!bodymeta.found || len == 0)) {
- close(fd);
- fd = -1;
- }
- } else {
- fd = message_body_range(&ss->cur_snap,
- ss->mailbox_dir_fd, rec.basename, want_text,
- &off, &len, &fdbusy);
- if (fd != -1)
- bodymeta.found = 1;
- }
-
- /* out of descriptors: retry once sent */
- if (fdbusy && fw->fds > 0) {
- fw->next = i;
- return;
- }
-
- if (bodymeta.found) {
- partial_range(req->has_partial,
- req->partial_start, req->partial_count,
- &off, &len);
- if (len == 0 && fd != -1) {
- close(fd);
- fd = -1;
- }
- bodymeta.offset = off;
- bodymeta.length = len;
- }
-
- /* imsg_compose() owns fd only once it succeeds */
- if (imsg_compose(&iev->ibuf, IMSG_MBOX_FETCH_BODY, 0, 0,
- fd, &bodymeta, sizeof(bodymeta)) == -1) {
- log_warn("session %u: imsg_compose "
- "IMSG_MBOX_FETCH_BODY", session_id);
- if (fd != -1)
- close(fd);
- } else {
- fw->composed += sizeof(bodymeta);
- if (fd != -1)
- fw->fds++;
- }
- }
-
- if (req->attrs & MBOX_FETCH_ENVELOPE) {
- struct imsg_mbox_fetch_envelope envmeta;
- struct imsg_parser_rep prep;
- const char *env;
- char *envbuf = NULL;
- int mfd;
-
- memset(&envmeta, 0, sizeof(envmeta));
- envmeta.seqno = view_seqno(ss, rec.uid, i);
- envmeta.uid = rec.uid;
- if ((env = fetch_cached(ss, &rec, PCACHE_ENVELOPE,
- &have_file, &envmeta.envlen)) != NULL)
- envmeta.found = 1;
- else if ((mfd = open_message_file(&ss->cur_snap,
- ss->mailbox_dir_fd, rec.basename)) != -1) {
- if (parser_request(IMSG_PARSER_ENVELOPE,
- "ENVELOPE", mfd, rec.basename, NULL, NULL,
- 0, ENVELOPE_MAX, &prep, &envbuf) == 0) {
- envmeta.found = 1;
- envmeta.envlen = prep.len;
- env = envbuf;
- pcache_put(ss->selected_mailbox,
- idx->uidvalidity, rec.uid,
- rec.basename, PCACHE_ENVELOPE,
- envbuf, prep.len);
- }
- close(mfd);
- }
-
- fetch_send_part(ss, IMSG_MBOX_FETCH_ENVELOPE,
- "IMSG_MBOX_FETCH_ENVELOPE", &envmeta,
- sizeof(envmeta),
- envmeta.found, env, envmeta.envlen);
- free(envbuf);
- }
-
- if (req->attrs & MBOX_FETCH_BODYSTRUCTURE) {
- struct imsg_mbox_fetch_bodystructure bsmeta;
- struct imsg_parser_rep prep;
- const char *bs;
- char *bsbuf = NULL;
- int mfd;
-
- memset(&bsmeta, 0, sizeof(bsmeta));
- bsmeta.seqno = view_seqno(ss, rec.uid, i);
- bsmeta.uid = rec.uid;
- if ((bs = fetch_cached(ss, &rec, PCACHE_BODYSTRUCTURE,
- &have_file, &bsmeta.bslen)) != NULL)
- bsmeta.found = 1;
- else if ((mfd = open_message_file(&ss->cur_snap,
- ss->mailbox_dir_fd, rec.basename)) != -1) {
- if (parser_request(IMSG_PARSER_BODYSTRUCTURE,
- "BODYSTRUCTURE", mfd, rec.basename, NULL,
- NULL, 0, BODYSTRUCTURE_MAX, &prep,
- &bsbuf) == 0) {
- bsmeta.found = 1;
- bsmeta.bslen = prep.len;
- bs = bsbuf;
- pcache_put(ss->selected_mailbox,
- idx->uidvalidity, rec.uid,
- rec.basename, PCACHE_BODYSTRUCTURE,
- bsbuf, prep.len);
- }
- close(mfd);
- }
-
- fetch_send_part(ss, IMSG_MBOX_FETCH_BODYSTRUCTURE,
- "IMSG_MBOX_FETCH_BODYSTRUCTURE", &bsmeta,
- sizeof(bsmeta), bsmeta.found, bs, bsmeta.bslen);
- free(bsbuf);
- }
-
- if (imsg_compose(&iev->ibuf, IMSG_MBOX_FETCH_META, 0, 0, -1,
+ if (imsg_compose(&ss->iev.ibuf, IMSG_MBOX_FETCH_META, 0, 0, -1,
&meta, sizeof(meta)) == -1)
log_warn("session %u: imsg_compose "
"IMSG_MBOX_FETCH_META", session_id);
out[i] = '\0';
}
-/* RFC 9051 SS6.3.11 STATUS */
+/* RFC 9051 section 6.3.11 STATUS */
int
handle_mbox_status(struct imsg_mbox_status *req, struct store_session *ss)
{
rec.basename, &size, suffix,
sizeof(suffix)) == -1) {
log_warnx("session %u: message %s indexed but "
- "missing on disk, skipped for STATUS "
- "UNSEEN/DELETED/SIZE", session_id,
- rec.basename);
+ "missing on disk, skipped (STATUS)",
+ session_id, rec.basename);
continue;
}
blob - 87c2870025ee974842c127c02c638b19577c9cda
blob + 191a3b187becea845e6322a2b81e4de3a08380f2
--- src/mbox_manage.c
+++ src/mbox_manage.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
+#include <sys/param.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/mount.h>
handle_mbox_select(struct imsg_mbox_select *req,
const struct seq_range *ranges, uint32_t nranges, struct store_session *ss)
{
- struct imsgev *iev = &ss->iev;
- struct mbox_index idx;
- struct imsg_mbox_selected reply;
- struct index_lock il = INDEX_LOCK_INIT;
- const char *target;
- int dfd, locked;
+ struct imsgev *iev = &ss->iev;
+ struct mbox_index idx;
+ struct imsg_mbox_selected reply;
+ struct index_lock il = INDEX_LOCK_INIT;
+ const char *target;
+ int dfd, locked;
memset(&reply, 0, sizeof(reply));
close(ss->mailbox_dir_fd);
ss->mailbox_dir_fd = dfd;
- if (strlcpy(ss->selected_mailbox, target,
- sizeof(ss->selected_mailbox)) >= sizeof(ss->selected_mailbox)) {
- log_warnx("session %u: SELECT %s: name too long to "
- "record, can't happen (validated above, same-size "
- "buffers)", session_id, req->mailbox);
- reply.error = MBOX_OP_ERR_GENERIC;
- index_free(&idx);
- index_lock_release(&il);
- goto send;
- }
+ strlcpy(ss->selected_mailbox, target, sizeof(ss->selected_mailbox));
reply.error = MBOX_OP_OK;
ss->mailbox_selected = 1;
char path[MBOX_NAME_MAX + 8];
size_t i;
- for (i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) {
+ for (i = 0; i < nitems(dirs); i++) {
if (snprintf(path, sizeof(path), "%s%s", prefix, dirs[i]) >=
(int)sizeof(path)) {
log_warnx("session %u: mailbox path too long",
return (0);
}
-
#define STORE_MINSPACE 5
#define STORE_MININODES 5
avail = sfs.f_bavail > 0 ? (uint64_t)sfs.f_bavail : 0;
if (sfs.f_blocks > 0 &&
avail * 100 < (avail + used) * STORE_MINSPACE) {
- log_warnx("session %u: less than %d%% of the mail store's "
- "space is free, refusing to add to it", session_id,
- STORE_MINSPACE);
+ log_warnx("session %u: under %d%% of the store's space free, "
+ "refused", session_id, STORE_MINSPACE);
return (0);
}
used = sfs.f_files - sfs.f_ffree;
avail = sfs.f_favail > 0 ? (uint64_t)sfs.f_favail : 0;
if (sfs.f_files > 0 &&
avail * 100 < (avail + used) * STORE_MININODES) {
- log_warnx("session %u: less than %d%% of the mail store's "
- "inodes are free, refusing to add to it", session_id,
- STORE_MININODES);
+ log_warnx("session %u: under %d%% of the store's inodes free, "
+ "refused", session_id, STORE_MININODES);
return (0);
}
return (1);
}
-/* RFC 9051 SS6.3.4 CREATE */
-
+/* RFC 9051 section 6.3.4 CREATE */
void
handle_mbox_create(struct imsg_mbox_create *req, struct imsgev *iev)
{
} else {
log_debug("session %u: CREATE %s: already exists",
session_id, req->mailbox);
- /* RFC 5530 SS3 ALREADYEXISTS */
+ /* RFC 5530 section 3 ALREADYEXISTS */
result.error = MBOX_OP_ERR_ALREADY_EXISTS;
}
goto send;
static int
remove_maildir_subtree(int dfd, const char *prefix)
{
- static const char *dirs[] = { "tmp", "new", "cur" };
+ static const char *dirs[] = { "tmp", "new", "cur" };
+ /* every file created here must go before rmdir(2) */
+ static const char *files[] = {
+ STORE_INDEX_NAME,
+ STORE_INDEX_TMP_NAME,
+ STORE_INDEX_LOCK_NAME,
+ };
/* room for STORE_INDEX_NAME after the longest name */
- char path[MBOX_NAME_MAX + 32];
- size_t i;
+ char path[MBOX_NAME_MAX + 32];
+ size_t i;
- for (i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) {
+ for (i = 0; i < nitems(dirs); i++) {
DIR *dp;
const struct dirent *de;
int ok = 1;
session_id);
return (-1);
}
- {
- int subfd;
-
- dp = NULL;
- subfd = openat(dfd, path, O_RDONLY | O_DIRECTORY);
- if (subfd != -1 && (dp = fdopendir(subfd)) == NULL)
- close(subfd);
- }
- if (dp == NULL) {
+ if ((dp = opendirat(dfd, path)) == NULL) {
if (errno == ENOENT)
/* tmp/ may never have been created */
continue;
continue;
}
if (!S_ISREG(st.st_mode)) {
- log_warnx("session %u: DELETE: refusing, "
- "%s is not a regular file", session_id,
- entpath);
+ log_warnx("session %u: DELETE %s: not a "
+ "regular file", session_id, entpath);
ok = 0;
continue;
}
}
}
- /* every file created here must go before rmdir(2) */
- {
- static const char *files[] = {
- STORE_INDEX_NAME,
- STORE_INDEX_TMP_NAME,
- STORE_INDEX_LOCK_NAME,
- };
- size_t f;
-
- for (f = 0; f < sizeof(files) / sizeof(files[0]); f++) {
- if (snprintf(path, sizeof(path), "%s%s", prefix,
- files[f]) >= (int)sizeof(path)) {
- log_warnx("session %u: DELETE: index path too "
- "long", session_id);
- return (-1);
- }
- if (unlinkat(dfd, path, 0) == -1 && errno != ENOENT) {
- log_warn("session %u: DELETE: unlink %s",
- session_id, path);
- return (-1);
- }
+ for (i = 0; i < nitems(files); i++) {
+ if (snprintf(path, sizeof(path), "%s%s", prefix, files[i]) >=
+ (int)sizeof(path)) {
+ log_warnx("session %u: DELETE: index path too long",
+ session_id);
+ return (-1);
}
+ if (unlinkat(dfd, path, 0) == -1 && errno != ENOENT) {
+ log_warn("session %u: DELETE: unlink %s", session_id,
+ path);
+ return (-1);
+ }
}
return (0);
}
-/* RFC 9051 SS6.3.5 DELETE */
-
+/* RFC 9051 section 6.3.5 DELETE */
void
handle_mbox_delete(struct imsg_mbox_delete *req, struct store_session *ss)
{
goto send;
}
-
if (fstatat(maildir_root_fd, req->mailbox, &st,
AT_SYMLINK_NOFOLLOW) == -1 || !S_ISDIR(st.st_mode)) {
log_debug("session %u: DELETE %s: no such mailbox",
session_id, req->mailbox);
- /* RFC 5530 SS3 NONEXISTENT */
+ /* RFC 5530 section 3 NONEXISTENT */
result.error = MBOX_OP_ERR_NO_SUCH_MAILBOX;
goto send;
}
session_id);
}
-/* RFC 9051 SS6.3.6 RENAME; INBOX is the root and cannot move */
-
+/* RFC 9051 section 6.3.6 RENAME; INBOX is the root and cannot move */
void
handle_mbox_rename(struct imsg_mbox_rename *req, struct store_session *ss)
{
goto send;
}
-
if (fstatat(maildir_root_fd, req->oldname, &st,
AT_SYMLINK_NOFOLLOW) == -1 || !S_ISDIR(st.st_mode)) {
log_debug("session %u: RENAME %s: no such mailbox",
session_id, req->oldname);
- /* RFC 5530 SS3 NONEXISTENT */
+ /* RFC 5530 section 3 NONEXISTENT */
result.error = MBOX_OP_ERR_NO_SUCH_MAILBOX;
goto send;
}
- /* RFC 9051 SS6.3.6: error to rename onto an existing name */
+ /* RFC 9051 section 6.3.6: error to rename onto an existing name */
if (fstatat(maildir_root_fd, req->newname, &st,
AT_SYMLINK_NOFOLLOW) == 0 || errno != ENOENT) {
log_debug("session %u: RENAME %s -> %s: destination exists",
session_id, req->oldname, req->newname);
- /* RFC 5530 SS3 ALREADYEXISTS */
+ /* RFC 5530 section 3 ALREADYEXISTS */
result.error = MBOX_OP_ERR_ALREADY_EXISTS;
goto send;
}
if (result.error == MBOX_OP_OK)
pcache_drop_mailbox(req->oldname);
if (result.error == MBOX_OP_OK &&
- strcmp(ss->selected_mailbox, req->oldname) == 0) {
- if (strlcpy(ss->selected_mailbox, req->newname,
- sizeof(ss->selected_mailbox)) >=
- sizeof(ss->selected_mailbox))
- log_warnx("session %u: RENAME %s -> %s: new name too "
- "long to record, can't happen (validated)",
- session_id, req->oldname, req->newname);
- }
+ strcmp(ss->selected_mailbox, req->oldname) == 0)
+ strlcpy(ss->selected_mailbox, req->newname,
+ sizeof(ss->selected_mailbox));
if (imsg_compose(&iev->ibuf, IMSG_MBOX_RESULT, 0, 0, -1, &result,
sizeof(result)) == -1)
log_warn("session %u: imsg_compose IMSG_MBOX_RESULT",
static int
sublist_load(struct sublist *sl)
{
- FILE *fp;
- char line[MBOX_NAME_MAX + 1];
+ FILE *fp = NULL;
+ char *line = NULL;
+ size_t linesize = 0;
+ int fd;
sl->present = 0;
- {
- int fd;
-
- fp = NULL;
- fd = openat(maildir_root_fd, STORE_SUBSCRIPTIONS_NAME,
- O_RDONLY);
- if (fd != -1 && (fp = fdopen(fd, "r")) == NULL)
- close(fd);
- }
+ fd = openat(maildir_root_fd, STORE_SUBSCRIPTIONS_NAME, O_RDONLY);
+ if (fd != -1 && (fp = fdopen(fd, "r")) == NULL)
+ close(fd);
if (fp == NULL) {
if (errno == ENOENT)
return (0);
}
sl->present = 1;
- while (fgets(line, sizeof(line), fp) != NULL) {
- /* fgets(3) splits an over-long line */
- if (strchr(line, '\n') == NULL &&
- strlen(line) == sizeof(line) - 1) {
- int c = fgetc(fp);
-
- if (c != EOF && c != '\n') {
- log_warnx("session %u: over-long line in %s, "
- "refusing to parse it", session_id,
- STORE_SUBSCRIPTIONS_NAME);
- goto fail;
- }
- }
+ while (getline(&line, &linesize, fp) != -1) {
line[strcspn(line, "\n")] = '\0';
if (line[0] == '\0')
continue;
/* a name SUBSCRIBE would refuse: the file was hand-edited */
if (!mailbox_name_syntax_ok(line)) {
- log_warnx("session %u: unusable name in %s, refusing "
- "to parse it", session_id,
- STORE_SUBSCRIPTIONS_NAME);
+ log_warnx("session %u: %s: unusable name, not parsed",
+ session_id, STORE_SUBSCRIPTIONS_NAME);
goto fail;
}
if (sublist_has(sl, line))
STORE_SUBSCRIPTIONS_NAME);
goto fail;
}
+ free(line);
fclose(fp);
return (0);
fail:
+ free(line);
fclose(fp);
sublist_free(sl);
sl->present = 0;
struct dirent *de;
int badname;
- {
- int rootfd;
-
- dp = NULL;
- rootfd = openat(maildir_root_fd, ".",
- O_RDONLY | O_DIRECTORY);
- if (rootfd != -1 && (dp = fdopendir(rootfd)) == NULL)
- close(rootfd);
- }
- if (dp == NULL) {
+ if ((dp = opendirat(maildir_root_fd, ".")) == NULL) {
log_warn("session %u: UNSUBSCRIBE: opendir .", session_id);
return (-1);
}
struct imsg_mbox_list_item item;
memset(&item, 0, sizeof(item));
- if (strlcpy(item.mailbox, name, sizeof(item.mailbox)) >=
- sizeof(item.mailbox)) {
- log_warnx("session %u: LIST: %s truncated, can't happen (the "
- "name rule already bounds it under MBOX_NAME_MAX)",
- session_id, name);
- return (0);
- }
+ strlcpy(item.mailbox, name, sizeof(item.mailbox));
item.exists = exists;
if (imsg_compose(&iev->ibuf, IMSG_MBOX_LIST_ITEM, 0, 0, -1, &item,
sizeof(item)) == -1) {
return (1);
}
-/* RFC 9051 SS6.3.9 LIST; INBOX is not sent */
-
+/* RFC 9051 section 6.3.9 LIST; INBOX is not sent */
void
handle_mbox_list(struct imsg_mbox_list *req, struct imsgev *iev)
{
memset(&result, 0, sizeof(result));
-
if (req->subscribed_only && sublist_load(&sl) == -1)
sl.present = 0;
- {
- int rootfd;
-
- dp = NULL;
- rootfd = openat(maildir_root_fd, ".",
- O_RDONLY | O_DIRECTORY);
- if (rootfd != -1 && (dp = fdopendir(rootfd)) == NULL)
- close(rootfd);
- }
- if (dp == NULL) {
+ if ((dp = opendirat(maildir_root_fd, ".")) == NULL) {
log_warn("session %u: LIST: opendir .", session_id);
result.error = MBOX_OP_ERR_GENERIC;
goto send;
/* a rejected name hides real mail: say so */
if (badname && !skip_warned) {
skip_warned = 1;
- log_warnx("session %u: LIST: skipping %s: "
- "not a valid mailbox name (RFC 9051 "
- "SS5.1); it will not be listed or "
- "selectable. Further skips this "
- "connection are not logged", session_id,
- de->d_name);
+ log_warnx("session %u: LIST: %s: invalid "
+ "mailbox name, skipped",
+ session_id, de->d_name);
}
continue;
}
}
closedir(dp);
- /* RFC 9051 SS6.3.9.6: subscribed names with no mailbox */
+ /* RFC 9051 section 6.3.9.6: subscribed names with no mailbox */
for (i = 0; i < sl.n; i++) {
struct stat st;
session_id);
}
-/* RFC 9051 SS6.3.7 SUBSCRIBE; idempotent */
-
+/* RFC 9051 section 6.3.7 SUBSCRIBE; idempotent */
void
handle_mbox_subscribe(struct imsg_mbox_subscribe *req, struct imsgev *iev)
{
goto send;
}
-
- /* RFC 9051 SS6.3.7 lets a server require the mailbox exist */
+ /* RFC 9051 section 6.3.7 lets a server require the mailbox exist */
if (fstatat(maildir_root_fd, req->mailbox, &st,
AT_SYMLINK_NOFOLLOW) == -1 || !S_ISDIR(st.st_mode)) {
log_debug("session %u: SUBSCRIBE %s: no such mailbox",
session_id);
}
-/* RFC 9051 SS6.3.8 UNSUBSCRIBE; idempotent */
-
+/* RFC 9051 section 6.3.8 UNSUBSCRIBE; idempotent */
void
handle_mbox_unsubscribe(struct imsg_mbox_subscribe *req, struct imsgev *iev)
{
result.error = MBOX_OP_ERR_GENERIC;
goto send;
}
- /* RFC 9051 SS6.3.8: no existence test */
+ /* RFC 9051 section 6.3.8: no existence test */
if (!mailbox_name_valid(req->mailbox)) {
log_debug("session %u: UNSUBSCRIBE %s: invalid name",
session_id, req->mailbox);
goto send;
}
-
if (subs_lock_acquire(&lockfd) == -1 || sublist_load(&sl) == -1) {
result.error = MBOX_OP_ERR_GENERIC;
goto send;
goto send;
}
if (!sublist_has(&sl, req->mailbox)) {
- /* SS6.3.8: unsubscribing what isn't subscribed returns OK */
+ /* section 6.3.8: unsubscribing the unsubscribed is OK */
result.error = MBOX_OP_OK;
goto send;
}
const char *
append_hostname(void)
{
- static char hostbuf[256];
+ static char hostbuf[HOST_NAME_MAX + 1];
static int resolved;
if (!resolved) {
if (gethostname(hostbuf, sizeof(hostbuf)) == -1) {
log_warn("session %u: gethostname", session_id);
- if (strlcpy(hostbuf, "imapd", sizeof(hostbuf)) >=
- sizeof(hostbuf))
- log_warnx("session %u: gethostname fallback "
- "truncated, can't happen", session_id);
+ strlcpy(hostbuf, "imapd", sizeof(hostbuf));
}
resolved = 1;
}
const struct imsg_mbox_append *req)
{
struct store_append *ap = &ss->append;
- char target[MBOX_NAME_MAX];
- int64_t delivery_ts;
+ char target[MBOX_NAME_MAX];
+ int64_t delivery_ts;
if (ap->active) {
log_warnx("session %u: APPEND begun with another in flight, "
return;
}
if (!index_field_valid(req->keywords)) {
- log_warnx("session %u: APPEND: refusing unsafe keywords "
- "field", session_id);
+ log_warnx("session %u: APPEND: unsafe keywords field",
+ session_id);
return;
}
handle_mbox_append_data(struct store_session *ss, const char *buf, size_t len)
{
struct store_append *ap = &ss->append;
- size_t written = 0;
+ size_t written = 0;
if (!ap->active) {
log_warnx("session %u: APPEND data with no APPEND in flight, "
blob - f27afc00ad6efce70e76eb235576e2801e307357
blob + dd0be27fb30c0b6486c528ce9d6f7b8ec30da378
--- src/mbox_search.c
+++ src/mbox_search.c
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
+#include <sys/time.h>
#include <dirent.h>
#include <errno.h>
char *save;
if (strlcpy(tmp, list, sizeof(tmp)) >= sizeof(tmp)) {
- log_warnx("session %u: kw_list_contains: keyword list "
- "truncated, can't happen (both same-size "
- "MBOX_FLAGS_MAX buffers); treating as not-found",
+ log_warnx("session %u: keyword list too long, no match",
session_id);
return (0);
}
for (tok = strtok_r(tmp, ",", &save); tok != NULL;
tok = strtok_r(NULL, ",", &save)) {
- /* RFC 9051 SS2.3.2: flags/keywords are case-insensitive */
+ /* RFC 9051 section 2.3.2: flags are case-insensitive */
if (strcasecmp(tok, kw) == 0)
return (1);
}
return (1);
}
-/* RFC 9051 SS6.4.6: SET ignores the old keywords */
+/* RFC 9051 section 6.4.6: SET ignores the old keywords */
int
merge_keywords(int mode, const char *old_kws, const char *new_kws,
char *out, size_t outsize)
if (mode == MBOX_STORE_REMOVE) {
if (strlcpy(tmp, old_kws, sizeof(tmp)) >= sizeof(tmp)) {
- log_warnx("session %u: merge_keywords: old_kws "
- "truncated, can't happen (same-size "
- "MBOX_FLAGS_MAX buffers); refusing to guess at "
- "the keyword set", session_id);
+ log_warnx("session %u: STORE: old keyword list too "
+ "long", session_id);
return (-1);
}
for (tok = strtok_r(tmp, ",", &save); tok != NULL;
if (mode == MBOX_STORE_ADD) {
if (strlcpy(tmp, old_kws, sizeof(tmp)) >= sizeof(tmp)) {
- log_warnx("session %u: merge_keywords: old_kws "
- "truncated, can't happen (same-size "
- "MBOX_FLAGS_MAX buffers); refusing to guess at "
- "the keyword set", session_id);
+ log_warnx("session %u: STORE: old keyword list too "
+ "long", session_id);
return (-1);
}
for (tok = strtok_r(tmp, ",", &save); tok != NULL;
}
if (strlcpy(tmp, new_kws, sizeof(tmp)) >= sizeof(tmp)) {
- log_warnx("session %u: merge_keywords: new_kws truncated, "
- "can't happen (same-size MBOX_FLAGS_MAX buffers); "
- "refusing to guess at the keyword set", session_id);
+ log_warnx("session %u: STORE: new keyword list too long",
+ session_id);
return (-1);
}
for (tok = strtok_r(tmp, ",", &save); tok != NULL;
const char *keywords;
int64_t internaldate;
uint64_t size;
- uint64_t modseq; /* RFC 7162 SS3.1.5 MODSEQ search key */
- const struct seq_range *saved;
+ uint64_t modseq; /* RFC 7162 section 3.1.5 MODSEQ key */
+ const struct seq_range *saved;
uint32_t nsaved;
};
}
}
-/* 1 for the RFC 9051 SS6.4.4 keys the parser-worker answers */
+/* 1 for the RFC 9051 section 6.4.4 keys the parser-worker answers */
int
search_op_content(int op)
{
if (sw->active) {
/* unreachable while the listener holds the next command */
- log_warnx("session %u: SEARCH arrived during a SEARCH, "
- "abandoning the earlier walk", session_id);
+ log_warnx("session %u: SEARCH during a SEARCH, earlier walk "
+ "abandoned", session_id);
search_walk_abort(ss);
}
memset(sw, 0, sizeof(*sw));
return (0);
}
-/* answers 0 or 1, or -1 if it could not be checked */
+/* 0 or 1; -1 if it could not be checked; -2 while no parser is ready */
static int
search_walk_ask(struct store_session *ss, const struct search_msg_ctx *m,
const char *basename)
}
if ((mfd = open_message_file(&ss->cur_snap, ss->mailbox_dir_fd,
basename)) == -1) {
- log_warnx("session %u: message %s cannot be opened, SEARCH "
- "cannot check it", session_id, basename);
+ log_warnx("session %u: message %s: open failed, SEARCH cannot "
+ "check it", session_id, basename);
return (-1);
}
memset(&preq, 0, sizeof(preq));
if (++sw->walked > SEARCH_YIELD_MESSAGES) {
sw->walked = 0;
sw->next = i;
- tv.tv_sec = 0;
- tv.tv_usec = 0;
+ timerclear(&tv);
evtimer_add(&sw->yield_ev, &tv);
return;
}
if (sw->asked >= SEARCH_YIELD_REQUESTS) {
sw->asked = 0;
sw->next = i;
- tv.tv_sec = 0;
- tv.tv_usec = 0;
+ timerclear(&tv);
evtimer_add(&sw->yield_ev, &tv);
return;
}
sw->active = 0;
}
-/* RFC 9051 SS6.4.4.1: matches on adjacent index lines share a range */
+/* RFC 9051 section 6.4.4.1: matches on adjacent index lines share a range */
static void
search_save_match(struct store_search_walk *sw, uint32_t line, uint32_t uid)
{
sw->sv_line = line;
}
-/* RFC 9051 SS6.4.4.1 and Table 4; 0 when refused, as NOTSAVED */
+/* RFC 9051 section 6.4.4.1 and Table 4; 0 when refused, as NOTSAVED */
static int
search_save_finish(struct store_session *ss, int ok)
{
{
struct store_session *ss = arg;
- (void)fd;
- (void)event;
session_id = ss->id;
if (ss->search.active)
search_walk_step(ss);
}
search_walk_step(ss);
}
-
blob - 7bb5732cb223f7c13c61e2c4dcfe219cd96454ab
blob + 875bad33c23f506e33a31f3cf3038b986b00ae65
--- src/mbox_store.c
+++ src/mbox_store.c
break;
}
- /* RFC 9051 SS6.4.6: refuse rather than store a truncation */
+ /* RFC 9051 section 6.4.6: refuse rather than store a truncation */
if (merge_keywords(req->mode, rec->keywords, req->keywords,
newkeywords, sizeof(newkeywords)) == -1) {
- log_warnx("session %u: STORE: merged keyword set for uid %u "
- "exceeds %zu bytes, failing STORE", session_id, rec->uid,
- sizeof(newkeywords));
+ log_warnx("session %u: STORE: uid %u: merged keywords over "
+ "%zu bytes", session_id, rec->uid, sizeof(newkeywords));
return (-1);
}
sysflags_to_letters(new_sysflags, st->letters, sizeof(st->letters));
}
}
-/* RFC 9051 SS6.4.6 STORE: plan every message, then change; LOCK_EX held */
+/* RFC 9051 section 6.4.6 STORE: plan all, then change; LOCK_EX held */
int
store_apply(struct store_session *ss, const struct imsg_mbox_store *req,
struct mbox_index *idx, const struct seq_range *resolved,
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 vr[SEQSET_MAX_RANGES];
struct store_step *steps = NULL;
size_t nsteps = 0, k;
+ uint64_t reported = 0;
uint32_t nresolved, nvr, sent = 0;
- uint64_t reported = 0;
int ok = 1, by_uid, synced, used, rc;
- int ghost, limit = 0;
+ int ghost, limit = 0, locked;
memset(&idx, 0, sizeof(idx));
if (!index_field_valid(req->keywords)) {
- log_warnx("session %u: STORE: refusing unsafe keywords field",
+ log_warnx("session %u: STORE: unsafe keywords field",
session_id);
ok = 0;
goto done;
reported = idx.highestmodseq;
- /* RFC 9051 SS6.4.9 */
+ /* RFC 9051 section 6.4.9 */
nresolved = seqset_resolve(vr, nvr, by_uid ?
index_max_uid(&idx) : (uint32_t)idx.nlines, !by_uid, resolved);
ghost = !req->by_uid && !used && view_names_ghost(ss, &idx,
continue;
}
- /* RFC 7162 SS3.1.3: UNCHANGEDSINCE echoes even under .SILENT */
+ /* RFC 7162 section 3.1.3: UNCHANGEDSINCE echoes with .SILENT */
if (!req->silent || req->has_unchangedsince) {
struct imsg_mbox_fetch_meta meta;
char newsuffix[16];
free(steps);
memset(&result, 0, sizeof(result));
- /* RFC 2180 SS4.2.1-SS4.2.3 */
+ /* RFC 2180 sections 4.2.1-4.2.3 */
result.error = !ok ? (limit ? MBOX_OP_ERR_LIMIT :
MBOX_OP_ERR_GENERIC) : ghost && !req->silent ?
MBOX_OP_ERR_EXPUNGEISSUED : MBOX_OP_OK;
out = 0;
for (in = 0; in < idx.nlines; in++) {
struct index_rec rec;
- const char *lp;
- uint32_t sysflags;
- char suffix[64];
+ const char *lp;
+ char suffix[64];
+ uint32_t sysflags;
if (index_parse_line(idx.lines[in], &rec) == -1) {
idx.lines[out++] = idx.lines[in];
if (locate_message_file(&ss->cur_snap, ss->mailbox_dir_fd,
rec.basename, NULL, suffix, sizeof(suffix)) == -1) {
log_warnx("session %u: message %s indexed but missing "
- "on disk, kept in index, not counted as "
- "expunged", session_id, rec.basename);
+ "on disk, kept, not expunged",
+ session_id, rec.basename);
idx.lines[out++] = idx.lines[in];
continue;
}
idx.lines[out++] = idx.lines[in];
continue;
}
- /* RFC 9051 SS6.4.9: \Deleted outside the ranges is kept */
+ /* RFC 9051 section 6.4.9: \Deleted outside the set is kept */
if (req->by_uid &&
!seqset_contains(resolved, nresolved, rec.uid)) {
idx.lines[out++] = idx.lines[in];
for (k = 0; k < ngone; k++) {
struct index_rec rec;
- char path[600];
+ char path[600];
if (index_parse_line(gone[k].line, &rec) == -1)
continue;
}
done:
- /* RFC 9051 SS6.4.1: CLOSE deselects only once its expunge is saved. */
+ /* RFC 9051 section 6.4.1: CLOSE deselects once its expunge is saved */
if (req->silent && ok)
ss->mailbox_selected = 0;
blob - f0673170737641f25ea55077ef7a880eec3800ad
blob + 061da5af7ca779d4c9007d8673d3754560c9c766
--- src/mboxname.c
+++ src/mboxname.c
#include "mboxname.h"
#include "utf8.h"
-/* RFC 9051 SS5.1: INBOX is case-insensitive */
+/* RFC 9051 section 5.1: INBOX is case-insensitive */
int
mailbox_name_is_inbox(const char *name)
{
for (i = 0; i < len; i++) {
unsigned char c = (unsigned char)name[i];
- /* RFC 9051 SS5.1.1: "/" is the hierarchy delimiter */
+ /* RFC 9051 section 5.1.1: "/" is the hierarchy delimiter */
if (c == '/')
return (0);
- /* RFC 9051 SS5.1 lets a server refuse CTL names */
+ /* RFC 9051 section 5.1 lets a server refuse CTL names */
if (c < 0x20 || c == 0x7f)
return (0);
}
blob - 66731709f1033ef153c187bd122bfb804719994d
blob + 16a1c724e3db800c87639c65f736eebf05a1e92c
--- src/mboxname.h
+++ src/mboxname.h
int mailbox_name_reserved(const char *);
-/* RFC 9051 SS5.1: INBOX is case-insensitive and always exists */
+/* RFC 9051 section 5.1: INBOX is case-insensitive and always exists */
int mailbox_name_is_inbox(const char *);
#endif /* IMAPD_MBOXNAME_H */
blob - b31e1c0eec8e826c1f842dc5fa52f2781378025e
blob + 139f080299a8866b11aabd0d6578e8ca39bcf134
--- src/mime.c
+++ src/mime.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <sys/param.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
static int
cur_snapshot_build(struct cur_snapshot *snap, int dfd)
{
- DIR *dp = NULL;
+ DIR *dp;
const struct dirent *de;
char **names = NULL, **tmp;
size_t n = 0, cap = 0;
- int curfd;
cur_snapshot_discard(snap);
- curfd = openat(dfd, "cur", O_RDONLY | O_DIRECTORY);
- if (curfd != -1 && (dp = fdopendir(curfd)) == NULL)
- close(curfd);
- if (dp == NULL) {
+ if ((dp = opendirat(dfd, "cur")) == NULL) {
snap->failed = 1;
return (-1);
}
return (-1);
if (suffix_out != NULL && strlcpy(suffix_out, name + baselen,
suffix_out_size) >= suffix_out_size) {
- log_warnx("session %u: %s: flag suffix truncated, refusing "
- "to report a possibly-wrong flag set", session_id, name);
+ log_warnx("session %u: %s: flag suffix truncated",
+ session_id, name);
return (-1);
}
return (0);
DIR *dp;
const struct dirent *de;
const char *hit;
- size_t baselen = strlen(basename);
+ size_t baselen;
int rv = -1;
+ baselen = strlen(basename);
if ((hit = cur_snapshot_find(snap, dfd, basename, baselen)) != NULL)
return (cur_entry_take(hit, baselen, path, pathsize,
suffix_out, suffix_out_size));
/* absent from the snapshot, so it may have arrived since: look */
- {
- int curfd;
-
- dp = NULL;
- curfd = openat(dfd, "cur", O_RDONLY | O_DIRECTORY);
- if (curfd != -1 && (dp = fdopendir(curfd)) == NULL)
- close(curfd);
- }
- if (dp == NULL) {
+ if ((dp = opendirat(dfd, "cur")) == NULL) {
if (errno != ENOENT)
log_warn("session %u: opendir cur", session_id);
return (-1);
struct stat st;
const char *hit;
char path[600];
- size_t baselen = strlen(basename);
+ size_t baselen;
+ baselen = strlen(basename);
suffix_out[0] = '\0';
if ((hit = cur_snapshot_find(snap, dfd, basename, baselen)) != NULL) {
limit = found_sep ? sepindex : (size_t)total;
for (i = 0; i < limit && i + 1 < (size_t)total; i++) {
if (readbuf[i] == '\0') {
- log_warnx("session %u: message %s has a NUL byte in "
- "its header, BODY.PEEK[HEADER] skipped",
- session_id, basename);
+ log_warnx("session %u: message %s: NUL in header, "
+ "BODY.PEEK[HEADER] skipped", session_id, basename);
return (-1);
}
}
if (!found_sep || sepindex > FETCH_HEADER_MAX) {
- log_warnx("session %u: message %s: no header/body separator "
- "found within %d bytes, BODY.PEEK[HEADER] skipped",
+ log_warnx("session %u: message %s: no header end within %d "
+ "bytes, BODY.PEEK[HEADER] skipped",
session_id, basename, FETCH_HEADER_MAX);
return (-1);
}
n = read(fd, readbuf + total, readbuf_size - total);
if (n == -1) {
if (errno == EINTR)
- /* as mbox_copy.c's staging read does */
continue;
log_warn("session %u: read message body (%s)",
session_id, basename);
return (-1);
}
-/* RFC 9051 SS6.4.5.1: ASCII case-insensitive */
+/* RFC 9051 section 6.4.5.1: ASCII case-insensitive */
int
header_field_name_matches(const char *name, size_t namelen, const char *list)
{
if (namelen == 0 || namelen >= sizeof(listcopy))
return (0);
- if (strlcpy(listcopy, list, sizeof(listcopy)) >= sizeof(listcopy)) {
- log_warnx("session %u: header_field_name_matches: list "
- "truncated, can't happen (list bounded by same-size "
- "HEADER_FIELDS_MAX buffer upstream); treating as "
- "not-found", session_id);
- return (0);
- }
+ strlcpy(listcopy, list, sizeof(listcopy));
for (tok = strtok_r(listcopy, " ", &save); tok != NULL;
tok = strtok_r(NULL, " ", &save)) {
if (strlen(tok) == namelen &&
return (0);
}
-/* one field plus its RFC 5322 SS2.2.3 obs-fold lines */
+/* one field plus its RFC 5322 section 2.2.3 obs-fold lines */
struct hdr_field {
size_t start;
size_t name_end;
if (hdr[f->colon] == ':')
break;
}
- /* RFC 5322 SS4.5: white space may precede the colon */
+ /* RFC 5322 section 4.5: white space may precede the colon */
f->name_end = f->colon;
while (f->colon < f->line_end && f->name_end > f->start &&
(hdr[f->name_end - 1] == ' ' || hdr[f->name_end - 1] == '\t'))
return (1);
}
-/* RFC 9051 SS6.4.5.1 HEADER.FIELDS[.NOT] */
+/* RFC 9051 section 6.4.5.1 HEADER.FIELDS[.NOT] */
int
filter_header_fields(const char *hdr, size_t hdrlen, const char *fields_spec,
int want_not, char *out, size_t *outlen_out)
/* no terminating blank line */
return (-1);
if (r == 0) {
- /* RFC 9051 SS6.4.5: the blank line is included */
+ /* RFC 9051 section 6.4.5: the blank line is included */
memcpy(out + outlen, hdr + f.start, f.end - f.start);
outlen += f.end - f.start;
*outlen_out = outlen;
return (0);
}
-/* RFC 5322 SS2.2.3 unfolding; NIL vs "" per RFC 9051 SS7.5.2 */
+/* RFC 5322 section 2.2.3 unfolding; NIL vs "" per RFC 9051 section 7.5.2 */
int
extract_header_field(const char *hdr, size_t hdrlen, const char *name,
char **val_out, size_t *vallen_out)
{
- size_t namelen = strlen(name);
+ size_t namelen;
size_t off = 0;
+ namelen = strlen(name);
*val_out = NULL;
*vallen_out = 0;
for (;;) {
if (hdr_next_field(hdr, hdrlen, off, &f) != 1)
return (0);
- /* a line with no colon is not a field (RFC 5322 SS2.2) */
+ /* a line with no colon is not a field (RFC 5322 section 2.2) */
if (f.colon == f.line_end)
continue;
vstart = f.colon + 1;
return (-1);
}
-/* RFC 2045 SS5.1 tspecials; a `token` excludes SPACE, CTLs, and these */
+/* RFC 2045 section 5.1 tspecials; a `token` excludes SPACE, CTLs, and these */
int
mime_is_tspecial(char c)
{
(*pos)++;
c = s[*pos];
}
- /* RFC 5322 SS4: keep CR and LF; a NUL would end out */
+ /* keep CR and LF (RFC 5322 section 4); NUL ends out */
if (c == '\0')
c = ' ';
if (outlen + 1 >= outsize)
}
}
-/* RFC 2045 SS5.1 Content-Type; SS5.2 default on failure */
+/* RFC 2045 section 5.1 Content-Type; section 5.2 default on failure */
int
-parse_content_type(const char *hdr, size_t hdrlen, char *type_out,
- size_t typesize, char *subtype_out, size_t subtypesize,
- char *params_fmt_out, size_t params_fmt_outsize, char *boundary_out,
- size_t boundary_outsize, int *has_boundary_out)
+parse_content_type(const char *hdr, size_t hdrlen, struct content_type *ct)
{
char *val = NULL;
size_t vallen = 0;
int nparams = 0;
int use_default = 0;
/* -1: envbuf_append*() never NUL-terminates, reserve a byte */
- size_t pfsize = (params_fmt_outsize > 0) ? params_fmt_outsize - 1 : 0;
+ size_t pfsize = sizeof(ct->params_fmt) - 1;
- *has_boundary_out = 0;
- boundary_out[0] = '\0';
+ memset(ct, 0, sizeof(*ct));
- if (pfsize == 0)
- return (-1);
-
if (extract_header_field(hdr, hdrlen, "Content-Type", &val,
&vallen) == -1 || vallen == 0)
use_default = 1;
if (!use_default && (mime_read_token_or_qstring(val, vallen, &pos,
- type_out, typesize) == -1 || pos >= vallen || val[pos] != '/'))
+ ct->type, sizeof(ct->type)) == -1 || pos >= vallen ||
+ val[pos] != '/'))
use_default = 1;
if (!use_default) {
pos++;
- if (mime_read_token_or_qstring(val, vallen, &pos, subtype_out,
- subtypesize) == -1)
+ if (mime_read_token_or_qstring(val, vallen, &pos, ct->subtype,
+ sizeof(ct->subtype)) == -1)
use_default = 1;
}
if (use_default) {
free(val);
- if (strlcpy(type_out, "TEXT", typesize) >= typesize ||
- strlcpy(subtype_out, "PLAIN", subtypesize) >=
- subtypesize) {
- log_warnx("session %u: parse_content_type: default "
- "type/subtype truncated, caller-supplied "
- "buffer too small for \"TEXT\"/\"PLAIN\"",
- session_id);
- return (-1);
- }
- if (envbuf_append_str(params_fmt_out, pfsize, &plen,
+ strlcpy(ct->type, "TEXT", sizeof(ct->type));
+ strlcpy(ct->subtype, "PLAIN", sizeof(ct->subtype));
+ if (envbuf_append_str(ct->params_fmt, pfsize, &plen,
"(\"CHARSET\" \"US-ASCII\")") == -1)
return (-1);
- params_fmt_out[plen] = '\0';
+ ct->params_fmt[plen] = '\0';
return (0);
}
- mime_str_upper(type_out);
- mime_str_upper(subtype_out);
+ mime_str_upper(ct->type);
+ mime_str_upper(ct->subtype);
for (;;) {
char attr[64], value[256];
break;
mime_str_upper(attr);
- if (envbuf_append_str(params_fmt_out, pfsize,
+ if (envbuf_append_str(ct->params_fmt, pfsize,
&plen, nparams == 0 ? "(" : " ") == -1 ||
- envbuf_append_nstring(params_fmt_out, pfsize,
+ envbuf_append_nstring(ct->params_fmt, pfsize,
&plen, attr, strlen(attr)) == -1 ||
- envbuf_append(params_fmt_out, pfsize, &plen,
+ envbuf_append(ct->params_fmt, pfsize, &plen,
" ", 1) == -1 ||
- envbuf_append_nstring(params_fmt_out, pfsize,
+ envbuf_append_nstring(ct->params_fmt, pfsize,
&plen, value, strlen(value)) == -1) {
free(val);
return (-1);
}
nparams++;
- if (strcasecmp(attr, "BOUNDARY") == 0) {
- /* RFC 2046 SS5.1.1: at most 70 characters */
- if (strlcpy(boundary_out, value, boundary_outsize) <
- boundary_outsize)
- *has_boundary_out = 1;
- }
+ if (strcasecmp(attr, "BOUNDARY") == 0 &&
+ strlcpy(ct->boundary, value, sizeof(ct->boundary)) <
+ sizeof(ct->boundary))
+ ct->has_boundary = 1;
}
free(val);
if (nparams > 0) {
- if (envbuf_append_str(params_fmt_out, pfsize, &plen,
+ if (envbuf_append_str(ct->params_fmt, pfsize, &plen,
")") == -1)
return (-1);
- params_fmt_out[plen] = '\0';
+ ct->params_fmt[plen] = '\0';
return (0);
}
- if (envbuf_append_str(params_fmt_out, pfsize, &plen, "NIL") == -1)
+ if (envbuf_append_str(ct->params_fmt, pfsize, &plen, "NIL") == -1)
return (-1);
- params_fmt_out[plen] = '\0';
+ ct->params_fmt[plen] = '\0';
return (0);
}
-/* RFC 2046 SS5.1.1 */
+/* RFC 2046 section 5.1.1 */
int
split_multipart(const char *body, size_t bodylen, const char *boundary,
size_t *part_starts, size_t *part_ends, int *nparts_out, int maxparts)
{
- /* "--" + boundary; RFC 2046 SS5.1.1 caps boundary at 70 characters */
+ /* "--" + boundary; RFC 2046 section 5.1.1 caps it at 70 characters */
char needle[2 + 70 + 1];
size_t needlelen;
size_t pos = 0;
int n = 0;
int closed = 0;
- if (strlcpy(needle, "--", sizeof(needle)) >= sizeof(needle)) {
- log_warnx("session %u: split_multipart: \"--\" truncated, "
- "can't happen (2 bytes into a 73-byte buffer)",
- session_id);
- return (-1);
- }
+ strlcpy(needle, "--", sizeof(needle));
needlelen = strlcat(needle, boundary, sizeof(needle));
if (needlelen >= sizeof(needle))
return (-1); /* boundary too long, non-conformant */
return (0);
}
-/* RFC 9051 SS6.4.5 section-part */
+/* RFC 9051 section 6.4.5 section-part */
int
parse_section_part(const char *s, int *path, int maxpath)
{
return (-1);
s++;
if (*s == '\0')
- return (-1); /* trailing dot */
+ return (-1);
}
return (n);
}
size_t bodylen, const int *path, int pathlen, const char **part_out,
size_t *partlen_out)
{
- char type[64], subtype[64];
- char params_fmt[600];
- char boundary[70 + 1];
- int has_boundary;
- size_t part_starts[MIME_MAX_PARTS], part_ends[MIME_MAX_PARTS];
- int n, want;
- const char *pbuf, *pbody;
- size_t plen, phdrend, pbodylen, ih;
+ struct content_type ct;
+ const char *pbuf, *pbody;
+ size_t part_starts[MIME_MAX_PARTS];
+ size_t part_ends[MIME_MAX_PARTS];
+ size_t plen, phdrend, pbodylen, ih;
+ int n, want;
if (depth > MIME_MAX_DEPTH)
return (-1);
- params_fmt[0] = '\0';
- if (parse_content_type(hdr, hdrlen, type, sizeof(type), subtype,
- sizeof(subtype), params_fmt, sizeof(params_fmt), boundary,
- sizeof(boundary), &has_boundary) == -1)
+ if (parse_content_type(hdr, hdrlen, &ct) == -1)
return (-1);
- if (strcasecmp(type, "MULTIPART") != 0 || !has_boundary)
+ if (strcasecmp(ct.type, "MULTIPART") != 0 || !ct.has_boundary)
return (-1);
- if (split_multipart(body, bodylen, boundary, part_starts, part_ends,
- &n, MIME_MAX_PARTS) == -1)
+ if (split_multipart(body, bodylen, ct.boundary, part_starts,
+ part_ends, &n, MIME_MAX_PARTS) == -1)
return (-1);
want = path[0];
else if (find_header_body_split(pbuf, plen, &phdrend) == -1)
return (-1);
- params_fmt[0] = '\0';
- if (parse_content_type(pbuf, phdrend, type, sizeof(type), subtype,
- sizeof(subtype), params_fmt, sizeof(params_fmt), boundary,
- sizeof(boundary), &has_boundary) == -1)
+ if (parse_content_type(pbuf, phdrend, &ct) == -1)
return (-1);
pbody = pbuf + phdrend;
pbodylen = plen - phdrend;
if (pathlen == 1) {
/* a leaf or a message: no "combined children" of a multipart */
- if (strcasecmp(type, "MULTIPART") == 0)
+ if (strcasecmp(ct.type, "MULTIPART") == 0)
return (-1);
*part_out = pbody;
*partlen_out = pbodylen;
return (0);
}
- /* RFC 9051 SS6.4.5.1: a message's parts are numbered within it */
- if (strcasecmp(type, "MESSAGE") == 0 &&
- (strcasecmp(subtype, "RFC822") == 0 ||
- strcasecmp(subtype, "GLOBAL") == 0)) {
+ /* RFC 9051 section 6.4.5.1: a message's parts are numbered within it */
+ if (strcasecmp(ct.type, "MESSAGE") == 0 &&
+ (strcasecmp(ct.subtype, "RFC822") == 0 ||
+ strcasecmp(ct.subtype, "GLOBAL") == 0)) {
if (pbodylen == 0 ||
find_header_body_split(pbody, pbodylen, &ih) == -1)
return (-1);
path + 1, pathlen - 1, part_out, partlen_out));
}
-/* RFC 9051 SS6.4.5.1: a non-multipart message is its own part 1 */
+/* RFC 9051 section 6.4.5.1: a non-multipart message is its own part 1 */
int
locate_mime_part(int depth, const char *hdr, size_t hdrlen,
const char *body, size_t bodylen, const int *path, int pathlen,
const char **part_out, size_t *partlen_out)
{
- char type[64], subtype[64];
- char params_fmt[600];
- char boundary[70 + 1];
- int has_boundary;
- size_t ih;
+ struct content_type ct;
+ size_t ih;
if (depth > MIME_MAX_DEPTH || pathlen < 1)
return (-1);
- params_fmt[0] = '\0';
- if (parse_content_type(hdr, hdrlen, type, sizeof(type), subtype,
- sizeof(subtype), params_fmt, sizeof(params_fmt), boundary,
- sizeof(boundary), &has_boundary) == -1)
+ if (parse_content_type(hdr, hdrlen, &ct) == -1)
return (-1);
- if (strcasecmp(type, "MULTIPART") == 0)
+ if (strcasecmp(ct.type, "MULTIPART") == 0)
return (find_mime_part(depth + 1, hdr, hdrlen, body, bodylen,
path, pathlen, part_out, partlen_out));
*partlen_out = bodylen;
return (0);
}
- if (strcasecmp(type, "MESSAGE") != 0 ||
- (strcasecmp(subtype, "RFC822") != 0 &&
- strcasecmp(subtype, "GLOBAL") != 0) || bodylen == 0 ||
+ if (strcasecmp(ct.type, "MESSAGE") != 0 ||
+ (strcasecmp(ct.subtype, "RFC822") != 0 &&
+ strcasecmp(ct.subtype, "GLOBAL") != 0) || bodylen == 0 ||
find_header_body_split(body, bodylen, &ih) == -1)
return (-1);
return (locate_mime_part(depth + 1, body, ih, body + ih,
bodylen - ih, path + 1, pathlen - 1, part_out, partlen_out));
}
-/* RFC 9051 SS6.4.5 "<start.count>" applied to a range; past its end is empty */
+/* RFC 9051 section 6.4.5 <start.count> on a range; past the end is empty */
void
partial_range(int has_partial, uint32_t start, uint32_t count,
uint64_t *off, uint64_t *len)
return (-1);
}
- /* RFC 9051 SS6.4.5: a missing part is an empty item */
+ /* RFC 9051 section 6.4.5: a missing part is an empty item */
if (locate_mime_part(0, wholebuf, hdrend, wholebuf + hdrend,
wholelen - hdrend, path, pathlen, &part, &partlen) == 0) {
*off_out = (uint64_t)(part - wholebuf);
}
if (!found) {
- log_warnx("session %u: message %s: no header/body separator "
- "found, BODY[TEXT] skipped", session_id, basename);
+ log_warnx("session %u: message %s: no header end, BODY[TEXT] "
+ "skipped", session_id, basename);
close(fd);
return (-1);
}
return (fd);
}
+static int
+flag_append(char *out, size_t outsize, int *first, const char *flag)
+{
+ if ((!*first && strlcat(out, " ", outsize) >= outsize) ||
+ strlcat(out, flag, outsize) >= outsize) {
+ log_warnx("session %u: flag list truncated", session_id);
+ return (0);
+ }
+ *first = 0;
+ return (1);
+}
+
/* flag letters as in Courier's maildir(5) */
void
build_flags_string(const char *maildir_suffix, const char *keywords,
{ 'T', "\\Deleted" },
};
const char *letters;
+ char *kw, *save;
+ char kwbuf[512];
size_t i;
int first = 1;
letters = strstr(maildir_suffix, "2,");
letters = (letters != NULL) ? letters + 2 : "";
- for (i = 0; i < sizeof(stdflags) / sizeof(stdflags[0]); i++) {
- if (strchr(letters, stdflags[i].letter) == NULL)
- continue;
- if (!first && strlcat(out, " ", outsize) >= outsize) {
- log_warnx("session %u: build_flags_string: out "
- "truncated, caller-supplied buffer too small "
- "for the flag list; stopping early", session_id);
+ for (i = 0; i < nitems(stdflags); i++) {
+ if (strchr(letters, stdflags[i].letter) != NULL &&
+ !flag_append(out, outsize, &first, stdflags[i].flag))
return;
- }
- if (strlcat(out, stdflags[i].flag, outsize) >= outsize) {
- log_warnx("session %u: build_flags_string: out "
- "truncated, caller-supplied buffer too small "
- "for the flag list; stopping early", session_id);
- return;
- }
- first = 0;
}
- if (keywords != NULL && keywords[0] != '\0') {
- char kwbuf[512];
- char *kw, *save;
-
- if (strlcpy(kwbuf, keywords, sizeof(kwbuf)) >= sizeof(kwbuf)) {
- log_warnx("session %u: build_flags_string: keywords "
- "too long for kwbuf, omitting keyword flags "
- "rather than guessing at a truncated list",
- session_id);
+ if (keywords == NULL || keywords[0] == '\0')
+ return;
+ strlcpy(kwbuf, keywords, sizeof(kwbuf));
+ for (kw = strtok_r(kwbuf, ",", &save); kw != NULL;
+ kw = strtok_r(NULL, ",", &save)) {
+ if (!flag_append(out, outsize, &first, kw))
return;
- }
- for (kw = strtok_r(kwbuf, ",", &save); kw != NULL;
- kw = strtok_r(NULL, ",", &save)) {
- if (!first && strlcat(out, " ", outsize) >= outsize) {
- log_warnx("session %u: build_flags_string: "
- "out truncated, caller-supplied "
- "buffer too small for the flag list; "
- "stopping early", session_id);
- return;
- }
- if (strlcat(out, kw, outsize) >= outsize) {
- log_warnx("session %u: build_flags_string: "
- "out truncated, caller-supplied "
- "buffer too small for the flag list; "
- "stopping early", session_id);
- return;
- }
- first = 0;
- }
}
}
-/* RFC 9051 SS2.3.3: from the basename, not mtime */
+/* RFC 9051 section 2.3.3: from the basename, not mtime */
int64_t
parse_maildir_timestamp(const char *basename)
{
blob - e6ffc62566f5ef03659e82ca925bbb36182680d5
blob + bf61ced9817a15ffeeaaf4c578c6c3ad2bb296c4
--- src/parent.c
+++ src/parent.c
* Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
* Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org>
*
- * This file's boot-time peer-wiring handshake -- setup_peer_send() and
- * setup_done_send(), and the IMSG_SETUP_PEER/IMSG_SETUP_DONE message
- * names they and imapd.h use -- follows smtpd's setup_peers()/
- * setup_done() (usr.sbin/smtpd/smtpd.c:861-902) and its identically-
- * named IMSG_SETUP_PEER/IMSG_SETUP_DONE enumerators (smtpd.h:211-213),
- * which no other daemon in the OpenBSD base system defines under those
- * names. The functions here are reworked onto imapd's own struct imsgev
- * and multi-child bookkeeping rather than copied verbatim, but the
- * message protocol and handshake shape are smtpd's; see the inline
- * citations at this file's setup_peer_send() and setup_done_send().
- *
- * send_keymgr_init()'s TLS key permission check -- fstat(2), then
- * st_uid != 0 and st_mode & (S_IRWXU|S_IRWXG|S_IRWXO) & ~0740 -- reuses
- * smtpd's ssl_load_key() check (usr.sbin/smtpd/ssl.c:112-140) verbatim
- * for that mask and rejection order; only the fixed 0740 bound and the
- * imsg delivery around it are this file's own.
- *
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <sys/param.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/resource.h>
static int store_child_attach(struct store_child *,
struct open_session *);
static void store_child_session_gone(struct store_child *);
+static void store_child_wire(struct store_child *);
static void store_child_parser(struct store_child *);
static void store_child_dispatch(int, short, void *);
static void store_child_timeout(int, short, void *);
int i;
struct rlimit rl;
- (void)argc;
-
if (geteuid() != 0)
fatalx("parent must start as root (running as uid %u)",
(unsigned int)geteuid());
bind_listen_socket(conf->listen_addr, conf->port_implicit_tls,
tls_fds);
if (n_cleartext == 0 && n_tls == 0)
- fatalx("no listener configured: imapd.conf named no usable "
- "\"listen on\" address/port");
+ fatalx("no usable \"listen on\" address in imapd.conf");
event_init();
/* no setproctitle() here: rc.d matches the parent's command line */
/* proc exec sendfd: every connection forks and fd-passes */
-#ifdef __OpenBSD__
if (pledge("stdio rpath inet proc exec sendfd", NULL) == -1)
fatal("pledge");
-#endif
event_dispatch();
fatalx("parent: exited event loop");
nargv[n++] = progpath;
nargv[n++] = "-x";
/* execv(3) takes char *const argv[] but never writes through it */
- nargv[n++] = (char *)(uintptr_t)role;
+ nargv[n++] = (char *)role;
for (i = 1; saved_argv[i] != NULL; i++) {
if (strcmp(saved_argv[i], "-x") == 0) {
/* a trailing -x would read past argv's NULL */
i++;
continue;
}
- if (n >= (int)(sizeof(nargv) / sizeof(nargv[0])) - 1) {
+ if (n >= (int)nitems(nargv) - 1) {
/* truncating could split an option from its value */
- log_warnx("exec_self_as: argv too long to pass to the "
- "%s child, refusing to exec a truncated command "
- "line", role);
+ log_warnx("exec_self_as: argv too long for the %s "
+ "child", role);
_exit(1);
}
nargv[n++] = saved_argv[i];
/* check against sessions the parent knows */
if ((os = open_session_find(req.session_id)) == NULL) {
- log_warnx("refusing IMSG_AUTH_CRED for session "
- "%u: not a session parent knows is open "
- "(never opened, already closed, or evicted "
- "by OPEN_SESSION_MAX)",
- req.session_id);
+ log_warnx("IMSG_AUTH_CRED for unknown session "
+ "%u, refused", req.session_id);
break;
}
/* only this session's own auth-worker may grant it */
if (iev != os->auth_iev) {
- log_warnx("refusing IMSG_AUTH_CRED for "
- "session %u: not from that session's own "
- "auth-worker channel",
+ log_warnx("IMSG_AUTH_CRED for session %u from "
+ "the wrong channel, refused",
req.session_id);
break;
}
if (os->authenticated) {
- log_warnx("refusing IMSG_AUTH_CRED for session "
- "%u: already authenticated, refusing "
- "duplicate grant", req.session_id);
+ log_warnx("IMSG_AUTH_CRED for session %u: "
+ "already authenticated", req.session_id);
/* reply: the listener waits with no timeout */
store_fork_failed(os, 0);
break;
imsg_free(&imsg);
}
imsgev_rearm_read(iev);
- (void)fd;
}
/* reap_child() frees c once SIGCHLD reports the exit */
{
int i;
- (void)fd; (void)event; (void)arg;
-
if (shutting_down)
return;
for (i = 0; i < n_cleartext; i++)
return (n);
}
-/* RFC 9051 SS7.1.5 BYE, on the cleartext port only */
+/* RFC 9051 section 7.1.5 BYE, on the cleartext port only */
static void
refuse_connection(int fd, int cleartext)
{
int client_fd, flags, implicit_tls, on = 1;
unsigned int nstartups, nopen, nsource;
- (void)event;
-
if ((client_fd = accept(fd, (struct sockaddr *)&ss, &sslen)) == -1) {
/* else the listen event fires again at once, and spins */
if (errno == EMFILE || errno == ENFILE) {
nsource = count_startups_from(&ss);
if (nsource >= gconf->max_startups_per_source) {
log_debug("startups per-source: refusing connection "
- "(%u unauthenticated already open from its "
- "address)", nsource);
+ "(%u unauthenticated from its address)", nsource);
refuse_connection(client_fd, arg == (void *)0);
return;
}
TAILQ_FOREACH(it, &open_sessions, entry)
nopen++;
if (nopen >= OPEN_SESSION_MAX) {
- log_warnx("refusing connection: %u open sessions already "
- "tracked (OPEN_SESSION_MAX)", nopen);
+ log_warnx("refusing connection: %u sessions open "
+ "(OPEN_SESSION_MAX)", nopen);
close(client_fd);
return;
}
if ((listener_pid = fork_child_nonfatal(PROC_LISTENER,
&new_listener_iev, parent_dispatch_child)) == -1) {
- log_warnx("session %u: refusing connection: could not spawn "
- "a listener-worker", session_id);
+ log_warnx("session %u: refusing connection: no listener-worker",
+ session_id);
free(os);
close(client_fd);
return;
if ((auth_pid = fork_child_nonfatal(PROC_AUTH, &new_auth_iev,
parent_dispatch_child)) == -1) {
- log_warnx("session %u: no auth-worker available, this "
- "session's first AUTHENTICATE will fail instead of the "
- "connection itself (listener-worker detects a missing "
- "auth peer the same way it detects one dying later)",
+ log_warnx("session %u: no auth-worker for this connection",
session_id);
} else {
os->auth_pid = auth_pid;
if (imsg_compose(&new_listener_iev->ibuf, IMSG_LISTENER_SESSION_INIT,
0, 0, client_fd, &init, sizeof(init)) == -1) {
log_warnx("session %u: imsg_compose "
- "IMSG_LISTENER_SESSION_INIT failed, connection lost",
- session_id);
+ "IMSG_LISTENER_SESSION_INIT, connection lost", session_id);
close(client_fd);
goto fail_workers;
}
if (p == NULL || p[0] == '\0' || p[0] == '/')
return (0);
for (;;) {
- const char *slash = strchr(seg, '/');
- size_t len = slash ? (size_t)(slash - seg) :
- strlen(seg);
+ const char *slash;
+ size_t len;
+ slash = strchr(seg, '/');
+ len = slash ? (size_t)(slash - seg) : strlen(seg);
if (len == 1 && seg[0] == '.')
return (0);
if (len == 2 && seg[0] == '.' && seg[1] == '.')
strcmp(it->maildir, maildir) == 0) {
if (it->nsessions >= gconf->account_sessions) {
log_info("session %u: refusing login: uid %u "
- "already has %u sessions (account "
- "sessions)", session_id, (unsigned int)uid,
+ "has %u sessions (account sessions)",
+ session_id, (unsigned int)uid,
it->nsessions);
store_fork_failed(os, 1);
return;
if (strlcpy(init_payload.spool_root, gconf->spool_root,
sizeof(init_payload.spool_root)) >=
sizeof(init_payload.spool_root)) {
- log_warnx("session %u: spool_root truncated spawning store "
- "child, refusing to wire a session to a possibly "
- "wrong spool root", session_id);
+ log_warnx("session %u: spool_root truncated, store not spawned",
+ session_id);
goto fail_kill;
}
if (strlcpy(init_payload.maildir, maildir,
sizeof(init_payload.maildir)) >= sizeof(init_payload.maildir)) {
- log_warnx("session %u: maildir truncated spawning store "
- "child, refusing to wire a session to a possibly "
- "wrong mailbox", session_id);
+ log_warnx("session %u: maildir truncated, store not spawned",
+ session_id);
goto fail_kill;
}
init_payload.bodystructure_read_max = gconf->bodystructure_read_max;
static int
store_child_attach(struct store_child *sc, struct open_session *os)
{
- if (setup_peer_send(&sc->iev, os->listener_iev, IMSG_SETUP_PEER,
- "IMSG_SETUP_PEER", os->session_id) == -1)
+ if (!sc->pending && setup_peer_send(&sc->iev, os->listener_iev,
+ IMSG_SETUP_PEER, "IMSG_SETUP_PEER", os->session_id) == -1)
return (-1);
os->store = sc;
sc->nsessions++;
return (0);
}
+/* a login is answered OK only once its store is in the maildir */
+static void
+store_child_wire(struct store_child *sc)
+{
+ struct open_session *os;
+
+ TAILQ_FOREACH(os, &open_sessions, entry) {
+ if (os->store != sc || os->listener_iev == NULL)
+ continue;
+ if (setup_peer_send(&sc->iev, os->listener_iev,
+ IMSG_SETUP_PEER, "IMSG_SETUP_PEER", os->session_id) == 0)
+ continue;
+ os->store = NULL;
+ store_child_session_gone(sc);
+ store_fork_failed(os, 0);
+ }
+}
+
/* the parent, not the store child, decides when it is done */
static void
store_child_session_gone(struct store_child *sc)
evtimer_del(&sc->timeout_ev);
sc->pending = 0;
imsg_free(&imsg);
+ store_child_wire(sc);
continue;
}
if (imsg_get_type(&imsg) == IMSG_PARSER_WANT) {
imsg_free(&imsg);
}
imsgev_rearm_read(&sc->iev);
- (void)fd;
}
static void
{
struct store_child *sc = arg;
- (void)fd;
- (void)event;
log_warnx("session %u: store setup timed out after %ds",
sc->session_id, STORE_SETUP_TIMEOUT_SEC);
store_child_fail(sc);
n = fread(buf, 1, bufsize, fp);
if (n == bufsize) {
if (fgetc(fp) != EOF) {
- log_warnx("%s: larger than %zu bytes, refusing to "
- "send a truncated %s (TLS will be disabled)",
- path, bufsize, what);
+ log_warnx("%s: over %zu bytes, %s not sent, TLS "
+ "disabled", path, bufsize, what);
return (-1);
}
} else if (!feof(fp)) {
- log_warnx("%s: short read that wasn't a clean EOF, refusing "
- "to send a possibly-truncated %s (TLS will be disabled)",
+ log_warnx("%s: short read, %s not sent, TLS disabled",
path, what);
return (-1);
}
log_warn("fstat %s", conf->tls_key_file);
fclose(fp);
} else if (st.st_uid != 0) {
- log_warnx("%s: not owned by uid 0, refusing to load "
- "(TLS will be disabled)", conf->tls_key_file);
+ log_warnx("%s: not owned by uid 0, TLS disabled",
+ conf->tls_key_file);
fclose(fp);
} else if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~0740) {
- log_warnx("%s: insecure permissions, must be at most "
- "rwxr----- (TLS will be disabled)", conf->tls_key_file);
+ log_warnx("%s: insecure permissions, TLS disabled",
+ conf->tls_key_file);
fclose(fp);
} else {
if (read_file_capped(fp, buf, sizeof(buf), &n,
memset(&init, 0, sizeof(init));
if (strlcpy(init.cred_file, conf->cred_file, sizeof(init.cred_file))
>= sizeof(init.cred_file)) {
- log_warnx("cred_file truncated sending IMSG_AUTH_INIT, config "
- "value too long for the wire struct's field");
+ log_warnx("cred_file truncated, IMSG_AUTH_INIT not sent");
return (-1);
}
{
struct openimap_config newconf;
- (void)fd; (void)event; (void)arg;
-
log_info("SIGHUP: reloading %s", conf_path);
memset(&newconf, 0, sizeof(newconf));
if (strcmp(newconf.listen_addr, gconf->listen_addr) != 0 ||
newconf.port_cleartext != gconf->port_cleartext ||
newconf.port_implicit_tls != gconf->port_implicit_tls) {
- log_warnx("SIGHUP: %s: \"listen on\" changed but listening "
- "sockets cannot be rebound without a restart, still "
- "serving %s:%u / %s:%u", conf_path, gconf->listen_addr,
- gconf->port_cleartext, gconf->listen_addr,
- gconf->port_implicit_tls);
+ log_warnx("SIGHUP: %s: \"listen on\" changed, takes effect on "
+ "restart", conf_path);
}
if (strcmp(newconf.cred_file, gconf->cred_file) != 0) {
- log_warnx("SIGHUP: %s: \"credentials\" changed but each "
- "auth-worker chroots to the old path's directory, a "
- "restart is required for this to take effect", conf_path);
+ log_warnx("SIGHUP: %s: \"credentials\" changed, takes effect "
+ "on restart", conf_path);
}
if (strlen(newconf.spool_root) >= sizeof(gconf->spool_root) ||
strlen(newconf.tls_cert_file) >= sizeof(gconf->tls_cert_file) ||
strlen(newconf.tls_key_file) >= sizeof(gconf->tls_key_file)) {
- log_warnx("SIGHUP: %s: reloaded value too long for gconf's "
- "field, keeping the already-running configuration",
- conf_path);
+ log_warnx("SIGHUP: %s: value too long, keeping the running "
+ "configuration", conf_path);
return;
}
/* keymgr holds the key for every connection, so it needs a push */
if (iev_keymgr != NULL) {
if (send_keymgr_init(iev_keymgr, gconf) == -1)
- log_warnx("SIGHUP: pushing the reloaded certificate "
- "and key to keymgr failed; it is still using the "
- "previously loaded material");
+ log_warnx("SIGHUP: keymgr did not take the new "
+ "certificate and key, keeping the old");
} else
log_warnx("SIGHUP: keymgr is gone, TLS private key not "
"reloaded");
struct store_child *sc;
int n = 0;
- (void)fd; (void)event; (void)arg;
-
TAILQ_FOREACH(c, &children, entry) {
kill(c->pid, SIGKILL);
n++;
struct timeval tv;
int i;
- (void)fd; (void)event; (void)arg;
-
/* a 2nd SIGTERM does not wait, as l2tpd.c:509-512 also does */
if (shutting_down) {
log_info("SIGTERM again: not waiting for children");
pid_t pid;
int status;
- (void)fd; (void)event; (void)arg;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
reap_child(pid, status);
"shutdown", pid);
else if (WIFSIGNALED(status))
log_warnx("keymgr[%d] terminated by "
- "signal %d, new TLS handshakes "
- "will now fail (already-"
- "negotiated TLS sessions are "
- "unaffected); run \"rcctl "
- "restart imapd\" to recover",
- pid, WTERMSIG(status));
+ "signal %d, TLS handshakes will "
+ "fail", pid, WTERMSIG(status));
else if (WIFEXITED(status))
- log_warnx("keymgr[%d] exited "
- "unexpectedly, status %d, new "
- "TLS handshakes will now fail "
- "(already-negotiated TLS "
- "sessions are unaffected); run "
- "\"rcctl restart imapd\" to "
- "recover", pid,
- WEXITSTATUS(status));
+ log_warnx("keymgr[%d] exited, status "
+ "%d, TLS handshakes will fail",
+ pid, WEXITSTATUS(status));
else
- log_warnx("keymgr[%d] exited "
- "unexpectedly, new TLS "
- "handshakes will now fail "
- "(already-negotiated TLS "
- "sessions are unaffected); run "
- "\"rcctl restart imapd\" to "
- "recover", pid);
+ log_warnx("keymgr[%d] exited, TLS "
+ "handshakes will fail", pid);
iev_keymgr = NULL;
free(c);
return;
free(os);
} else {
log_debug("session %u: auth-worker[%d] "
- "exited (status %d); this session's "
- "listener-worker will detect this on "
- "its own auth channel", os->session_id,
- pid, status);
+ "exited, status %d",
+ os->session_id, pid, status);
os->auth_iev = NULL;
os->auth_pid = 0;
}
blob - cfad15bf92d2bb0d9cfdf97b9ce1d6cd053156ae
blob + 53bc7f3ae111d129938cd551bfeb10c68a178044
--- src/parse.y
+++ src/parse.y
* Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
* Copyright (c) 2001 Theo de Raadt. All rights reserved.
*
- * The parser skeleton below (lgetc()/lungetc()/findeol(), the hand-
- * written yylex() built on top of them, pushfile()/popfile(), and
- * symset()/symget()) follows the structure common to ripd's, smtpd's,
- * and httpd's own parse.y in the OpenBSD base system, all of which
- * carry this same four-name copyright chain at their root. This file's
- * grammar and domain-specific rules are original; the above four names
- * are carried forward for the shared parser-skeleton lineage only.
- *
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
conf = xconf;
memset(conf, 0, sizeof(*conf));
- (void)strlcpy(conf->listen_addr, "0.0.0.0", sizeof(conf->listen_addr));
+ (void)strlcpy(conf->listen_addr, "*", sizeof(conf->listen_addr));
conf->port_cleartext = 143;
conf->port_implicit_tls = 993;
conf->idle_poll_secs = IDLE_POLL_DEFAULT;
blob - 89cadd6d7d8b8435bffef0913ac0d978bd4afb4e
blob + 1c15f07121604a242b854c6e07d35ec683493006
--- src/parser.c
+++ src/parser.c
imsgev_init(&iev_store, store_fd, parser_dispatch_store, NULL);
/* no "rpath": the only bytes arrive on passed descriptors */
-#ifdef __OpenBSD__
if (pledge("stdio recvfd", NULL) == -1)
fatal("pledge");
-#endif
event_dispatch();
fatalx("parser: exited event loop");
struct imsg imsg;
ssize_t n;
- (void)fd;
-
if (event & EV_WRITE) {
if (imsgbuf_write(&iev->ibuf) == -1)
fatal("imsgbuf_write");
blob - fa25214c72a54d8338fdec64ec0a4114adcfd77c
blob + 8b3ea7fcb79f6b83ec5eef538e392579ff15c0b7
--- src/search_cmd.c
+++ src/search_cmd.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
+#include <sys/param.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
char pool[SEARCH_OPERANDS_MAX];
};
-
static int
search_push(struct search_parse_ctx *ctx, const struct search_node *node,
const char **errmsg)
return (0);
}
-/* RFC 9051 SS9 sequence-set, pushed as ORed ranges */
+/* RFC 9051 section 9 sequence-set, pushed as ORed ranges */
static int
parse_search_seqset(char **pp, struct search_parse_ctx *ctx, int op,
const char **errmsg)
return (0);
}
-/* RFC 9051 SS6.4.4 search-key: 0 ok, -1 BAD, -2 NO */
+/* RFC 9051 section 6.4.4 search-key: 0 ok, -1 BAD, -2 NO */
int
parse_search_key(char **pp, struct search_parse_ctx *ctx, const char **errmsg)
{
return (rc);
}
-
static int
read_search_token(char **pp, char *buf, size_t bufsize, const char *errtext,
const char **errmsg)
return (0);
}
-/* RFC 9051 SS9 date, as the UTC midnight that begins it */
+/* RFC 9051 section 9 date, as the UTC midnight that begins it */
static int
read_search_date(char **pp, int64_t *out, const char **errmsg)
{
p++;
if (*p == '"') {
const char *start = p + 1;
- char *end = strchr(start, '"');
+ char *end;
size_t len;
+ end = strchr(start, '"');
if (end == NULL) {
*errmsg = "unterminated date string";
return (-1);
ctx->operand_len++;
}
-/* RFC 9051 SS9 astring operand */
+/* RFC 9051 section 9 astring operand */
static int
read_search_astring(char **pp, struct search_parse_ctx *ctx,
uint32_t *offp, uint32_t *lenp, const char **errmsg)
*errmsg = "unterminated quoted string";
return (-1);
}
- /* RFC 9051 SS9: quoted-specials are the only escapes */
+ /* RFC 9051 section 9: only quoted-specials escape */
if (*p == '\\' && *++p != '"' && *p != '\\') {
*errmsg = "malformed quoted string";
return (-1);
}
p++;
} else {
- /* RFC 9051 SS9 ASTRING-CHAR */
+ /* RFC 9051 section 9 ASTRING-CHAR */
for (; *p != '\0' && *p != ' ' && *p != ')'; p++) {
if ((unsigned char)*p <= 0x1f ||
(unsigned char)*p >= 0x7f ||
return (0);
}
+/* what follows a search key, before it is pushed as one node */
+enum search_arg {
+ SARG_NONE,
+ SARG_KEYWORD,
+ SARG_DATE,
+ SARG_NUMBER,
+ SARG_MODSEQ,
+ SARG_STRING,
+ SARG_HEADER,
+ SARG_UIDSET,
+ SARG_KEY,
+ SARG_KEY2
+};
+
+static const struct search_keyword {
+ const char *name;
+ int op;
+ enum search_arg arg;
+} search_keywords[] = {
+ { "ALL", SEARCH_OP_ALL, SARG_NONE },
+ { "ANSWERED", SEARCH_OP_ANSWERED, SARG_NONE },
+ { "UNANSWERED", SEARCH_OP_UNANSWERED, SARG_NONE },
+ { "DELETED", SEARCH_OP_DELETED, SARG_NONE },
+ { "UNDELETED", SEARCH_OP_UNDELETED, SARG_NONE },
+ { "DRAFT", SEARCH_OP_DRAFT, SARG_NONE },
+ { "UNDRAFT", SEARCH_OP_UNDRAFT, SARG_NONE },
+ { "FLAGGED", SEARCH_OP_FLAGGED, SARG_NONE },
+ { "UNFLAGGED", SEARCH_OP_UNFLAGGED, SARG_NONE },
+ { "SEEN", SEARCH_OP_SEEN, SARG_NONE },
+ { "UNSEEN", SEARCH_OP_UNSEEN, SARG_NONE },
+ { "KEYWORD", SEARCH_OP_KEYWORD, SARG_KEYWORD },
+ { "UNKEYWORD", SEARCH_OP_UNKEYWORD, SARG_KEYWORD },
+ { "BEFORE", SEARCH_OP_BEFORE, SARG_DATE },
+ { "ON", SEARCH_OP_ON, SARG_DATE },
+ { "SINCE", SEARCH_OP_SINCE, SARG_DATE },
+ { "LARGER", SEARCH_OP_LARGER, SARG_NUMBER },
+ { "SMALLER", SEARCH_OP_SMALLER, SARG_NUMBER },
+ { "UID", SEARCH_OP_UIDSET, SARG_UIDSET },
+ { "MODSEQ", SEARCH_OP_MODSEQ, SARG_MODSEQ },
+ { "NOT", SEARCH_OP_NOT, SARG_KEY },
+ { "OR", SEARCH_OP_OR, SARG_KEY2 },
+ /* RFC 9051 section 6.4.4 content keys, answered from the message */
+ { "SENTBEFORE", SEARCH_OP_SENTBEFORE, SARG_DATE },
+ { "SENTON", SEARCH_OP_SENTON, SARG_DATE },
+ { "SENTSINCE", SEARCH_OP_SENTSINCE, SARG_DATE },
+ { "BCC", SEARCH_OP_BCC, SARG_STRING },
+ { "BODY", SEARCH_OP_BODY, SARG_STRING },
+ { "CC", SEARCH_OP_CC, SARG_STRING },
+ { "FROM", SEARCH_OP_FROM, SARG_STRING },
+ { "HEADER", SEARCH_OP_HEADER, SARG_HEADER },
+ { "SUBJECT", SEARCH_OP_SUBJECT, SARG_STRING },
+ { "TEXT", SEARCH_OP_TEXT, SARG_STRING },
+ { "TO", SEARCH_OP_TO, SARG_STRING },
+};
+
+static int
+read_search_number(char **pp, int64_t *out, int modseq, const char **errmsg)
+{
+ const char *errstr;
+ char numbuf[24];
+
+ if (read_search_token(pp, numbuf, sizeof(numbuf), modseq ?
+ "missing or malformed MODSEQ value" :
+ "missing or malformed octet count", errmsg) == -1)
+ return (-1);
+ errstr = "not a number";
+ if (numbuf[0] >= '0' && numbuf[0] <= '9')
+ *out = strtonum(numbuf, 0, INT64_MAX, &errstr);
+ if (errstr != NULL) {
+ *errmsg = modseq ? "malformed MODSEQ value" :
+ "malformed octet count";
+ return (-1);
+ }
+ return (0);
+}
+
+/* RFC 7162 section 3.1.5: MODSEQ [entry-name type-req]; both are skipped */
+static int
+skip_modseq_entry(char **pp, const char **errmsg)
+{
+ char *p = *pp, *end;
+
+ while (*p == ' ')
+ p++;
+ if (*p == '\0' || isdigit((unsigned char)*p)) {
+ *pp = p;
+ return (0);
+ }
+ if (*p == '"') {
+ if ((end = strchr(p + 1, '"')) == NULL) {
+ *errmsg = "unterminated MODSEQ entry-name";
+ return (-1);
+ }
+ p = end + 1;
+ } else {
+ while (*p != '\0' && *p != ' ' && *p != ')')
+ p++;
+ }
+ while (*p == ' ')
+ p++;
+
+ if (strncasecmp(p, "priv", 4) == 0 &&
+ (p[4] == ' ' || p[4] == '\0' || p[4] == ')'))
+ p += 4;
+ else if (strncasecmp(p, "shared", 6) == 0 &&
+ (p[6] == ' ' || p[6] == '\0' || p[6] == ')'))
+ p += 6;
+ else if (strncasecmp(p, "all", 3) == 0 &&
+ (p[3] == ' ' || p[3] == '\0' || p[3] == ')'))
+ p += 3;
+ else {
+ *errmsg = "expected priv/shared/all after MODSEQ entry-name";
+ return (-1);
+ }
+ while (*p == ' ')
+ p++;
+ *pp = p;
+ return (0);
+}
+
int
parse_search_key_inner(char **pp, struct search_parse_ctx *ctx,
const char **errmsg)
{
- char *p = *pp;
- char word[32];
- size_t wlen;
+ const struct search_keyword *kw;
+ struct search_node node;
+ char *p = *pp, *start;
+ char word[32];
+ size_t wlen, i;
+ int rc;
while (*p == ' ')
p++;
}
if (*p == '(') {
- int rc;
-
p++;
- rc = parse_search_key_list(&p, ctx, errmsg, 1);
- if (rc != 0)
+ if ((rc = parse_search_key_list(&p, ctx, errmsg, 1)) != 0)
return (rc);
while (*p == ' ')
p++;
return (0);
}
- {
- char *start = p;
+ start = p;
+ while (*p != '\0' && *p != ' ' && *p != ')')
+ p++;
+ wlen = (size_t)(p - start);
+ if (wlen == 0 || wlen >= sizeof(word)) {
+ *errmsg = "unknown search key";
+ return (-1);
+ }
+ memcpy(word, start, wlen);
+ word[wlen] = '\0';
- while (*p != '\0' && *p != ' ' && *p != ')')
- p++;
- wlen = (size_t)(p - start);
- if (wlen == 0 || wlen >= sizeof(word)) {
- *errmsg = "unknown search key";
- return (-1);
+ kw = NULL;
+ for (i = 0; i < nitems(search_keywords); i++) {
+ if (strcasecmp(word, search_keywords[i].name) == 0) {
+ kw = &search_keywords[i];
+ break;
}
- memcpy(word, start, wlen);
- word[wlen] = '\0';
}
-
- if (strcasecmp(word, "ALL") == 0) {
- struct search_node node;
-
- memset(&node, 0, sizeof(node));
- node.op = SEARCH_OP_ALL;
- if (search_push(ctx, &node, errmsg) == -1)
- return (-1);
- *pp = p;
- return (0);
+ if (kw == NULL) {
+ *errmsg = "unknown search key";
+ return (-1);
}
- {
- static const struct {
- const char *name;
- int op;
- } boolkeys[] = {
- { "ANSWERED", SEARCH_OP_ANSWERED },
- { "UNANSWERED", SEARCH_OP_UNANSWERED },
- { "DELETED", SEARCH_OP_DELETED },
- { "UNDELETED", SEARCH_OP_UNDELETED },
- { "DRAFT", SEARCH_OP_DRAFT },
- { "UNDRAFT", SEARCH_OP_UNDRAFT },
- { "FLAGGED", SEARCH_OP_FLAGGED },
- { "UNFLAGGED", SEARCH_OP_UNFLAGGED },
- { "SEEN", SEARCH_OP_SEEN },
- { "UNSEEN", SEARCH_OP_UNSEEN },
- };
- size_t i;
-
- for (i = 0; i < sizeof(boolkeys) / sizeof(boolkeys[0]); i++) {
- struct search_node node;
-
- if (strcasecmp(word, boolkeys[i].name) != 0)
- continue;
- memset(&node, 0, sizeof(node));
- node.op = boolkeys[i].op;
- if (search_push(ctx, &node, errmsg) == -1)
- return (-1);
- *pp = p;
- return (0);
- }
- }
-
- if (strcasecmp(word, "KEYWORD") == 0 ||
- strcasecmp(word, "UNKEYWORD") == 0) {
- struct search_node node;
-
- memset(&node, 0, sizeof(node));
- node.op = (strcasecmp(word, "KEYWORD") == 0) ?
- SEARCH_OP_KEYWORD : SEARCH_OP_UNKEYWORD;
-
+ memset(&node, 0, sizeof(node));
+ node.op = kw->op;
+ switch (kw->arg) {
+ case SARG_NONE:
+ break;
+ case SARG_KEYWORD:
if (read_search_token(&p, node.keyword, sizeof(node.keyword),
"missing or too-long KEYWORD/UNKEYWORD argument",
errmsg) == -1)
return (-1);
-
- if (search_push(ctx, &node, errmsg) == -1)
- return (-1);
- *pp = p;
- return (0);
- }
-
- if (strcasecmp(word, "BEFORE") == 0 || strcasecmp(word, "ON") == 0 ||
- strcasecmp(word, "SINCE") == 0) {
- struct search_node node;
-
- memset(&node, 0, sizeof(node));
- if (strcasecmp(word, "BEFORE") == 0)
- node.op = SEARCH_OP_BEFORE;
- else if (strcasecmp(word, "ON") == 0)
- node.op = SEARCH_OP_ON;
- else
- node.op = SEARCH_OP_SINCE;
-
+ break;
+ case SARG_DATE:
if (read_search_date(&p, &node.num, errmsg) == -1)
return (-1);
-
- if (search_push(ctx, &node, errmsg) == -1)
+ break;
+ case SARG_NUMBER:
+ if (read_search_number(&p, &node.num, 0, errmsg) == -1)
return (-1);
- *pp = p;
- return (0);
- }
-
- if (strcasecmp(word, "LARGER") == 0 ||
- strcasecmp(word, "SMALLER") == 0) {
- struct search_node node;
- char numbuf[24];
- char *numend;
- unsigned long long v;
-
- memset(&node, 0, sizeof(node));
- node.op = (strcasecmp(word, "LARGER") == 0) ?
- SEARCH_OP_LARGER : SEARCH_OP_SMALLER;
-
- if (read_search_token(&p, numbuf, sizeof(numbuf),
- "missing or malformed octet count", errmsg) == -1)
+ break;
+ case SARG_MODSEQ:
+ if (skip_modseq_entry(&p, errmsg) == -1 ||
+ read_search_number(&p, &node.num, 1, errmsg) == -1)
return (-1);
-
- errno = 0;
- v = strtoull(numbuf, &numend, 10);
- if (*numend != '\0' || errno == ERANGE) {
- *errmsg = "malformed octet count";
- return (-1);
- }
- /* node.num is signed: refuse above INT64_MAX */
- if (v > (unsigned long long)INT64_MAX) {
- *errmsg = "octet count out of range";
- return (-1);
- }
- node.num = (int64_t)v;
-
- if (search_push(ctx, &node, errmsg) == -1)
- return (-1);
- *pp = p;
- return (0);
- }
-
- if (strcasecmp(word, "UID") == 0) {
- while (*p == ' ')
- p++;
- if (parse_search_seqset(&p, ctx, SEARCH_OP_UIDSET, errmsg) ==
- -1)
- return (-1);
- *pp = p;
- return (0);
- }
-
- if (strcasecmp(word, "MODSEQ") == 0) {
- struct search_node node;
- char numbuf[24];
- char *numend;
- unsigned long long v;
-
- memset(&node, 0, sizeof(node));
- node.op = SEARCH_OP_MODSEQ;
-
- while (*p == ' ')
- p++;
-
- /* RFC 7162 SS3.1.5 entry-name and type-req are skipped */
- if (*p != '\0' && !isdigit((unsigned char)*p)) {
- if (*p == '"') {
- char *end = strchr(p + 1, '"');
-
- if (end == NULL) {
- *errmsg = "unterminated MODSEQ "
- "entry-name";
- return (-1);
- }
- p = end + 1;
- } else {
- while (*p != '\0' && *p != ' ' && *p != ')')
- p++;
- }
- while (*p == ' ')
- p++;
-
- if (strncasecmp(p, "priv", 4) == 0 &&
- (p[4] == ' ' || p[4] == '\0' || p[4] == ')'))
- p += 4;
- else if (strncasecmp(p, "shared", 6) == 0 &&
- (p[6] == ' ' || p[6] == '\0' || p[6] == ')'))
- p += 6;
- else if (strncasecmp(p, "all", 3) == 0 &&
- (p[3] == ' ' || p[3] == '\0' || p[3] == ')'))
- p += 3;
- else {
- *errmsg = "expected priv/shared/all after "
- "MODSEQ entry-name";
- return (-1);
- }
- while (*p == ' ')
- p++;
- }
-
- if (read_search_token(&p, numbuf, sizeof(numbuf),
- "missing or malformed MODSEQ value", errmsg) == -1)
- return (-1);
-
- errno = 0;
- v = strtoull(numbuf, &numend, 10);
- if (*numend != '\0' || errno == ERANGE) {
- *errmsg = "malformed MODSEQ value";
- return (-1);
- }
- /* same signed-cast trap as LARGER/SMALLER above */
- if (v > (unsigned long long)INT64_MAX) {
- *errmsg = "MODSEQ value out of range";
- return (-1);
- }
- node.num = (int64_t)v;
-
- if (search_push(ctx, &node, errmsg) == -1)
- return (-1);
ctx->uses_modseq = 1;
- *pp = p;
- return (0);
- }
-
- if (strcasecmp(word, "NOT") == 0) {
- struct search_node node;
- int rc;
-
- rc = parse_search_key(&p, ctx, errmsg);
- if (rc != 0)
- return (rc);
-
- memset(&node, 0, sizeof(node));
- node.op = SEARCH_OP_NOT;
- if (search_push(ctx, &node, errmsg) == -1)
+ break;
+ case SARG_HEADER:
+ case SARG_STRING:
+ if (kw->arg == SARG_HEADER && read_search_astring(&p, ctx,
+ &node.name_off, &node.name_len, errmsg) == -1)
return (-1);
+ if (read_search_astring(&p, ctx, &node.str_off, &node.str_len,
+ errmsg) == -1)
+ return (-1);
+ break;
+ case SARG_UIDSET:
+ while (*p == ' ')
+ p++;
+ if (parse_search_seqset(&p, ctx, SEARCH_OP_UIDSET, errmsg) ==
+ -1)
+ return (-1);
*pp = p;
- return (0);
- }
-
- if (strcasecmp(word, "OR") == 0) {
- struct search_node node;
- int rc;
-
- rc = parse_search_key(&p, ctx, errmsg);
- if (rc != 0)
+ return (0); /* the set is its own node */
+ case SARG_KEY2:
+ if ((rc = parse_search_key(&p, ctx, errmsg)) != 0)
return (rc);
- rc = parse_search_key(&p, ctx, errmsg);
- if (rc != 0)
+ /* FALLTHROUGH */
+ case SARG_KEY:
+ if ((rc = parse_search_key(&p, ctx, errmsg)) != 0)
return (rc);
-
- memset(&node, 0, sizeof(node));
- node.op = SEARCH_OP_OR;
- if (search_push(ctx, &node, errmsg) == -1)
- return (-1);
- *pp = p;
- return (0);
+ break;
}
- /* RFC 9051 SS6.4.4 content keys, answered from the message */
- if (strcasecmp(word, "SENTBEFORE") == 0 ||
- strcasecmp(word, "SENTON") == 0 ||
- strcasecmp(word, "SENTSINCE") == 0) {
- struct search_node node;
-
- memset(&node, 0, sizeof(node));
- if (strcasecmp(word, "SENTBEFORE") == 0)
- node.op = SEARCH_OP_SENTBEFORE;
- else if (strcasecmp(word, "SENTON") == 0)
- node.op = SEARCH_OP_SENTON;
- else
- node.op = SEARCH_OP_SENTSINCE;
-
- if (read_search_date(&p, &node.num, errmsg) == -1)
- return (-1);
-
- if (search_push(ctx, &node, errmsg) == -1)
- return (-1);
- *pp = p;
- return (0);
- }
-
- {
- static const struct {
- const char *name;
- int op;
- } string_keys[] = {
- { "BCC", SEARCH_OP_BCC },
- { "BODY", SEARCH_OP_BODY },
- { "CC", SEARCH_OP_CC },
- { "FROM", SEARCH_OP_FROM },
- { "HEADER", SEARCH_OP_HEADER },
- { "SUBJECT", SEARCH_OP_SUBJECT },
- { "TEXT", SEARCH_OP_TEXT },
- { "TO", SEARCH_OP_TO },
- };
- struct search_node node;
- size_t i;
-
- for (i = 0; i < sizeof(string_keys) / sizeof(string_keys[0]);
- i++) {
- if (strcasecmp(word, string_keys[i].name) != 0)
- continue;
- memset(&node, 0, sizeof(node));
- node.op = string_keys[i].op;
- /* HEADER's field-name is an astring too */
- if (node.op == SEARCH_OP_HEADER &&
- read_search_astring(&p, ctx, &node.name_off,
- &node.name_len, errmsg) == -1)
- return (-1);
- if (read_search_astring(&p, ctx, &node.str_off,
- &node.str_len, errmsg) == -1)
- return (-1);
-
- if (search_push(ctx, &node, errmsg) == -1)
- return (-1);
- *pp = p;
- return (0);
- }
- }
-
- *errmsg = "unknown search key";
- return (-1);
+ if (search_push(ctx, &node, errmsg) == -1)
+ return (-1);
+ *pp = p;
+ return (0);
}
-/* RFC 9051 SS6.4.4 search-key list, ANDed */
+/* RFC 9051 section 6.4.4 search-key list, ANDed */
int
parse_search_key_list(char **pp, struct search_parse_ctx *ctx,
const char **errmsg, int in_parens)
{
- int rc;
+ struct search_node combine;
+ int rc;
rc = parse_search_key(pp, ctx, errmsg);
if (rc != 0)
if (rc != 0)
return (rc);
- {
- struct search_node combine;
-
- memset(&combine, 0, sizeof(combine));
- combine.op = SEARCH_OP_AND;
- if (search_push(ctx, &combine, errmsg) == -1)
- return (-1);
- }
+ memset(&combine, 0, sizeof(combine));
+ combine.op = SEARCH_OP_AND;
+ if (search_push(ctx, &combine, errmsg) == -1)
+ return (-1);
}
}
memset(&ctx, 0, sizeof(ctx));
rc = parse_search_key_list(&p, &ctx, &errmsg, 0);
- /* RFC 9051 SS7.1: LIMIT, an implementation limit was reached */
+ /* RFC 9051 section 7.1: LIMIT, an implementation limit was reached */
if (rc == 0 && ctx.operand_len > SEARCH_OPERANDS_MAX) {
errmsg = "[LIMIT] search strings exceed 4096 octets in all";
rc = -2;
return (0);
}
-/* RFC 9051 SS6.4.4 search-return-opts */
+/* RFC 9051 section 6.4.4 search-return-opts */
int
parse_search_return_opts(char **pp, uint32_t *opts_out, const char **errmsg)
{
}
}
-/* RFC 9051 SS9 charset: atom / quoted */
+/* RFC 9051 section 9 charset: atom / quoted */
static int
read_search_charset(char **pp, char *out, size_t outsize)
{
static void search_dispatch_finish(struct session *,
const struct search_parse_result *, struct search_node *);
-/* RFC 9051 SS6.4.4.1: a SAVE answered NO here empties "$" too */
+/* RFC 9051 section 6.4.4.1: a SAVE answered NO here empties "$" too */
static void
search_saved_clear(struct session *s, uint32_t opts)
{
if ((opts & SEARCH_RETURN_SAVE) && s->store_iev != NULL)
- (void)send_mbox_request(s, IMSG_MBOX_SAVED_CLEAR, "SEARCH",
+ (void)send_mbox_request(s, IMSG_MBOX_SAVED_CLEAR,
"IMSG_MBOX_SAVED_CLEAR", NULL, 0, NULL, 0, 0);
}
-/* RFC 9051 SS6.4.4 SEARCH; US-ASCII and UTF-8 only */
+/* RFC 9051 section 6.4.4 SEARCH; US-ASCII and UTF-8 only */
int
cmd_search(struct session *s, const char *tag, char *args)
{
- return search_dispatch(s, tag, args, 0);
+ return (search_dispatch(s, tag, args, 0));
}
int
search_dispatch(struct session *s, const char *tag, char *args, int by_uid)
{
+ struct search_parse_result res;
+ struct search_node nodes[SEARCH_PROGRAM_MAX_NODES];
char *p;
- uint32_t return_opts = 0;
- int rc;
const char *errmsg;
+ uint32_t return_opts = 0;
+ int rc;
if (args == NULL) {
session_reply(s, tag, "BAD", "SEARCH requires search criteria");
}
if (return_opts == 0)
- return_opts = SEARCH_RETURN_ALL; /* RFC 9051 SS6.4.4 */
+ /* RFC 9051 section 6.4.4 */
+ return_opts = SEARCH_RETURN_ALL;
if (strncasecmp(p, "CHARSET", 7) == 0 &&
(p[7] == ' ' || p[7] == '\0')) {
if (strcasecmp(charset, "US-ASCII") != 0 &&
strcasecmp(charset, "UTF-8") != 0) {
search_saved_clear(s, return_opts);
- /* RFC 9051 SS9 puts the charset list in parens */
+ /* RFC 9051 section 9 puts the charset list in parens */
session_reply(s, tag, "NO",
"[BADCHARSET (US-ASCII UTF-8)] unsupported "
"CHARSET");
return (1);
}
- {
- struct search_parse_result res;
- struct search_node nodes[SEARCH_PROGRAM_MAX_NODES];
+ memset(&res, 0, sizeof(res));
+ res.rc = search_program_parse(p, nodes, &res.nnodes, &res.uses_modseq,
+ res.pool, &res.poollen, res.errmsg, sizeof(res.errmsg));
+ if (res.rc == -1 && strcmp(res.errmsg, literal_wanted) == 0)
+ return (session_arg_error(s, tag, literal_wanted));
- memset(&res, 0, sizeof(res));
- res.rc = search_program_parse(p, nodes, &res.nnodes,
- &res.uses_modseq, res.pool, &res.poollen, res.errmsg,
- sizeof(res.errmsg));
- if (res.rc == -1 && strcmp(res.errmsg, literal_wanted) == 0)
- return (session_arg_error(s, tag, literal_wanted));
+ free(s->search_matches);
+ s->search_matches = NULL;
+ s->search_nmatches = 0;
+ s->search_matches_cap = 0;
+ s->search_alloc_failed = 0;
+ s->search_return_opts = return_opts;
+ s->cmd_by_uid = by_uid;
- free(s->search_matches);
- s->search_matches = NULL;
- s->search_nmatches = 0;
- s->search_matches_cap = 0;
- s->search_alloc_failed = 0;
- s->search_return_opts = return_opts;
- s->cmd_by_uid = by_uid;
-
- if (strlcpy(s->pending_tag, tag, sizeof(s->pending_tag)) >=
- sizeof(s->pending_tag)) {
- search_saved_clear(s, return_opts);
- session_reply(s, tag, "NO",
- "[SERVERBUG] internal error");
- return (1);
- }
- search_dispatch_finish(s, &res,
- res.nnodes > 0 ? nodes : NULL);
+ if (strlcpy(s->pending_tag, tag, sizeof(s->pending_tag)) >=
+ sizeof(s->pending_tag)) {
+ search_saved_clear(s, return_opts);
+ session_reply(s, tag, "NO", "[SERVERBUG] internal error");
+ return (1);
}
+ search_dispatch_finish(s, &res, res.nnodes > 0 ? nodes : NULL);
return (1);
}
search_dispatch_finish(struct session *s,
const struct search_parse_result *res, struct search_node *nodes)
{
+ struct imsg_mbox_search req;
+
if (res->rc == -1) {
session_reply(s, s->pending_tag, "BAD", res->errmsg);
s->state = SESSION_SELECTED;
s->search_used_modseq = res->uses_modseq;
s->search_max_modseq = 0;
- /* RFC 7162 SS3.1: MODSEQ enables CONDSTORE */
+ /* RFC 7162 section 3.1: MODSEQ enables CONDSTORE */
if (res->uses_modseq)
session_condstore_enable(s);
s->state = SESSION_SEARCHING;
- {
- struct imsg_mbox_search req;
+ memset(&req, 0, sizeof(req));
+ req.nnodes = res->nnodes;
+ req.poollen = res->poollen;
+ req.return_opts = s->search_return_opts;
+ memcpy(req.pool, res->pool, res->poollen);
- memset(&req, 0, sizeof(req));
- req.nnodes = res->nnodes;
- req.poollen = res->poollen;
- req.return_opts = s->search_return_opts;
- memcpy(req.pool, res->pool, res->poollen);
-
- if (!send_mbox_request(s, IMSG_MBOX_SEARCH, "SEARCH",
- "IMSG_MBOX_SEARCH", &req, sizeof(req), nodes, res->nnodes,
- sizeof(struct search_node))) {
- search_saved_clear(s, s->search_return_opts);
- session_reply(s, s->pending_tag, "NO",
- "[SERVERBUG] internal error");
- s->state = SESSION_SELECTED;
- return;
- }
+ if (send_mbox_request(s, IMSG_MBOX_SEARCH,
+ "IMSG_MBOX_SEARCH", &req, sizeof(req), nodes, res->nnodes,
+ sizeof(struct search_node)) == -1) {
+ search_saved_clear(s, s->search_return_opts);
+ session_reply(s, s->pending_tag, "NO",
+ "[SERVERBUG] internal error");
+ s->state = SESSION_SELECTED;
}
}
if (s->search_nmatches == s->search_matches_cap) {
uint32_t newcap = s->search_matches_cap ?
s->search_matches_cap * 2 : 32;
- uint32_t *n = reallocarray(s->search_matches, newcap,
- sizeof(uint32_t));
+ uint32_t *n;
+ n = reallocarray(s->search_matches, newcap, sizeof(uint32_t));
if (n == NULL) {
log_warn("session %u: realloc SEARCH match array",
s->id);
s->search_matches_cap = newcap;
}
- /* RFC 9051 SS6.4.9: UID SEARCH reports UIDs */
+ /* RFC 9051 section 6.4.9: UID SEARCH reports UIDs */
s->search_matches[s->search_nmatches++] = s->cmd_by_uid ?
m->uid : m->seqno;
- /* RFC 7162 SS3.1.6 */
+ /* RFC 7162 section 3.1.6 */
if (m->modseq > s->search_max_modseq)
s->search_max_modseq = m->modseq;
}
#define SEARCH_RESP_PREFIX_MAX 256
#define SEARCH_ALL_PER_MATCH 11 /* "4294967295" + one separator */
-/* RFC 9051 SS7.3.4 ESEARCH response */
+/* RFC 9051 section 7.3.4 ESEARCH response */
void
session_finish_search(struct session *s, struct imsg_mbox_result *res)
{
}
if (res->error != MBOX_OP_OK || s->search_alloc_failed)
goto fail;
- /* RFC 9051 SS6.4.4: SAVE alone suppresses ESEARCH */
+ /* RFC 9051 section 6.4.4: SAVE alone suppresses ESEARCH */
if (s->search_return_opts == SEARCH_RETURN_SAVE)
goto done;
}
len = (size_t)n;
- /* RFC 9051 SS9: "UID" after correlator, before MIN/MAX/ALL/MODSEQ */
+ /* RFC 9051 section 9: "UID" after the correlator, before the others */
if (s->cmd_by_uid && len < bufsize)
len += (size_t)snprintf(buf + len, bufsize - len, " UID");
s->search_nmatches, &truncated);
len += listlen;
if (truncated)
- log_warnx("session %u: SEARCH ALL response "
- "truncated at %zu bytes (%u total "
- "matches) -- can't happen, bufsize is "
- "sized for the worst case", s->id,
- bufsize, s->search_nmatches);
+ fatalx("session %u: SEARCH ALL truncated at "
+ "%zu bytes (%u matches)", s->id, bufsize,
+ s->search_nmatches);
}
}
len += (size_t)snprintf(buf + len, bufsize - len,
" COUNT %u", s->search_nmatches);
- /* RFC 7162 SS3.1.10: MODSEQ in ESEARCH */
+ /* RFC 7162 section 3.1.10: MODSEQ in ESEARCH */
if (s->search_used_modseq && s->search_nmatches > 0 &&
len < bufsize)
len += (size_t)snprintf(buf + len, bufsize - len,
free(buf);
done:
- /* as RFC 9051 SS6.4.9's "UID <cmd> completed" */
+ /* as RFC 9051 section 6.4.9's "UID <cmd> completed" */
session_reply(s, s->pending_tag, "OK",
s->cmd_by_uid ? "UID SEARCH completed" : "SEARCH completed");
goto cleanup;
blob - 39a322115575c507c1a25286fcf680d5f615b106
blob + b2237913a69fabeb9c2f650a4f8f514a76dbb15c
--- src/search_match.c
+++ src/search_match.c
const struct imsg_parser_leaf *, uint32_t, const char *,
char *);
-/* RFC 9051 SS6.4.4 folds the ASCII range only, byte by byte */
+/* RFC 9051 section 6.4.4 folds the ASCII range only, byte by byte */
static int
ascii_lower(int c)
{
return (-1);
}
-/* RFC 2047 SS2 encoded-word */
+/* RFC 2047 section 2 encoded-word */
static size_t
decode_word(const char *w, size_t len, char *out)
{
return (0);
}
-/* RFC 5322 SS2.2.3 unfolding and RFC 2047 SS6.1 decoding */
+/* RFC 5322 section 2.2.3 unfolding and RFC 2047 section 6.1 decoding */
static char *
decode_value(const char *v, size_t vlen, size_t *outlen)
{
return (out);
}
-/* RFC 5322 SS3.3 allows FWS, folds included, between a date's tokens */
+/* RFC 5322 section 3.3 allows FWS, folds included, between a date's tokens */
static int
date_fws(int c)
{
return (c == ' ' || c == '\t' || c == '\r' || c == '\n');
}
-/* RFC 9051 SS6.4.4: the day only, ignoring time and zone */
+/* RFC 9051 section 6.4.4: the day only, ignoring time and zone */
static int
date_field_day(const char *v, size_t vlen, int64_t *out)
{
while (p < end && date_fws((unsigned char)*p))
p++;
- /* RFC 5322 SS3.3: day-of-week is optional */
+ /* RFC 5322 section 3.3: day-of-week is optional */
if (p < end && ascii_lower((unsigned char)*p) >= 'a' &&
ascii_lower((unsigned char)*p) <= 'z') {
while (p < end && *p != ',')
return (-1);
}
-/* RFC 9051 SS6.4.4: addresses as ENVELOPE shows them, not group names */
+/* RFC 9051 section 6.4.4: addresses as ENVELOPE shows them, not group names */
static int
match_addresses(const char *v, size_t vlen, const char *needle,
size_t needlelen)
size_t i, ulen = 0, pos = 0, toklen, declen;
int hit = 0, ingroup = 0, kind;
- /* RFC 5322 SS2.2.3: unfolding drops CR and LF */
+ /* RFC 5322 section 2.2.3: unfolding drops CR and LF */
if ((unf = malloc(vlen + 1)) == NULL)
return (-1);
for (i = 0; i < vlen; i++) {
return (0);
}
-/* RFC 2045 SS6.8: characters outside the alphabet are ignored */
+/* RFC 2045 section 6.8: characters outside the alphabet are ignored */
static size_t
decode_b64(const char *in, size_t len, char *out)
{
return (n);
}
-/* RFC 2045 SS6.7; an "=" that is neither form is kept as it is */
+/* RFC 2045 section 6.7; an "=" that is neither form is kept as it is */
static size_t
decode_qp(const char *in, size_t len, char *out)
{
}
}
-/* RFC 9051 SS6.4.4: TEXT sees every header, MIME part headers too */
+/* RFC 9051 section 6.4.4: TEXT sees every header, MIME part headers too */
static int
scan_header(struct body_walk *w, const char *hdr, size_t hdrlen)
{
return (0);
}
-/* RFC 2045 SS5.2, SS6.4; RFC 2046 SS5.1.3, SS5.1.5 */
+/* RFC 2045 section 5.2, section 6.4; RFC 2046 section 5.1.3, section 5.1.5 */
static int
part_type(const char *hdr, size_t hdrlen, int digest, char *boundary,
size_t boundarysize, int *enc, int *subdigest)
(vallen == 4 && (strncasecmp(val, "7bit", 4) == 0 ||
strncasecmp(val, "8bit", 4) == 0)) ||
(vallen == 6 && strncasecmp(val, "binary", 6) == 0))) {
- /* SS6.4: an unknown one makes it octet-stream */
+ /* section 6.4: an unknown one makes it octet-stream */
free(val);
return (PART_SKIP);
}
free(val);
}
- /* SS5.2: none, or an invalid one, is text/plain */
+ /* section 5.2: none, or an invalid one, is text/plain */
if (extract_header_field(hdr, hdrlen, "Content-Type", &val,
&vallen) == -1 || vallen == 0) {
free(val);
if (mime_read_token_or_qstring(val, vallen, &pos, value,
sizeof(value)) == -1)
break;
- /* RFC 2046 SS5.1.1: at most 70 characters */
+ /* RFC 2046 section 5.1.1: at most 70 characters */
if (strcasecmp(attr, "boundary") == 0 &&
strlcpy(boundary, value, boundarysize) < boundarysize)
has_boundary = 1;
free(val);
if (strcasecmp(type, "multipart") == 0) {
- /* RFC 2046 SS5.1.1 requires the boundary: invalid without */
+ /* RFC 2046 section 5.1.1: invalid without a boundary */
if (!has_boundary)
return (PART_TEXT);
*subdigest = strcasecmp(subtype, "digest") == 0;
PART_TEXT;
else
kind = strcasecmp(type, "text") == 0 ? PART_TEXT : PART_SKIP;
- /* RFC 2046 SS5.1.1, SS5.2.1: these are never encoded */
+ /* RFC 2046 section 5.1.1, section 5.2.1: these are never encoded */
if (kind != PART_TEXT)
*enc = ENC_IDENTITY;
return (kind);
}
-/* RFC 2046 SS5.1.1: the next "--boundary" line at or after *pos */
+/* RFC 2046 section 5.1.1: the next "--boundary" line at or after *pos */
static int
next_delimiter(const char *body, size_t len, const char *delim,
size_t dlen, size_t *pos, size_t *start, int *closing)
if (dlen >= sizeof(delim))
return (0);
for (;;) {
- int found = next_delimiter(body, len, delim, dlen, &pos,
- &dstart, &closing);
+ int found;
+ found = next_delimiter(body, len, delim, dlen, &pos, &dstart,
+ &closing);
if (!found)
dstart = len;
if (inpart) {
}
}
-/* RFC 9051 SS6.4.4 BODY and TEXT over TEXT and MESSAGE parts */
+/* RFC 9051 section 6.4.4 BODY and TEXT over TEXT and MESSAGE parts */
static int
match_body(const char *msg, size_t hdrlen, size_t len,
const struct imsg_parser_leaf *leaves, uint32_t nleaves,
blob - 39c29d5483acbef9f5451c27c58821b90f75bc4e
blob + 510e38cf36986d52626ac5b038cee07ca77e0914
--- src/store.c
+++ src/store.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
{
struct imsgbuf ibuf3;
struct imsg imsg;
- ssize_t n;
struct imsg_store_init init;
+ char unveil_path[sizeof(init.maildir) + 1];
+ ssize_t n;
gid_t gid;
-
imsgev_ibuf_init(&ibuf3, 3);
for (;;) {
setproctitle("store uid %u", (unsigned int)init.uid);
/* confine before the ack, so a failure can be reported */
+ if (snprintf(unveil_path, sizeof(unveil_path), "/%s",
+ init.maildir) >= (int)sizeof(unveil_path))
+ fatalx("session %u: maildir path too long: %s",
+ session_id, init.maildir);
+ if (unveil(unveil_path, "rwc") == -1)
+ fatal("session %u: unveil %s", session_id, unveil_path);
+ if (chdir(unveil_path) == -1)
+ fatal("session %u: chdir %s", session_id, unveil_path);
- {
- char unveil_path[sizeof(init.maildir) + 1];
-
- if (snprintf(unveil_path, sizeof(unveil_path), "/%s",
- init.maildir) >= (int)sizeof(unveil_path))
- fatalx("session %u: maildir path too long: %s",
- session_id, init.maildir);
- if (unveil(unveil_path, "rwc") == -1)
- fatal("unveil %s", unveil_path);
-
- if (chdir(unveil_path) == -1)
- fatal("chdir %s", unveil_path);
- }
-
/* unveil(2) covers lookups relative to these (sys/kern/vfs_lookup.c) */
if ((maildir_root_fd = open(".", O_RDONLY | O_DIRECTORY)) == -1)
fatal("open maildir root");
imsgev_init_from_ibuf(&parent_iev, &ibuf3, store_parent_dispatch,
NULL);
- /* the handshake may already have read the first attach: take it */
+ /* the handshake may have read a later message too: take it */
store_parent_dispatch(parent_iev.ibuf.fd, 0, &parent_iev);
/* sendfd: FETCH hands the listener a read-only descriptor */
-#ifdef __OpenBSD__
if (pledge("stdio rpath wpath cpath flock recvfd sendfd", NULL) == -1)
fatal("pledge");
-#endif
log_debug("session %u: store ready (uid %u, gid %u, spool %s, "
"maildir %s)", session_id, init.uid, init.gid, init.spool_root,
imsg_free(&imsg);
}
imsgev_rearm_read(iev);
- (void)fd;
}
static void
break;
}
if (id == 0 || ss != NULL) {
- log_warnx("session %u: refusing a session that is 0 or "
- "already attached", id);
+ log_warnx("session %u: attach refused: id 0 or already "
+ "attached", id);
close(fd);
return;
}
return (!inquote && !escaped && depth == 0);
}
-/* RFC 9051 SS9: a literal may not hold NUL */
+/* RFC 9051 section 9: a literal may not hold NUL */
static int
parser_literal_safe(const char *buf, size_t len)
{
*rep_out = rep;
rc = 0;
} else
- log_warnx("session %u: parser-worker returned "
- "a %s that cannot be sent, message %s "
- "skipped", session_id, label, basename);
+ log_warnx("session %u: parser %s unsendable, "
+ "message %s skipped",
+ session_id, label, basename);
} else if (rep.found && rep.len > 0 && rep.len <= maxlen &&
imsg_get_len(&imsg) == rep.len) {
if ((*buf_out = malloc(rep.len)) == NULL)
*buf_out = NULL;
} else if (!parser_reply_valid(type, *buf_out,
rep.len, maxlen)) {
- log_warnx("session %u: parser-worker returned "
- "a %s that cannot be sent, message %s "
- "skipped", session_id, label, basename);
+ log_warnx("session %u: parser %s unsendable, "
+ "message %s skipped",
+ session_id, label, basename);
free(*buf_out);
*buf_out = NULL;
} else {
parser_strikes[i].count = 0;
}
if (++parser_strikes[i].count == PARSER_STRIKES)
- log_warnx("session %u: message %s has failed the "
- "parser-worker %d times, not sending it again",
- session_id, basename, PARSER_STRIKES);
+ log_warnx("session %u: message %s: parser failed %d times, "
+ "not retried", session_id, basename, PARSER_STRIKES);
}
static int
return;
}
if (parser_up) {
- log_warnx("store: a second parser-worker, refusing it");
+ log_warnx("store: second parser-worker refused");
close(fd);
return;
}
static void
parser_idle(int fd, short event, void *arg)
{
- (void)fd;
- (void)event;
- (void)arg;
log_debug("store: parser-worker idle, letting it go");
parser_drop();
}
static void
parser_wait_expired(int fd, short event, void *arg)
{
- (void)fd;
- (void)event;
- (void)arg;
log_warnx("store: no parser-worker within %d seconds",
PARSER_REPLY_TIMEOUT_SEC);
parser_give_up();
int
mailbox_name_valid(const char *name)
{
- /* not NUL-terminated by imsg_get_data() */
if (memchr(name, '\0', MBOX_NAME_MAX) == NULL) {
- log_warnx("session %u: mailbox name field is not "
- "NUL-terminated, refusing", session_id);
+ log_warnx("session %u: mailbox name not NUL-terminated",
+ session_id);
return (0);
}
return (mailbox_name_syntax_ok(name));
}
-
-
int
mailbox_open_dir(const char *target)
{
O_RDONLY | O_DIRECTORY));
}
+/* opendir(3) relative to dfd; NULL with errno set, nothing left open */
+DIR *
+opendirat(int dfd, const char *name)
+{
+ DIR *dp;
+ int fd, saved_errno;
+
+ if ((fd = openat(dfd, name, O_RDONLY | O_DIRECTORY)) == -1)
+ return (NULL);
+ if ((dp = fdopendir(fd)) == NULL) {
+ saved_errno = errno;
+ close(fd);
+ errno = saved_errno;
+ }
+ return (dp);
+}
+
static int
imsg_field_valid(const char *field, size_t size, const char *what)
{
if (memchr(field, '\0', size) != NULL)
return (1);
- log_warnx("session %u: %s is not NUL-terminated, refusing the "
- "request", session_id, what);
+ log_warnx("session %u: %s not NUL-terminated", session_id, what);
return (0);
}
for (i = 0; i < nnodes; i++) {
n = &nodes[i];
if (memchr(n->keyword, '\0', sizeof(n->keyword)) == NULL) {
- log_warnx("session %u: SEARCH node %u keyword is not "
- "NUL-terminated, refusing the request",
- session_id, i);
+ log_warnx("session %u: SEARCH node %u: keyword not "
+ "NUL-terminated", session_id, i);
return (0);
}
if (search_op_content(n->op) && (n->str_off > poollen ||
n->str_len > poollen - n->str_off ||
n->name_off > poollen ||
n->name_len > poollen - n->name_off)) {
- log_warnx("session %u: SEARCH node %u string lies "
- "outside its pool, refusing the request",
- session_id, i);
+ log_warnx("session %u: SEARCH node %u: string outside "
+ "its pool", session_id, i);
return (0);
}
}
}
bodylen = imsg_get_len(imsg);
if (bodylen != (size_t)count * elemsize) {
- log_warnx("session %u: %s length mismatch (header says %u "
- "elements, imsg has %zu bytes)", session_id, what, count,
- bodylen);
+ log_warnx("session %u: %s length mismatch (header %u "
+ "elements, imsg %zu bytes)",
+ session_id, what, count, bodylen);
return (NULL);
}
*ok_out = 1;
return (now.tv_nsec >= d->give_up_at.tv_nsec);
}
-/* RFC 9051 SS7.1 INUSE: answers one command, having changed nothing. */
+/* RFC 9051 section 7.1 INUSE: answers one command, having changed nothing. */
static void
deferred_answer_busy_for(uint32_t type, struct store_session *ss)
{
size_t len = 0;
uint32_t rtype = 0;
- /* RFC 9051 SS6.4.4.1: a SAVE answered NO empties "$" */
+ /* RFC 9051 section 6.4.4.1: a SAVE answered NO empties "$" */
if (type == IMSG_MBOX_SEARCH &&
(ss->deferred.req.search.return_opts & SEARCH_RETURN_SAVE))
ss->nsaved = 0;
len = sizeof(appended);
break;
default:
- log_warnx("session %u: no busy reply for imsg %u, can't "
- "happen; only deferrable types are deferred", session_id,
+ fatalx("session %u: no busy reply for imsg %u", session_id,
type);
- break;
}
if (data != NULL && imsg_compose(&iev->ibuf, rtype, 0, 0, -1, data,
len) == -1)
tv.tv_usec = (d->wait_ms % 1000) * 1000;
evtimer_set(&d->ev, deferred_retry, ss);
if (evtimer_add(&d->ev, &tv) == -1) {
- log_warnx("session %u: no timer for the index lock retry; "
- "answering busy now rather than waiting for ever",
- session_id);
+ log_warnx("session %u: evtimer_add (lock retry), answering "
+ "busy", session_id);
deferred_give_up(ss);
return;
}
struct store_deferred *d = &ss->deferred;
int again;
- (void)fd;
- (void)event;
session_id = ss->id;
switch (d->type) {
again = handle_mbox_append_end(ss);
break;
default:
- log_warnx("session %u: cannot re-run imsg %u, can't happen",
- session_id, d->type);
- deferred_give_up(ss);
- return;
+ fatalx("session %u: deferred imsg %u", session_id, d->type);
}
if (!again) {
d->active = 0;
struct timespec now;
if (d->active) {
- log_warnx("session %u: a second command to defer while one "
- "waits, refusing the new one", session_id);
+ log_warnx("session %u: a second deferred command, refused",
+ session_id);
deferred_answer_busy_for(type, ss);
return;
}
if (reqlen > sizeof(d->req) || nelts > maxelts ||
- (size_t)nelts * eltlen > sizeof(d->elts)) {
- /* the handler answered nothing, so something must */
- log_warnx("session %u: imsg %u too large to defer, can't "
- "happen, it is checked on receipt", session_id, type);
- deferred_answer_busy_for(type, ss);
- return;
- }
+ (size_t)nelts * eltlen > sizeof(d->elts))
+ fatalx("session %u: imsg %u too large to defer", session_id,
+ type);
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
log_warn("session %u: clock_gettime", session_id);
now.tv_sec = 0;
{
if (ss->mailbox_selected)
return (1);
- log_warnx("session %u: %s before a successful IMSG_MBOX_SELECT, "
- "refusing", session_id, what);
+ log_warnx("session %u: %s before IMSG_MBOX_SELECT, refused",
+ session_id, what);
return (0);
}
+static struct seq_range *
+recv_ranges(struct imsg *imsg, const char *what, uint32_t nranges, int *ok)
+{
+ return (recv_trailing_array(imsg, what, nranges, SEQSET_MAX_RANGES,
+ sizeof(struct seq_range), ok));
+}
+
+static int
+ranges_required(uint32_t nranges, const char *what)
+{
+ if (nranges != 0)
+ return (1);
+ log_warnx("session %u: %s requires at least one range", session_id,
+ what);
+ return (0);
+}
+
+/* rc is the handler's: 1 keeps the command for the index lock */
+static void
+ranged_done(struct store_session *ss, uint32_t type, const void *req,
+ size_t reqlen, struct seq_range *ranges, uint32_t nranges, int rc)
+{
+ if (rc == 1)
+ defer_command(type, req, reqlen, ranges, nranges,
+ SEQSET_MAX_RANGES, sizeof(*ranges), ss);
+ free(ranges);
+}
+
void
store_dispatch(int fd, short event, void *arg)
{
struct store_session *ss = arg;
struct imsgev *iev = &ss->iev;
- struct imsg imsg;
- ssize_t n;
+ struct imsg imsg;
+ union mbox_request u;
+ struct seq_range *ranges;
+ struct search_node *nodes;
+ char buf[MAX_IMSGSIZE];
+ size_t len;
+ ssize_t n;
+ int ok, rc;
/* the account's other sessions share this process: label the log */
session_id = ss->id;
imsg_free(&imsg);
store_end(ss);
return;
- case IMSG_MBOX_SELECT: {
- struct imsg_mbox_select req;
- struct seq_range *ranges;
- int ok;
-
- if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_SELECT (header)");
+ case IMSG_MBOX_SELECT:
+ if (!recv_header(&imsg, &u.select, sizeof(u.select),
+ "IMSG_MBOX_SELECT"))
break;
- }
- ranges = recv_trailing_array(&imsg, "IMSG_MBOX_SELECT",
- req.qresync_nranges, SEQSET_MAX_RANGES,
- sizeof(struct seq_range), &ok);
+ ranges = recv_ranges(&imsg, "IMSG_MBOX_SELECT",
+ u.select.qresync_nranges, &ok);
if (!ok)
break;
- /* RFC 9051 SS6.3.2: failure deselects */
+ /* RFC 9051 section 6.3.2: failure deselects */
ss->mailbox_selected = 0;
-
- if (handle_mbox_select(&req, ranges,
- req.qresync_nranges, ss) == 1)
- defer_command(IMSG_MBOX_SELECT, &req,
- sizeof(req), ranges, req.qresync_nranges,
- SEQSET_MAX_RANGES, sizeof(*ranges), ss);
- free(ranges);
+ rc = handle_mbox_select(&u.select, ranges,
+ u.select.qresync_nranges, ss);
+ ranged_done(ss, IMSG_MBOX_SELECT, &u.select,
+ sizeof(u.select), ranges, u.select.qresync_nranges,
+ rc);
break;
- }
- case IMSG_MBOX_FETCH: {
- struct imsg_mbox_fetch req;
- struct seq_range *ranges;
- int ok;
-
- if (!require_mailbox_selected(ss, "IMSG_MBOX_FETCH"))
- break;
-
- if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_FETCH (header)");
- break;
- }
- if (!imsg_field_valid(req.header_fields,
- sizeof(req.header_fields),
+ case IMSG_MBOX_FETCH:
+ if (!require_mailbox_selected(ss, "IMSG_MBOX_FETCH") ||
+ !recv_header(&imsg, &u.fetch, sizeof(u.fetch),
+ "IMSG_MBOX_FETCH") ||
+ !imsg_field_valid(u.fetch.header_fields,
+ sizeof(u.fetch.header_fields),
"IMSG_MBOX_FETCH.header_fields") ||
- !imsg_field_valid(req.section_part,
- sizeof(req.section_part),
- "IMSG_MBOX_FETCH.section_part"))
+ !imsg_field_valid(u.fetch.section_part,
+ sizeof(u.fetch.section_part),
+ "IMSG_MBOX_FETCH.section_part") ||
+ !ranges_required(u.fetch.nranges,
+ "IMSG_MBOX_FETCH"))
break;
- if (req.nranges == 0) {
- log_warnx("session %u: IMSG_MBOX_FETCH "
- "requires at least one range", session_id);
- break;
- }
- ranges = recv_trailing_array(&imsg, "IMSG_MBOX_FETCH",
- req.nranges, SEQSET_MAX_RANGES,
- sizeof(struct seq_range), &ok);
+ ranges = recv_ranges(&imsg, "IMSG_MBOX_FETCH",
+ u.fetch.nranges, &ok);
if (!ok)
break;
- if (handle_mbox_fetch(&req, ranges, req.nranges,
- ss) == 1)
- defer_command(IMSG_MBOX_FETCH, &req,
- sizeof(req), ranges, req.nranges,
- SEQSET_MAX_RANGES, sizeof(*ranges), ss);
- free(ranges);
+ rc = handle_mbox_fetch(&u.fetch, ranges,
+ u.fetch.nranges, ss);
+ ranged_done(ss, IMSG_MBOX_FETCH, &u.fetch,
+ sizeof(u.fetch), ranges, u.fetch.nranges, rc);
break;
- }
- case IMSG_MBOX_STORE: {
- struct imsg_mbox_store req;
- struct seq_range *ranges;
- int ok;
-
- if (!require_mailbox_selected(ss, "IMSG_MBOX_STORE"))
+ case IMSG_MBOX_STORE:
+ if (!require_mailbox_selected(ss, "IMSG_MBOX_STORE") ||
+ !recv_header(&imsg, &u.store, sizeof(u.store),
+ "IMSG_MBOX_STORE") ||
+ !imsg_field_valid(u.store.keywords,
+ sizeof(u.store.keywords),
+ "IMSG_MBOX_STORE.keywords") ||
+ !ranges_required(u.store.nranges,
+ "IMSG_MBOX_STORE"))
break;
-
- if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_STORE (header)");
- break;
- }
- if (!imsg_field_valid(req.keywords,
- sizeof(req.keywords), "IMSG_MBOX_STORE.keywords"))
- break;
- if (req.nranges == 0) {
- log_warnx("session %u: IMSG_MBOX_STORE "
- "requires at least one range", session_id);
- break;
- }
- ranges = recv_trailing_array(&imsg, "IMSG_MBOX_STORE",
- req.nranges, SEQSET_MAX_RANGES,
- sizeof(struct seq_range), &ok);
+ ranges = recv_ranges(&imsg, "IMSG_MBOX_STORE",
+ u.store.nranges, &ok);
if (!ok)
break;
- if (handle_mbox_store(&req, ranges, req.nranges,
- ss) == 1)
- defer_command(IMSG_MBOX_STORE, &req,
- sizeof(req), ranges, req.nranges,
- SEQSET_MAX_RANGES, sizeof(*ranges), ss);
- free(ranges);
+ rc = handle_mbox_store(&u.store, ranges,
+ u.store.nranges, ss);
+ ranged_done(ss, IMSG_MBOX_STORE, &u.store,
+ sizeof(u.store), ranges, u.store.nranges, rc);
break;
- }
- case IMSG_MBOX_EXPUNGE: {
- struct imsg_mbox_expunge req;
- struct seq_range *ranges;
- int ok;
-
- if (!require_mailbox_selected(ss, "IMSG_MBOX_EXPUNGE"))
+ case IMSG_MBOX_EXPUNGE:
+ if (!require_mailbox_selected(ss,
+ "IMSG_MBOX_EXPUNGE") ||
+ !recv_header(&imsg, &u.expunge, sizeof(u.expunge),
+ "IMSG_MBOX_EXPUNGE"))
break;
-
- if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_EXPUNGE (header)");
- break;
- }
- ranges = recv_trailing_array(&imsg,
- "IMSG_MBOX_EXPUNGE", req.nranges,
- SEQSET_MAX_RANGES, sizeof(struct seq_range), &ok);
+ ranges = recv_ranges(&imsg, "IMSG_MBOX_EXPUNGE",
+ u.expunge.nranges, &ok);
if (!ok)
break;
- if (handle_mbox_expunge(&req, ranges, req.nranges,
- ss) == 1)
- defer_command(IMSG_MBOX_EXPUNGE, &req,
- sizeof(req), ranges, req.nranges,
- SEQSET_MAX_RANGES, sizeof(*ranges), ss);
- free(ranges);
+ rc = handle_mbox_expunge(&u.expunge, ranges,
+ u.expunge.nranges, ss);
+ ranged_done(ss, IMSG_MBOX_EXPUNGE, &u.expunge,
+ sizeof(u.expunge), ranges, u.expunge.nranges, rc);
break;
- }
case IMSG_MBOX_IDLE_REFRESH:
if (imsg_get_len(&imsg) != 0) {
log_warnx("bad IMSG_MBOX_IDLE_REFRESH");
"IMSG_MBOX_IDLE_REFRESH");
handle_mbox_idle_refresh(ss);
break;
- case IMSG_MBOX_APPEND: {
- struct imsg_mbox_append req;
-
+ case IMSG_MBOX_APPEND:
/* no reply: the literal follows regardless */
- if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_APPEND");
- break;
- }
- if (!imsg_field_valid(req.keywords,
- sizeof(req.keywords), "IMSG_MBOX_APPEND.keywords"))
- break;
- handle_mbox_append_begin(ss, &req);
+ if (recv_fixed(&imsg, &u.append, sizeof(u.append),
+ "IMSG_MBOX_APPEND") &&
+ imsg_field_valid(u.append.keywords,
+ sizeof(u.append.keywords),
+ "IMSG_MBOX_APPEND.keywords"))
+ handle_mbox_append_begin(ss, &u.append);
break;
- }
- case IMSG_MBOX_APPEND_DATA: {
- char buf[MAX_IMSGSIZE];
- size_t len;
-
+ case IMSG_MBOX_APPEND_DATA:
len = imsg_get_len(&imsg);
if (len == 0 || len > sizeof(buf) ||
imsg_get_buf(&imsg, buf, len) == -1) {
}
handle_mbox_append_data(ss, buf, len);
break;
- }
case IMSG_MBOX_APPEND_END:
if (handle_mbox_append_end(ss) == 1)
defer_command(IMSG_MBOX_APPEND_END, NULL, 0,
NULL, 0, 0, 0, ss);
break;
- case IMSG_MBOX_SEARCH: {
- struct imsg_mbox_search req;
- struct search_node *nodes;
- int ok;
-
- if (!require_mailbox_selected(ss, "IMSG_MBOX_SEARCH"))
+ case IMSG_MBOX_SEARCH:
+ if (!require_mailbox_selected(ss, "IMSG_MBOX_SEARCH") ||
+ !recv_header(&imsg, &u.search, sizeof(u.search),
+ "IMSG_MBOX_SEARCH"))
break;
-
- if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1 ||
- req.poollen > sizeof(req.pool)) {
+ if (u.search.poollen > sizeof(u.search.pool)) {
log_warnx("bad IMSG_MBOX_SEARCH (header)");
break;
}
nodes = recv_trailing_array(&imsg, "IMSG_MBOX_SEARCH",
- req.nnodes, SEARCH_PROGRAM_MAX_NODES,
+ u.search.nnodes, SEARCH_PROGRAM_MAX_NODES,
sizeof(struct search_node), &ok);
if (!ok)
break;
- if (nodes != NULL &&
- !search_nodes_valid(nodes, req.nnodes,
- req.poollen)) {
+ if (nodes != NULL && !search_nodes_valid(nodes,
+ u.search.nnodes, u.search.poollen)) {
free(nodes);
break;
}
- if (handle_mbox_search(&req, nodes, req.nnodes,
- ss) == 1)
- defer_command(IMSG_MBOX_SEARCH, &req,
- sizeof(req), nodes, req.nnodes,
+ if (handle_mbox_search(&u.search, nodes,
+ u.search.nnodes, ss) == 1)
+ defer_command(IMSG_MBOX_SEARCH, &u.search,
+ sizeof(u.search), nodes, u.search.nnodes,
SEARCH_PROGRAM_MAX_NODES, sizeof(*nodes),
ss);
free(nodes);
break;
- }
case IMSG_MBOX_SAVED_CLEAR:
if (imsg_get_len(&imsg) != 0) {
log_warnx("bad IMSG_MBOX_SAVED_CLEAR");
}
ss->nsaved = 0;
break;
- case IMSG_MBOX_STATUS: {
- struct imsg_mbox_status req;
-
- if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_STATUS");
- break;
- }
- if (handle_mbox_status(&req, ss) == 1)
- defer_command(IMSG_MBOX_STATUS, &req,
- sizeof(req), NULL, 0, 0, 0, ss);
+ case IMSG_MBOX_STATUS:
+ if (recv_fixed(&imsg, &u.status, sizeof(u.status),
+ "IMSG_MBOX_STATUS") &&
+ handle_mbox_status(&u.status, ss) == 1)
+ defer_command(IMSG_MBOX_STATUS, &u.status,
+ sizeof(u.status), NULL, 0, 0, 0, ss);
break;
- }
- case IMSG_MBOX_COPY: {
- struct imsg_mbox_copy req;
- struct seq_range *ranges;
- int ok;
-
- if (!require_mailbox_selected(ss, "IMSG_MBOX_COPY"))
+ case IMSG_MBOX_COPY:
+ if (!require_mailbox_selected(ss, "IMSG_MBOX_COPY") ||
+ !recv_header(&imsg, &u.copy, sizeof(u.copy),
+ "IMSG_MBOX_COPY") ||
+ !ranges_required(u.copy.nranges, "IMSG_MBOX_COPY"))
break;
-
- if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_COPY (header)");
- break;
- }
- if (req.nranges == 0) {
- log_warnx("session %u: IMSG_MBOX_COPY "
- "requires at least one range", session_id);
- break;
- }
- ranges = recv_trailing_array(&imsg, "IMSG_MBOX_COPY",
- req.nranges, SEQSET_MAX_RANGES,
- sizeof(struct seq_range), &ok);
+ ranges = recv_ranges(&imsg, "IMSG_MBOX_COPY",
+ u.copy.nranges, &ok);
if (!ok)
break;
- if (handle_mbox_copy(&req, ranges, req.nranges,
- ss) == 1)
- defer_command(IMSG_MBOX_COPY, &req,
- sizeof(req), ranges, req.nranges,
- SEQSET_MAX_RANGES, sizeof(*ranges), ss);
- free(ranges);
+ rc = handle_mbox_copy(&u.copy, ranges, u.copy.nranges,
+ ss);
+ ranged_done(ss, IMSG_MBOX_COPY, &u.copy, sizeof(u.copy),
+ ranges, u.copy.nranges, rc);
break;
- }
- case IMSG_MBOX_MOVE: {
- struct imsg_mbox_copy req;
- struct seq_range *ranges;
- int ok;
-
- if (!require_mailbox_selected(ss, "IMSG_MBOX_MOVE"))
+ case IMSG_MBOX_MOVE:
+ if (!require_mailbox_selected(ss, "IMSG_MBOX_MOVE") ||
+ !recv_header(&imsg, &u.copy, sizeof(u.copy),
+ "IMSG_MBOX_MOVE") ||
+ !ranges_required(u.copy.nranges, "IMSG_MBOX_MOVE"))
break;
-
- if (imsg_get_buf(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_MOVE (header)");
- break;
- }
- if (req.nranges == 0) {
- log_warnx("session %u: IMSG_MBOX_MOVE "
- "requires at least one range", session_id);
- break;
- }
- ranges = recv_trailing_array(&imsg, "IMSG_MBOX_MOVE",
- req.nranges, SEQSET_MAX_RANGES,
- sizeof(struct seq_range), &ok);
+ ranges = recv_ranges(&imsg, "IMSG_MBOX_MOVE",
+ u.copy.nranges, &ok);
if (!ok)
break;
- if (handle_mbox_move(&req, ranges, req.nranges,
- ss) == 1)
- defer_command(IMSG_MBOX_MOVE, &req,
- sizeof(req), ranges, req.nranges,
- SEQSET_MAX_RANGES, sizeof(*ranges), ss);
- free(ranges);
+ rc = handle_mbox_move(&u.copy, ranges, u.copy.nranges,
+ ss);
+ ranged_done(ss, IMSG_MBOX_MOVE, &u.copy, sizeof(u.copy),
+ ranges, u.copy.nranges, rc);
break;
- }
- case IMSG_MBOX_LIST: {
- struct imsg_mbox_list req;
-
- if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_LIST");
- break;
- }
- handle_mbox_list(&req, iev);
+ case IMSG_MBOX_LIST:
+ if (recv_fixed(&imsg, &u.list, sizeof(u.list),
+ "IMSG_MBOX_LIST"))
+ handle_mbox_list(&u.list, iev);
break;
- }
- case IMSG_MBOX_CREATE: {
- struct imsg_mbox_create req;
-
- if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_CREATE");
- break;
- }
- handle_mbox_create(&req, iev);
+ case IMSG_MBOX_CREATE:
+ if (recv_fixed(&imsg, &u.create, sizeof(u.create),
+ "IMSG_MBOX_CREATE"))
+ handle_mbox_create(&u.create, iev);
break;
- }
- case IMSG_MBOX_DELETE: {
- struct imsg_mbox_delete req;
-
- if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_DELETE");
- break;
- }
- handle_mbox_delete(&req, ss);
+ case IMSG_MBOX_DELETE:
+ if (recv_fixed(&imsg, &u.delete, sizeof(u.delete),
+ "IMSG_MBOX_DELETE"))
+ handle_mbox_delete(&u.delete, ss);
break;
- }
- case IMSG_MBOX_RENAME: {
- struct imsg_mbox_rename req;
-
- if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_RENAME");
- break;
- }
- handle_mbox_rename(&req, ss);
+ case IMSG_MBOX_RENAME:
+ if (recv_fixed(&imsg, &u.rename, sizeof(u.rename),
+ "IMSG_MBOX_RENAME"))
+ handle_mbox_rename(&u.rename, ss);
break;
- }
- case IMSG_MBOX_SUBSCRIBE: {
- struct imsg_mbox_subscribe req;
-
- if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_SUBSCRIBE");
- break;
- }
- handle_mbox_subscribe(&req, iev);
+ case IMSG_MBOX_SUBSCRIBE:
+ if (recv_fixed(&imsg, &u.subscribe, sizeof(u.subscribe),
+ "IMSG_MBOX_SUBSCRIBE"))
+ handle_mbox_subscribe(&u.subscribe, iev);
break;
- }
- case IMSG_MBOX_UNSUBSCRIBE: {
- struct imsg_mbox_subscribe req;
-
- if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
- log_warnx("bad IMSG_MBOX_UNSUBSCRIBE");
- break;
- }
- handle_mbox_unsubscribe(&req, iev);
+ case IMSG_MBOX_UNSUBSCRIBE:
+ if (recv_fixed(&imsg, &u.subscribe, sizeof(u.subscribe),
+ "IMSG_MBOX_UNSUBSCRIBE"))
+ handle_mbox_unsubscribe(&u.subscribe, iev);
break;
- }
default:
log_debug("store_dispatch: unhandled %d (session %u)",
imsg_get_type(&imsg), session_id);
cur_snapshot_discard(&ss->cur_snap);
}
imsgev_rearm_read(iev);
- (void)fd;
}
static void
blob - 0694acc1ffb3e92c45054d2d96a511d9ccdbcd5d
blob + f82ed4aea344afcc854de6974ae2f4a236c22df2
--- src/store_cache.c
+++ src/store_cache.c
/*
* ENVELOPE and BODYSTRUCTURE text, as checked by parser_request(), kept
- * per account worker. The key is RFC 9051 SS2.3.1.1's mailbox name,
+ * per account worker. The key is RFC 9051 section 2.3.1.1's mailbox name,
* UIDVALIDITY and UID; the index's file name is checked on every hit.
* Evicted oldest first, as usr.sbin/smtpd/queue_backend.c evicts.
*/
RB_HEAD(pcache_tree, pcache_entry);
TAILQ_HEAD(pcache_lru, pcache_entry);
-static int pcache_cmp(struct pcache_entry *, struct pcache_entry *);
-static struct pcache_entry *pcache_find(const char *, uint32_t, uint32_t,
- const char *);
-static void pcache_free(struct pcache_entry *);
+static int pcache_cmp(struct pcache_entry *,
+ struct pcache_entry *);
+static struct pcache_entry *pcache_find(const char *, uint32_t, uint32_t,
+ const char *);
+static void pcache_free(struct pcache_entry *);
RB_PROTOTYPE_STATIC(pcache_tree, pcache_entry, node, pcache_cmp);
RB_GENERATE_STATIC(pcache_tree, pcache_entry, node, pcache_cmp);
}
}
-/* a new UIDVALIDITY (RFC 9051 SS2.3.1.1) ends the old entries */
+/* a new UIDVALIDITY (RFC 9051 section 2.3.1.1) ends the old entries */
void
pcache_check_mailbox(const char *mailbox, uint32_t uidvalidity)
{
blob - 35ffc566b73f498e61f59bf40776748d26b87b89
blob + 1a90d4829bde5cb8355514fef2b9a56494558e36
--- src/store_cmd.c
+++ src/store_cmd.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
int
cmd_idle(struct session *s, const char *tag, char *args)
{
- /* RFC 9051 SS6.3.13: IDLE takes no arguments */
- (void)args;
-
if (strlcpy(s->pending_tag, tag, sizeof(s->pending_tag)) >=
sizeof(s->pending_tag)) {
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
return (1);
}
-/* RFC 9051 SS6.4.1 CLOSE: a silent EXPUNGE */
+/* RFC 9051 section 6.4.1 CLOSE: a silent EXPUNGE */
int
cmd_close(struct session *s, const char *tag, char *args)
{
- (void)args;
- return session_request_expunge(s, tag, 1, 0, NULL, 0);
+ return (session_request_expunge(s, tag, 1, 0, NULL, 0));
}
-/* RFC 9051 SS6.4.2 UNSELECT */
+/* RFC 9051 section 6.4.2 UNSELECT */
int
cmd_unselect(struct session *s, const char *tag, char *args)
{
- (void)args;
-
s->state = SESSION_AUTHENTICATED;
session_reply(s, tag, "OK", "Unselect completed");
return (1);
}
-/* RFC 9051 SS6.4.3 EXPUNGE */
+/* RFC 9051 section 6.4.3 EXPUNGE */
int
cmd_expunge(struct session *s, const char *tag, char *args)
{
- (void)args;
- return session_request_expunge(s, tag, 0, 0, NULL, 0);
+ return (session_request_expunge(s, tag, 0, 0, NULL, 0));
}
int
else if (strcasecmp(tok, "\\Draft") == 0)
sysflags |= MBOX_FLAG_DRAFT;
else if (strcasecmp(tok, "\\Recent") == 0) {
- *errmsg = "\\Recent cannot be set, RFC 9051 "
- "deprecates it and excludes it from the "
- "flag grammar entirely";
+ *errmsg = "\\Recent cannot be set";
return (-2);
} else {
*errmsg = "unsupported system flag"
return (0);
}
-/* RFC 7162 SS3.1.3 UNCHANGEDSINCE; 0 is legal */
+/* RFC 7162 section 3.1.3 UNCHANGEDSINCE; 0 is legal */
int
parse_store_modifiers(char *modspec, struct imsg_mbox_store *req,
const char **errmsg)
for (tok = strtok_r(p, " ", &save); tok != NULL;
tok = strtok_r(NULL, " ", &save)) {
if (strcasecmp(tok, "UNCHANGEDSINCE") == 0) {
- const char *valtok = strtok_r(NULL, " ", &save);
- char *ep;
+ const char *valtok, *errstr;
+ valtok = strtok_r(NULL, " ", &save);
if (valtok == NULL) {
*errmsg = "UNCHANGEDSINCE requires a "
"mod-sequence value";
return (-1);
}
- /* strtoull(3) accepts a sign */
if (*valtok < '0' || *valtok > '9') {
*errmsg = "invalid UNCHANGEDSINCE mod-sequence";
return (-1);
}
- errno = 0;
- req->unchangedsince = strtoull(valtok, &ep, 10);
- if (*ep != '\0' || errno != 0 ||
- req->unchangedsince > MODSEQ_MAX) {
+ req->unchangedsince = strtonum(valtok, 0, MODSEQ_MAX,
+ &errstr);
+ if (errstr != NULL) {
*errmsg = "invalid UNCHANGEDSINCE mod-sequence";
return (-1);
}
return (0);
}
-/* RFC 9051 SS6.4.6: store-modifiers lead */
+/* RFC 9051 section 6.4.6: store-modifiers lead */
int
cmd_store_cmd(struct session *s, const char *tag, char *args)
{
- return store_do(s, tag, args, 0);
+ return (store_do(s, tag, args, 0));
}
int
}
if (s->mbox_readonly) {
- /* RFC 9051 SS6.3.3: EXAMINE is read-only */
+ /* RFC 9051 section 6.3.3: EXAMINE is read-only */
session_reply(s, tag, "NO", "[CANNOT] Mailbox is read-only "
"(selected via EXAMINE)");
return (1);
return (1);
}
- /* RFC 7162 SS3.1: UNCHANGEDSINCE enables CONDSTORE */
+ /* RFC 7162 section 3.1: UNCHANGEDSINCE enables CONDSTORE */
if (req.has_unchangedsince)
session_condstore_enable(s);
s->cmd_by_uid = by_uid;
s->state = SESSION_STORING;
- if (!send_mbox_request(s, IMSG_MBOX_STORE, cmdname, "IMSG_MBOX_STORE",
- &req, sizeof(req), ranges, nranges, sizeof(struct seq_range))) {
+ if (send_mbox_request(s, IMSG_MBOX_STORE, "IMSG_MBOX_STORE", &req,
+ sizeof(req), ranges, nranges, sizeof(struct seq_range)) == -1) {
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
s->state = SESSION_SELECTED;
return (1);
return (1);
}
-/* RFC 9051 SS6.4.7 COPY and SS6.4.8 MOVE */
+/* RFC 9051 section 6.4.7 COPY and section 6.4.8 MOVE */
int
copy_move_dispatch(struct session *s, const char *tag, char *args,
int by_uid, int is_move)
if (listener_reject_bad_utf8(s, tag, mailbox))
return (1);
- /* a name the server refuses is RFC 5530 SS3 NO [CANNOT], not BAD */
+ /* a refused name is RFC 5530 section 3 NO [CANNOT], not BAD */
if (!mailbox_name_is_inbox(mailbox) &&
!listener_mailbox_name_valid(mailbox)) {
session_reply(s, tag, "NO", "[CANNOT] invalid mailbox name");
}
if (is_move && s->mbox_readonly) {
- /* RFC 9051 SS6.3.3: MOVE removes messages; COPY may proceed */
+ /* RFC 9051 section 6.3.3: MOVE would remove; COPY is fine */
session_reply(s, tag, "NO", "[CANNOT] Mailbox is read-only "
"(selected via EXAMINE)");
return (1);
s->cmd_is_move = is_move;
s->state = SESSION_COPYING;
- if (!send_mbox_request(s, is_move ? IMSG_MBOX_MOVE : IMSG_MBOX_COPY,
- cmdname, cmdname, &req, sizeof(req), ranges, nranges,
- sizeof(struct seq_range))) {
+ if (send_mbox_request(s, is_move ? IMSG_MBOX_MOVE : IMSG_MBOX_COPY,
+ is_move ? "IMSG_MBOX_MOVE" : "IMSG_MBOX_COPY", &req, sizeof(req),
+ ranges, nranges, sizeof(struct seq_range)) == -1) {
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
s->state = SESSION_SELECTED;
return (1);
return (1);
}
-
int
cmd_copy(struct session *s, const char *tag, char *args)
{
- return copy_move_dispatch(s, tag, args, 0, 0);
+ return (copy_move_dispatch(s, tag, args, 0, 0));
}
-
int
cmd_move(struct session *s, const char *tag, char *args)
{
- return copy_move_dispatch(s, tag, args, 0, 1);
+ return (copy_move_dispatch(s, tag, args, 0, 1));
}
-/* RFC 9051 SS6.4.9 UID */
+/* RFC 9051 section 6.4.9 UID */
int
cmd_uid(struct session *s, const char *tag, char *args)
{
subargs = NULL;
if (strcasecmp(sub, "FETCH") == 0)
- return fetch_dispatch(s, tag, subargs, 1);
+ return (fetch_dispatch(s, tag, subargs, 1));
if (strcasecmp(sub, "STORE") == 0)
- return store_do(s, tag, subargs, 1);
+ return (store_do(s, tag, subargs, 1));
if (strcasecmp(sub, "SEARCH") == 0)
- return search_dispatch(s, tag, subargs, 1);
+ return (search_dispatch(s, tag, subargs, 1));
if (strcasecmp(sub, "EXPUNGE") == 0)
- return uid_expunge_dispatch(s, tag, subargs);
+ return (uid_expunge_dispatch(s, tag, subargs));
if (strcasecmp(sub, "COPY") == 0)
- return copy_move_dispatch(s, tag, subargs, 1, 0);
+ return (copy_move_dispatch(s, tag, subargs, 1, 0));
if (strcasecmp(sub, "MOVE") == 0)
- return copy_move_dispatch(s, tag, subargs, 1, 1);
+ return (copy_move_dispatch(s, tag, subargs, 1, 1));
session_reply(s, tag, "BAD",
"UID sub-command must be COPY, FETCH, MOVE, SEARCH, or STORE");
return (1);
}
-/* RFC 9051 SS7.5.1 EXPUNGE, or RFC 7162 SS3.2.10.2 VANISHED */
+/* RFC 9051 section 7.5.1 EXPUNGE, or RFC 7162 section 3.2.10.2 VANISHED */
void
session_send_expunge_response(struct session *s,
const struct imsg_mbox_expunged *exp)
session_untagged(s, buf);
}
-/* RFC 7162 SS3.2.6 VANISHED (EARLIER) */
+/* RFC 7162 section 3.2.6 VANISHED (EARLIER) */
void
session_handle_fetch_vanished(struct session *s,
const struct imsg_mbox_select_vanished *v)
if (s->mbox_readonly) {
if (is_close) {
- /* RFC 9051 SS6.4.1: read-only, so just deselect */
+ /* RFC 9051 section 6.4.1: read-only, just deselect */
s->state = SESSION_AUTHENTICATED;
session_reply(s, tag, "OK", "CLOSE completed");
return (1);
}
- /* RFC 9051 SS6.3.3: no read-only exception for EXPUNGE */
+ /* RFC 9051 section 6.3.3: no read-only exception for EXPUNGE */
session_reply(s, tag, "NO", "[CANNOT] Mailbox is read-only "
"(selected via EXAMINE)");
return (1);
s->cmd_by_uid = by_uid;
s->state = SESSION_EXPUNGING;
- if (!send_mbox_request(s, IMSG_MBOX_EXPUNGE, cmdname,
+ if (send_mbox_request(s, IMSG_MBOX_EXPUNGE,
"IMSG_MBOX_EXPUNGE", &req, sizeof(req), ranges, nranges,
- sizeof(struct seq_range))) {
+ sizeof(struct seq_range)) == -1) {
session_reply(s, tag, "NO", "[SERVERBUG] internal error");
s->state = SESSION_SELECTED;
return (1);
return (1);
}
- return session_request_expunge(s, tag, 0, 1, ranges, nranges);
+ return (session_request_expunge(s, tag, 0, 1, ranges, nranges));
}
-/* RFC 7162 SS3.1.3 MODIFIED entry */
+/* RFC 7162 section 3.1.3 MODIFIED entry */
void
session_handle_store_modified(struct session *s,
struct imsg_mbox_store_modified *m)
if (s->store_modified_n == s->store_modified_cap) {
uint32_t newcap = s->store_modified_cap ?
s->store_modified_cap * 2 : 16;
- uint32_t *n = reallocarray(s->store_modified, newcap,
- sizeof(*n));
+ uint32_t *n;
+ n = reallocarray(s->store_modified, newcap, sizeof(*n));
if (n == NULL) {
log_warn("session %u: realloc STORE MODIFIED array",
s->id);
}
/* refuses rather than truncates when it would not fit */
-static size_t
-append_range_token(char *buf, size_t bufsize, size_t written, int *first,
+/* appends "lo" or "lo:hi" after a comma; -1 when it would not fit */
+static int
+append_range_token(char *buf, size_t bufsize, size_t *written, int *first,
uint32_t lo, uint32_t hi, int *truncated)
{
char tok[24];
else
toklen = snprintf(tok, sizeof(tok), "%u:%u", lo, hi);
if (toklen < 0)
- return ((size_t)-1);
+ return (-1);
- if (written + (size_t)toklen + (*first ? 0 : 1) >= bufsize) {
+ if (*written + (size_t)toklen + (*first ? 0 : 1) >= bufsize) {
*truncated = 1;
- return ((size_t)-1);
+ return (-1);
}
if (!*first)
- buf[written++] = ',';
- memcpy(buf + written, tok, (size_t)toklen);
- written += (size_t)toklen;
- buf[written] = '\0';
+ buf[(*written)++] = ',';
+ memcpy(buf + *written, tok, (size_t)toklen);
+ *written += (size_t)toklen;
+ buf[*written] = '\0';
*first = 0;
- return (written);
+ return (0);
}
-/* RFC 9051 SS7.3.4 sequence list; input sorted */
+/* RFC 9051 section 7.3.4 sequence list; input sorted */
size_t
format_seq_list(char *buf, size_t bufsize, const uint32_t *nums, uint32_t n,
int *truncated)
uint32_t start = nums[i];
uint32_t end = start;
uint32_t j = i + 1;
- size_t w;
while (j < n && nums[j] == end + 1) {
end = nums[j];
j++;
}
- w = append_range_token(buf, bufsize, written, &first, start,
- end, truncated);
- if (w == (size_t)-1)
+ if (append_range_token(buf, bufsize, &written, &first, start,
+ end, truncated) == -1)
break;
- written = w;
i = j;
}
buf[0] = '\0';
for (i = 0; i < n; i++) {
- size_t w;
-
- w = append_range_token(buf, bufsize, written, &first,
- ranges[i].lo, ranges[i].hi, truncated);
- if (w == (size_t)-1)
+ if (append_range_token(buf, bufsize, &written, &first,
+ ranges[i].lo, ranges[i].hi, truncated) == -1)
break;
- written = w;
}
return (written);
#define COPYUID_PER_ENTRY 11
#define COPYUID_WRAPPER_MAX 160
-/* RFC 9051 SS6.4.7/SS6.4.8 terminal reply */
+/* RFC 9051 section 6.4.7/section 6.4.8 terminal reply */
void
session_finish_copy_or_move(struct session *s,
const struct imsg_mbox_result *res)
if (res->error != MBOX_OP_OK || s->copy_alloc_failed) {
char text[48];
- /* RFC 9051 SS6.4.7: TRYCREATE */
+ /* RFC 9051 section 6.4.7: TRYCREATE */
if (!s->copy_alloc_failed &&
res->error == MBOX_OP_ERR_NO_SUCH_MAILBOX)
snprintf(text, sizeof(text), "[TRYCREATE] no such "
"mailbox");
else if (!s->copy_alloc_failed &&
res->error == MBOX_OP_ERR_BUSY)
- snprintf(text, sizeof(text), "%s", IMAP_BUSY_TEXT);
+ strlcpy(text, IMAP_BUSY_TEXT, sizeof(text));
else if (!s->copy_alloc_failed &&
res->error == MBOX_OP_ERR_LIMIT)
snprintf(text, sizeof(text), "[LIMIT] %s failed",
cmdname);
else if (!s->copy_alloc_failed &&
res->error == MBOX_OP_ERR_OVERQUOTA)
- snprintf(text, sizeof(text), "%s",
- IMAP_OVERQUOTA_TEXT);
+ strlcpy(text, IMAP_OVERQUOTA_TEXT, sizeof(text));
else
snprintf(text, sizeof(text), "%s failed", cmdname);
session_reply(s, s->pending_tag, "NO", text);
s->mbox_highestmodseq = res->highestmodseq;
-
if (s->copy_n > 0) {
char *srcbuf = NULL, *destbuf = NULL, *text = NULL;
size_t listsize, textsize;
session_reply(s, s->pending_tag, "OK", "Done");
} else if (!sent) {
- /* RFC 9051 SS6.4.7: a SHOULD; never truncated */
+ /* RFC 9051 section 6.4.7: a SHOULD; never truncated */
char fallback[64];
snprintf(fallback, sizeof(fallback), "%s completed",
session_reply(s, s->pending_tag, "OK", fallback);
}
} else {
- /* RFC 9051 SS6.4.7's own worked example, verbatim. */
+ /* RFC 9051 section 6.4.7's own worked example, verbatim. */
session_reply(s, s->pending_tag, "OK",
"No matching messages, so nothing copied");
}
blob - af036bd14d59347e1db3ab7d2c57f9914359fe22
blob + 2ee04d07aa22ba325d4b7df0403cafa80a16c44c
--- src/store_internal.h
+++ src/store_internal.h
#include <sys/types.h>
#include <sys/queue.h>
+#include <dirent.h>
#include <stdint.h>
#include "mboxname.h"
+/*
+ * Returns: handle_mbox_*() 0 once answered, 1 to defer on a busy index
+ * lock; the rest 0/-1 or 1/0, except where a definition says otherwise.
+ */
+
struct mbox_index {
uint32_t uidvalidity;
uint32_t uidnext;
uint64_t seen_modseq;
};
-/* RFC 9051 SS6.4.4 SEARCH, yielding to the account's other sessions */
+/* RFC 9051 section 6.4.4 SEARCH, yielding to the account's other sessions */
struct store_search_walk {
int active;
int wait_parser; /* paused until one comes */
int valid;
};
-/* RFC 9051 SS6.3.12 APPEND; a failure is held until END */
+/* RFC 9051 section 6.3.12 APPEND; a failure is held until END */
struct store_append {
int active;
int failed;
size_t n;
};
+union mbox_request {
+ struct imsg_mbox_select select;
+ struct imsg_mbox_fetch fetch;
+ struct imsg_mbox_store store;
+ struct imsg_mbox_expunge expunge;
+ struct imsg_mbox_search search;
+ struct imsg_mbox_status status;
+ struct imsg_mbox_copy copy;
+ struct imsg_mbox_append append;
+ struct imsg_mbox_list list;
+ struct imsg_mbox_create create;
+ struct imsg_mbox_delete delete;
+ struct imsg_mbox_rename rename;
+ struct imsg_mbox_subscribe subscribe;
+};
+
/* one command waiting on the index lock; re-run from the top */
struct store_deferred {
int active;
struct event ev;
struct timespec give_up_at;
unsigned int wait_ms;
+ union mbox_request req;
union {
- struct imsg_mbox_store store;
- struct imsg_mbox_expunge expunge;
- struct imsg_mbox_fetch fetch;
- struct imsg_mbox_search search;
- struct imsg_mbox_select select;
- struct imsg_mbox_status status;
- struct imsg_mbox_copy copy;
- } req;
- union {
struct seq_range ranges[SEQSET_MAX_RANGES];
struct search_node nodes[SEARCH_PROGRAM_MAX_NODES];
} elts;
size_t hostlen;
};
+/* RFC 2045 section 5.1 Content-Type; RFC 2046 section 5.1.1: boundary <= 70 */
+struct content_type {
+ char type[64];
+ char subtype[64];
+ char params_fmt[600];
+ char boundary[70 + 1];
+ int has_boundary;
+};
+
#define ADDR_MAILBOX 1
#define ADDR_GROUP 2
#define ADDR_GROUP_END 3
TAILQ_ENTRY(store_session) entry;
struct imsgev iev;
- /* a failed SELECT leaves nothing selected (RFC 9051 SS6.3.2) */
+ /* a failed SELECT leaves nothing selected (RFC 9051 section 6.3.2) */
int mailbox_selected;
/* "" for INBOX */
struct store_idle_baseline idle_baseline;
struct store_append append;
- /* RFC 9051 SS6.4.4.1 "$", as UID ranges */
+ /* RFC 9051 section 6.4.4.1 "$", as UID ranges */
struct seq_range saved[SEQSET_MAX_RANGES];
uint32_t nsaved;
};
struct imsgev *);
int mailbox_name_valid(const char *);
int mailbox_open_dir(const char *);
+DIR *opendirat(int, const char *);
void cur_snapshot_discard(struct cur_snapshot *);
int fetch_walk_paused(struct store_session *);
void fetch_walk_resume(struct store_session *);
int mime_read_token_or_qstring(const char *, size_t, size_t *,
char *, size_t);
void mime_str_upper(char *);
-int parse_content_type(const char *, size_t, char *, size_t,
- char *, size_t, char *, size_t, char *, size_t, int *);
+int parse_content_type(const char *, size_t, struct content_type *);
int split_multipart(const char *, size_t, const char *,
size_t *, size_t *, int *, int);
int build_body_structure(int, int *, const char *, size_t,
int index_parse_line(const char *, struct index_rec *);
int index_field_valid(const char *); /* no ':', CR or LF */
int index_basename_valid(const char *); /* safe in "new/%s" */
-uint32_t index_max_uid(struct mbox_index *);
+uint32_t index_max_uid(struct mbox_index *);
void send_vanished_range(const struct mbox_index *, uint32_t, uint32_t,
struct imsgev *);
-uint32_t seqset_resolve(const struct seq_range *, uint32_t, uint32_t,
- int, struct seq_range[SEQSET_MAX_RANGES]);
+uint32_t seqset_resolve(const struct seq_range *, uint32_t, uint32_t, int,
+ struct seq_range[SEQSET_MAX_RANGES]);
int seqset_contains(const struct seq_range *, uint32_t, uint32_t);
-uint32_t seqset_max_hi(const struct seq_range *, uint32_t);
+uint32_t seqset_max_hi(const struct seq_range *, uint32_t);
/* PAST_END holds only because the scans walk in ascending order */
enum seqset_pos {
blob - fb6781900013f0b414aa6a72d370df8ce6e14ddc
blob + 16c3448a2f38c87298731a90db0f7964928411b7
--- src/store_ipc.c
+++ src/store_ipc.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
*foundp = 0;
if (!found)
- /* store.c found/built nothing for this message */
return;
wirelen = imsg_get_len(imsg);
*foundp = 1;
}
+union mbox_reply {
+ struct imsg_mbox_selected selected;
+ struct imsg_mbox_status_result status;
+ struct imsg_mbox_fetch_header header;
+ struct imsg_mbox_fetch_envelope envelope;
+ struct imsg_mbox_fetch_bodystructure bodystructure;
+ struct imsg_mbox_fetch_meta meta;
+ struct imsg_mbox_expunged expunged;
+ struct imsg_mbox_copy_mapping mapping;
+ struct imsg_mbox_search_match match;
+ struct imsg_mbox_select_vanished vanished;
+ struct imsg_mbox_store_modified modified;
+ struct imsg_mbox_view_exists exists;
+ struct imsg_mbox_idle_refreshed refreshed;
+ struct imsg_mbox_result result;
+ struct imsg_mbox_list_item item;
+ struct imsg_mbox_appended appended;
+};
+
+static void
+session_recv_fetch_body(struct session *s, struct imsg *imsg)
+{
+ struct imsg_mbox_fetch_body bodyhdr;
+ int bodyfd;
+
+ bodyfd = imsg_get_fd(imsg);
+ if (s->pending_body_fd != -1)
+ close(s->pending_body_fd);
+ s->pending_body_fd = -1;
+ s->pending_body_off = 0;
+ s->pending_body_len = 0;
+ s->pending_body_found = 0;
+ if (!recv_fixed(imsg, &bodyhdr, sizeof(bodyhdr),
+ "IMSG_MBOX_FETCH_BODY")) {
+ if (bodyfd != -1)
+ close(bodyfd);
+ return;
+ }
+ if (!bodyhdr.found || (bodyhdr.length > 0 && bodyfd == -1)) {
+ if (bodyhdr.found)
+ log_warnx("session %u: IMSG_MBOX_FETCH_BODY without "
+ "its descriptor", s->id);
+ if (bodyfd != -1)
+ close(bodyfd);
+ return;
+ }
+ s->pending_body_fd = bodyfd;
+ s->pending_body_off = bodyhdr.offset;
+ s->pending_body_len = bodyhdr.length;
+ s->pending_body_found = 1;
+}
+
+/* a MOVE's EXPUNGEs wait for COPYUID, RFC 9051 section 6.4.8 */
+static void
+session_hold_expunged(struct session *s, const struct imsg_mbox_expunged *exp)
+{
+ struct imsg_mbox_expunged *enew;
+ uint32_t newcap;
+
+ if (s->move_expunged_n == s->move_expunged_cap) {
+ newcap = s->move_expunged_cap ? s->move_expunged_cap * 2 : 16;
+ enew = reallocarray(s->move_expunged, newcap, sizeof(*enew));
+ if (enew == NULL) {
+ log_warn("session %u: realloc move_expunged", s->id);
+ return;
+ }
+ s->move_expunged = enew;
+ s->move_expunged_cap = newcap;
+ }
+ s->move_expunged[s->move_expunged_n++] = *exp;
+}
+
void
session_store_dispatch(int fd, short event, void *arg)
{
- struct session *s = arg;
- struct imsg imsg;
- ssize_t n;
+ struct session *s = arg;
+ struct imsg imsg;
+ union mbox_reply u;
+ char buf[32];
+ ssize_t n;
if (event & EV_WRITE) {
if (imsgbuf_write(&s->store_iev->ibuf) == -1) {
break;
switch (imsg_get_type(&imsg)) {
- case IMSG_MBOX_SELECTED: {
- struct imsg_mbox_selected res;
-
- if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) {
- log_warnx("bad IMSG_MBOX_SELECTED");
- break;
- }
- session_handle_mbox_selected(s, &res);
+ case IMSG_MBOX_SELECTED:
+ if (recv_fixed(&imsg, &u.selected, sizeof(u.selected),
+ "IMSG_MBOX_SELECTED"))
+ session_handle_mbox_selected(s, &u.selected);
break;
- }
- case IMSG_MBOX_STATUS_RESULT: {
- struct imsg_mbox_status_result res;
-
- if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) {
- log_warnx("bad IMSG_MBOX_STATUS_RESULT");
- break;
- }
- session_handle_mbox_status_result(s, &res);
+ case IMSG_MBOX_STATUS_RESULT:
+ if (recv_fixed(&imsg, &u.status, sizeof(u.status),
+ "IMSG_MBOX_STATUS_RESULT"))
+ session_handle_mbox_status_result(s, &u.status);
break;
- }
- case IMSG_MBOX_FETCH_HEADER: {
- struct imsg_mbox_fetch_header hdr;
-
- if (imsg_get_buf(&imsg, &hdr, sizeof(hdr)) == -1) {
- log_warnx("bad IMSG_MBOX_FETCH_HEADER "
- "(header)");
- break;
- }
- fetch_part_recv(s, &imsg, "IMSG_MBOX_FETCH_HEADER",
- hdr.found, hdr.hdrlen, &s->pending_header_buf,
- &s->pending_header_len, &s->pending_header_found);
+ case IMSG_MBOX_FETCH_HEADER:
+ if (recv_header(&imsg, &u.header, sizeof(u.header),
+ "IMSG_MBOX_FETCH_HEADER"))
+ fetch_part_recv(s, &imsg,
+ "IMSG_MBOX_FETCH_HEADER", u.header.found,
+ u.header.hdrlen, &s->pending_header_buf,
+ &s->pending_header_len,
+ &s->pending_header_found);
break;
- }
- case IMSG_MBOX_FETCH_BODY: {
- struct imsg_mbox_fetch_body bodyhdr;
- int bodyfd;
-
- bodyfd = imsg_get_fd(&imsg);
- if (s->pending_body_fd != -1)
- close(s->pending_body_fd);
- s->pending_body_fd = -1;
- s->pending_body_off = 0;
- s->pending_body_len = 0;
- s->pending_body_found = 0;
- if (imsg_get_data(&imsg, &bodyhdr,
- sizeof(bodyhdr)) == -1) {
- log_warnx("bad IMSG_MBOX_FETCH_BODY");
- if (bodyfd != -1)
- close(bodyfd);
- break;
- }
- if (!bodyhdr.found ||
- (bodyhdr.length > 0 && bodyfd == -1)) {
- if (bodyhdr.found)
- log_warnx("session %u: "
- "IMSG_MBOX_FETCH_BODY without its "
- "descriptor", s->id);
- if (bodyfd != -1)
- close(bodyfd);
- break;
- }
- s->pending_body_fd = bodyfd;
- s->pending_body_off = bodyhdr.offset;
- s->pending_body_len = bodyhdr.length;
- s->pending_body_found = 1;
+ case IMSG_MBOX_FETCH_BODY:
+ session_recv_fetch_body(s, &imsg);
break;
- }
- case IMSG_MBOX_FETCH_ENVELOPE: {
- struct imsg_mbox_fetch_envelope envhdr;
-
- if (imsg_get_buf(&imsg, &envhdr, sizeof(envhdr)) ==
- -1) {
- log_warnx("bad IMSG_MBOX_FETCH_ENVELOPE "
- "(header)");
- break;
- }
- fetch_part_recv(s, &imsg, "IMSG_MBOX_FETCH_ENVELOPE",
- envhdr.found, envhdr.envlen,
- &s->pending_envelope_buf, &s->pending_envelope_len,
- &s->pending_envelope_found);
+ case IMSG_MBOX_FETCH_ENVELOPE:
+ if (recv_header(&imsg, &u.envelope, sizeof(u.envelope),
+ "IMSG_MBOX_FETCH_ENVELOPE"))
+ fetch_part_recv(s, &imsg,
+ "IMSG_MBOX_FETCH_ENVELOPE",
+ u.envelope.found, u.envelope.envlen,
+ &s->pending_envelope_buf,
+ &s->pending_envelope_len,
+ &s->pending_envelope_found);
break;
- }
- case IMSG_MBOX_FETCH_BODYSTRUCTURE: {
- struct imsg_mbox_fetch_bodystructure bshdr;
-
- if (imsg_get_buf(&imsg, &bshdr, sizeof(bshdr)) ==
- -1) {
- log_warnx("bad IMSG_MBOX_FETCH_BODYSTRUCTURE "
- "(header)");
- break;
- }
- fetch_part_recv(s, &imsg,
- "IMSG_MBOX_FETCH_BODYSTRUCTURE",
- bshdr.found, bshdr.bslen,
- &s->pending_bodystructure_buf,
- &s->pending_bodystructure_len,
- &s->pending_bodystructure_found);
+ case IMSG_MBOX_FETCH_BODYSTRUCTURE:
+ if (recv_header(&imsg, &u.bodystructure,
+ sizeof(u.bodystructure),
+ "IMSG_MBOX_FETCH_BODYSTRUCTURE"))
+ fetch_part_recv(s, &imsg,
+ "IMSG_MBOX_FETCH_BODYSTRUCTURE",
+ u.bodystructure.found,
+ u.bodystructure.bslen,
+ &s->pending_bodystructure_buf,
+ &s->pending_bodystructure_len,
+ &s->pending_bodystructure_found);
break;
- }
- case IMSG_MBOX_FETCH_META: {
- struct imsg_mbox_fetch_meta meta;
-
- if (imsg_get_data(&imsg, &meta, sizeof(meta)) == -1) {
- log_warnx("bad IMSG_MBOX_FETCH_META");
+ case IMSG_MBOX_FETCH_META:
+ if (!recv_fixed(&imsg, &u.meta, sizeof(u.meta),
+ "IMSG_MBOX_FETCH_META"))
break;
- }
- /* imsg_get_data() does not NUL-terminate */
- meta.flags[sizeof(meta.flags) - 1] = '\0';
- /* SELECTING buffers, for RFC 7162 SS3.2.6 order */
+ u.meta.flags[sizeof(u.meta.flags) - 1] = '\0';
+ /* SELECTING buffers: RFC 7162 section 3.2.6 order */
if (s->state == SESSION_SELECTING)
- session_handle_select_fetch(s, &meta);
+ session_handle_select_fetch(s, &u.meta);
else if (s->state == SESSION_STORING)
- session_send_store_fetch_response(s, &meta);
+ session_send_store_fetch_response(s, &u.meta);
else
- session_send_fetch_response(s, &meta);
+ session_send_fetch_response(s, &u.meta);
break;
- }
- case IMSG_MBOX_EXPUNGED: {
- struct imsg_mbox_expunged exp;
-
- if (imsg_get_data(&imsg, &exp, sizeof(exp)) == -1) {
- log_warnx("bad IMSG_MBOX_EXPUNGED");
+ case IMSG_MBOX_EXPUNGED:
+ if (!recv_fixed(&imsg, &u.expunged, sizeof(u.expunged),
+ "IMSG_MBOX_EXPUNGED"))
break;
- }
- /* buffered until after COPYUID (RFC 9051 SS6.4.8) */
- if (s->state == SESSION_COPYING) {
- if (s->move_expunged_n ==
- s->move_expunged_cap) {
- uint32_t newcap =
- s->move_expunged_cap ?
- s->move_expunged_cap * 2 : 16;
- struct imsg_mbox_expunged *enew =
- reallocarray(s->move_expunged,
- newcap, sizeof(*enew));
-
- if (enew == NULL) {
- log_warn("session %u: "
- "realloc move_expunged",
- s->id);
- break;
- }
- s->move_expunged = enew;
- s->move_expunged_cap = newcap;
- }
- s->move_expunged[s->move_expunged_n++] = exp;
- } else
- session_send_expunge_response(s, &exp);
+ if (s->state == SESSION_COPYING)
+ session_hold_expunged(s, &u.expunged);
+ else
+ session_send_expunge_response(s, &u.expunged);
break;
- }
- case IMSG_MBOX_COPY_MAPPING: {
- struct imsg_mbox_copy_mapping m;
-
- if (imsg_get_data(&imsg, &m, sizeof(m)) == -1) {
- log_warnx("bad IMSG_MBOX_COPY_MAPPING");
- break;
- }
- session_handle_mbox_copy_mapping(s, &m);
+ case IMSG_MBOX_COPY_MAPPING:
+ if (recv_fixed(&imsg, &u.mapping, sizeof(u.mapping),
+ "IMSG_MBOX_COPY_MAPPING"))
+ session_handle_mbox_copy_mapping(s, &u.mapping);
break;
- }
- case IMSG_MBOX_SEARCH_MATCH: {
- struct imsg_mbox_search_match m;
-
- if (imsg_get_data(&imsg, &m, sizeof(m)) == -1) {
- log_warnx("bad IMSG_MBOX_SEARCH_MATCH");
- break;
- }
- session_handle_mbox_search_match(s, &m);
+ case IMSG_MBOX_SEARCH_MATCH:
+ if (recv_fixed(&imsg, &u.match, sizeof(u.match),
+ "IMSG_MBOX_SEARCH_MATCH"))
+ session_handle_mbox_search_match(s, &u.match);
break;
- }
- case IMSG_MBOX_SELECT_VANISHED: {
- struct imsg_mbox_select_vanished v;
-
- if (imsg_get_data(&imsg, &v, sizeof(v)) == -1) {
- log_warnx("bad IMSG_MBOX_SELECT_VANISHED");
+ case IMSG_MBOX_SELECT_VANISHED:
+ if (!recv_fixed(&imsg, &u.vanished, sizeof(u.vanished),
+ "IMSG_MBOX_SELECT_VANISHED"))
break;
- }
if (s->state == SESSION_SELECTING)
- session_handle_select_vanished(s, &v);
+ session_handle_select_vanished(s, &u.vanished);
else
- session_handle_fetch_vanished(s, &v);
+ session_handle_fetch_vanished(s, &u.vanished);
break;
- }
- case IMSG_MBOX_STORE_MODIFIED: {
- struct imsg_mbox_store_modified m;
-
- if (imsg_get_data(&imsg, &m, sizeof(m)) == -1) {
- log_warnx("bad IMSG_MBOX_STORE_MODIFIED");
- break;
- }
- session_handle_store_modified(s, &m);
+ case IMSG_MBOX_STORE_MODIFIED:
+ if (recv_fixed(&imsg, &u.modified, sizeof(u.modified),
+ "IMSG_MBOX_STORE_MODIFIED"))
+ session_handle_store_modified(s, &u.modified);
break;
- }
- case IMSG_MBOX_VIEW_EXPUNGE: {
- struct imsg_mbox_expunged item;
-
- if (imsg_get_data(&imsg, &item, sizeof(item)) == -1) {
- log_warnx("bad IMSG_MBOX_VIEW_EXPUNGE");
- break;
- }
- session_send_expunge_response(s, &item);
+ case IMSG_MBOX_VIEW_EXPUNGE:
+ if (recv_fixed(&imsg, &u.expunged, sizeof(u.expunged),
+ "IMSG_MBOX_VIEW_EXPUNGE"))
+ session_send_expunge_response(s, &u.expunged);
break;
- }
- case IMSG_MBOX_VIEW_FETCH: {
- struct imsg_mbox_fetch_meta meta;
-
- if (imsg_get_data(&imsg, &meta, sizeof(meta)) == -1) {
- log_warnx("bad IMSG_MBOX_VIEW_FETCH");
+ case IMSG_MBOX_VIEW_FETCH:
+ if (!recv_fixed(&imsg, &u.meta, sizeof(u.meta),
+ "IMSG_MBOX_VIEW_FETCH"))
break;
- }
- /* same risk as IMSG_MBOX_FETCH_META's flags above */
- meta.flags[sizeof(meta.flags) - 1] = '\0';
- session_handle_view_fetch(s, &meta);
+ u.meta.flags[sizeof(u.meta.flags) - 1] = '\0';
+ session_handle_view_fetch(s, &u.meta);
break;
- }
- case IMSG_MBOX_VIEW_EXISTS: {
- struct imsg_mbox_view_exists ve;
- char buf[32];
-
- if (imsg_get_data(&imsg, &ve, sizeof(ve)) == -1) {
- log_warnx("bad IMSG_MBOX_VIEW_EXISTS");
+ case IMSG_MBOX_VIEW_EXISTS:
+ if (!recv_fixed(&imsg, &u.exists, sizeof(u.exists),
+ "IMSG_MBOX_VIEW_EXISTS"))
break;
- }
- snprintf(buf, sizeof(buf), "%u EXISTS", ve.exists);
+ snprintf(buf, sizeof(buf), "%u EXISTS",
+ u.exists.exists);
session_untagged(s, buf);
break;
- }
- case IMSG_MBOX_IDLE_REFRESHED: {
- struct imsg_mbox_idle_refreshed res;
-
- if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) {
- log_warnx("bad IMSG_MBOX_IDLE_REFRESHED");
- break;
- }
- session_handle_idle_refreshed(s, &res);
+ case IMSG_MBOX_IDLE_REFRESHED:
+ if (recv_fixed(&imsg, &u.refreshed, sizeof(u.refreshed),
+ "IMSG_MBOX_IDLE_REFRESHED"))
+ session_handle_idle_refreshed(s, &u.refreshed);
break;
- }
- case IMSG_MBOX_RESULT: {
- struct imsg_mbox_result res;
-
- if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) {
- log_warnx("bad IMSG_MBOX_RESULT");
- break;
- }
- session_handle_mbox_result(s, &res);
+ case IMSG_MBOX_RESULT:
+ if (recv_fixed(&imsg, &u.result, sizeof(u.result),
+ "IMSG_MBOX_RESULT"))
+ session_handle_mbox_result(s, &u.result);
break;
- }
- case IMSG_MBOX_LIST_ITEM: {
- struct imsg_mbox_list_item item;
-
- if (imsg_get_data(&imsg, &item, sizeof(item)) == -1) {
- log_warnx("bad IMSG_MBOX_LIST_ITEM");
+ case IMSG_MBOX_LIST_ITEM:
+ if (!recv_fixed(&imsg, &u.item, sizeof(u.item),
+ "IMSG_MBOX_LIST_ITEM"))
break;
- }
- /* not NUL-terminated by imsg_get_data() */
- item.mailbox[sizeof(item.mailbox) - 1] = '\0';
- session_handle_mbox_list_item(s, &item);
+ u.item.mailbox[sizeof(u.item.mailbox) - 1] = '\0';
+ session_handle_mbox_list_item(s, &u.item);
break;
- }
- case IMSG_MBOX_APPENDED: {
- struct imsg_mbox_appended res;
-
- if (imsg_get_data(&imsg, &res, sizeof(res)) == -1) {
- log_warnx("bad IMSG_MBOX_APPENDED");
- break;
- }
- session_handle_mbox_appended(s, &res);
+ case IMSG_MBOX_APPENDED:
+ if (recv_fixed(&imsg, &u.appended, sizeof(u.appended),
+ "IMSG_MBOX_APPENDED"))
+ session_handle_mbox_appended(s, &u.appended);
break;
- }
default:
log_debug("session %u: store channel: unhandled %d",
s->id, imsg_get_type(&imsg));
imsg_free(&imsg);
}
imsgev_rearm_read(s->store_iev);
- (void)fd;
if (!session_dequeue_next(s))
return; /* s was torn down by a queued LOGOUT, do not touch */
}
-/* RFC 9051 SS6.3.2; for QRESYNC: VANISHED, FETCH, tagged OK */
+/* RFC 9051 section 6.3.2; for QRESYNC: VANISHED, FETCH, tagged OK */
void
session_handle_mbox_selected(struct session *s,
const struct imsg_mbox_selected *res)
{
- char buf[128];
+ char buf[128];
+ char qname[MBOX_QUOTED_MAX];
+ char listbuf[MBOX_QUOTED_MAX + 64];
+ uint32_t i;
+ int qlen;
if (res->error != MBOX_OP_OK || s->qresync_alloc_failed) {
/* RFC 5530 NONEXISTENT only when there is no such mailbox */
session_untagged(s, buf);
}
- /* RFC 9051 SS6.3.3: EXAMINE gets no PERMANENTFLAGS */
+ /* RFC 9051 section 6.3.3: EXAMINE gets no PERMANENTFLAGS */
session_untagged(s,
"FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)");
if (s->mbox_readonly)
"OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen "
"\\Draft \\*)] System flags and keywords allowed");
- {
- char qname[MBOX_QUOTED_MAX];
- char listbuf[MBOX_QUOTED_MAX + 64];
- int qlen;
+ if (quote_mailbox(qname, sizeof(qname), s->selected_mailbox) == -1)
+ log_warnx("session %u: SELECT LIST mailbox name truncated",
+ s->id);
+ qlen = snprintf(listbuf, sizeof(listbuf), "* LIST () \"/\" %s\r\n",
+ qname);
+ if (qlen > 0 && (size_t)qlen < sizeof(listbuf))
+ session_write(s, listbuf, (size_t)qlen);
+ else
+ log_warnx("session %u: SELECT LIST response did not fit",
+ s->id);
- if (quote_mailbox(qname, sizeof(qname),
- s->selected_mailbox) == -1)
- log_warnx("session %u: SELECT LIST mailbox name "
- "truncated", s->id);
- qlen = snprintf(listbuf, sizeof(listbuf),
- "* LIST () \"/\" %s\r\n", qname);
- if (qlen > 0 && (size_t)qlen < sizeof(listbuf))
- session_write(s, listbuf, (size_t)qlen);
- else
- log_warnx("session %u: SELECT LIST response did not "
- "fit", s->id);
- }
-
if (s->vanished_nranges > 0) {
char vbuf[8192];
char full[8192 + 32];
s->vanished_nranges = 0;
s->vanished_cap = 0;
- {
- uint32_t i;
-
- for (i = 0; i < s->qresync_nfetches; i++)
- session_send_qresync_fetch_response(s,
- &s->qresync_fetches[i]);
- }
+ for (i = 0; i < s->qresync_nfetches; i++)
+ session_send_qresync_fetch_response(s, &s->qresync_fetches[i]);
free(s->qresync_fetches);
s->qresync_fetches = NULL;
s->qresync_nfetches = 0;
"[READ-WRITE] SELECT completed");
}
-/* RFC 9051 SS6.3.11; attributes in a fixed order */
+/* RFC 9051 section 6.3.11; attributes in a fixed order */
void
session_handle_mbox_status_result(struct session *s,
const struct imsg_mbox_status_result *res)
{
char qname[MBOX_QUOTED_MAX];
- /* RFC 9051 SS4.3-escaped name plus attributes */
+ /* RFC 9051 section 4.3-escaped name plus attributes */
char buf[MBOX_QUOTED_MAX + 384];
size_t len;
int n, first = 1;
if (s->status_attrs & STATUS_ATT_MESSAGES)
STATUS_APPEND("MESSAGES %u", res->messages);
if (s->status_attrs & STATUS_ATT_RECENT)
- /* RFC 9051 SS2.3.2 dropped \Recent; always 0 */
+ /* RFC 9051 section 2.3.2 dropped \Recent; always 0 */
STATUS_APPEND("RECENT %u", 0U);
if (s->status_attrs & STATUS_ATT_UIDNEXT)
STATUS_APPEND("UIDNEXT %u", res->uidnext);
case MBOX_OP_OK:
break;
case MBOX_OP_ERR_NO_SUCH_MAILBOX:
- /* RFC 5530 SS3 NONEXISTENT */
+ /* RFC 5530 section 3 NONEXISTENT */
session_reply(s, s->pending_tag, "NO",
"[NONEXISTENT] no such mailbox");
return;
case MBOX_OP_ERR_ALREADY_EXISTS:
- /* RFC 5530 SS3 ALREADYEXISTS */
+ /* RFC 5530 section 3 ALREADYEXISTS */
session_reply(s, s->pending_tag, "NO",
"[ALREADYEXISTS] mailbox already exists");
return;
if (s->state == SESSION_SELECTED &&
strcmp(s->selected_mailbox, s->mbox_op_name) == 0) {
if (strcmp(cmdname, "RENAME") == 0) {
- if (strlcpy(s->selected_mailbox, s->rename_newname,
- sizeof(s->selected_mailbox)) >=
- sizeof(s->selected_mailbox))
- log_warnx("session %u: selected_mailbox "
- "truncated after RENAME, can't happen "
- "(both same size)", s->id);
+ strlcpy(s->selected_mailbox, s->rename_newname,
+ sizeof(s->selected_mailbox));
} else if (strcmp(cmdname, "DELETE") == 0) {
/* the mailbox is gone: deselect, as CLOSE does */
s->state = SESSION_AUTHENTICATED;
if (!list_pattern_match(s->list_pattern, item->mailbox, 0))
return;
- /* RFC 9051 SS7.3.1 attributes */
+ /* RFC 9051 section 7.3.1 attributes */
if (!s->list_subscribed_only)
attrs = "";
else if (s->list_is_lsub)
if (s->copy_n == s->copy_cap) {
uint32_t newcap = s->copy_cap ? s->copy_cap * 2 : 16;
- uint32_t *newsrc = reallocarray(s->copy_src_uids, newcap,
- sizeof(uint32_t));
+ uint32_t *newsrc;
uint32_t *newdest;
+ newsrc = reallocarray(s->copy_src_uids, newcap,
+ sizeof(uint32_t));
if (newsrc == NULL) {
log_warn("session %u: realloc COPY src array", s->id);
s->copy_alloc_failed = 1;
if (s->vanished_nranges == s->vanished_cap) {
uint32_t newcap = s->vanished_cap ?
s->vanished_cap * 2 : 16;
- struct vanished_range *n = reallocarray(s->vanished_ranges,
- newcap, sizeof(*n));
+ struct vanished_range *n;
+ n = reallocarray(s->vanished_ranges, newcap, sizeof(*n));
if (n == NULL) {
log_warn("session %u: realloc VANISHED range array",
s->id);
uint32_t newcap =
s->qresync_fetches_cap ?
s->qresync_fetches_cap * 2 : 16;
- struct imsg_mbox_fetch_meta *n = reallocarray(
- s->qresync_fetches, newcap, sizeof(*n));
+ struct imsg_mbox_fetch_meta *n;
+ n = reallocarray(s->qresync_fetches, newcap, sizeof(*n));
if (n == NULL) {
log_warn("session %u: realloc QRESYNC fetch array",
s->id);
s->qresync_fetches[s->qresync_nfetches++] = *m;
}
-/* RFC 9051 SS6.3.13: an unsolicited FETCH carries UID */
+/* RFC 9051 section 6.3.13: an unsolicited FETCH carries UID */
void
session_handle_view_fetch(struct session *s,
const struct imsg_mbox_fetch_meta *meta)
{
struct session *s = arg;
- (void)fd;
- (void)event;
-
if (!s->idling || s->state != SESSION_SELECTED) {
- log_debug("session %u: idle poll fired while not idling "
- "(idling=%d state=%d), ignored", s->id, s->idling,
- (int)s->state);
+ log_debug("session %u: idle poll while not idling (idling=%d "
+ "state=%d)", s->id, s->idling, (int)s->state);
return;
}
tv.tv_sec = (time_t)listener_idle_poll_secs;
tv.tv_usec = 0;
if (evtimer_add(&s->idle_ev, &tv) == -1)
- log_warnx("session %u: evtimer_add (idle poll); this IDLE "
- "will not be woken until the client sends DONE", s->id);
+ log_warnx("session %u: evtimer_add (idle poll)", s->id);
}
void
evtimer_del(&s->idle_ev);
}
-/* RFC 7162 SS3.2.5.1: always UID, FLAGS and MODSEQ */
+/* RFC 7162 section 3.2.5.1: always UID, FLAGS and MODSEQ */
void
session_send_qresync_fetch_response(struct session *s,
const struct imsg_mbox_fetch_meta *meta)
session_untagged(s, buf);
}
-/* RFC 7162 SS3.1.3 MODIFIED, worst case per entry */
+/* RFC 7162 section 3.1.3 MODIFIED, worst case per entry */
#define MODIFIED_PER_ENTRY 11
#define MODIFIED_WRAPPER_MAX 160
void
session_handle_mbox_result(struct session *s, struct imsg_mbox_result *res)
{
+ const char *cmdname;
+ char reply[96];
int was_close = 0, was_storing = 0, was_expunging = 0;
- const char *cmdname;
if (s->state == SESSION_SEARCHING) {
session_finish_search(s, res);
return;
}
- /* RFC 9051 SS6.4.9: "UID <CMD>" */
+ /* RFC 9051 section 6.4.9: "UID <CMD>" */
if (s->state == SESSION_STORING) {
cmdname = s->cmd_by_uid ? "UID STORE" : "STORE";
was_storing = 1;
s->id, cmdname, res->error == MBOX_OP_OK ? "OK" : "ERROR",
res->count);
- /* RFC 9051 SS6.4.1: only a successful CLOSE deselects */
+ /* RFC 9051 section 6.4.1: only a successful CLOSE deselects */
s->state = (was_close && res->error == MBOX_OP_OK) ?
SESSION_AUTHENTICATED : SESSION_SELECTED;
if (res->error != MBOX_OP_OK) {
- char text[64];
-
free(s->store_modified);
s->store_modified = NULL;
s->store_modified_n = 0;
IMAP_BUSY_TEXT);
return;
}
- snprintf(text, sizeof(text), "%s%s failed",
+ snprintf(reply, sizeof(reply), "%s%s failed",
res->error == MBOX_OP_ERR_EXPUNGEISSUED ?
"[EXPUNGEISSUED] " : res->error == MBOX_OP_ERR_LIMIT ?
"[LIMIT] " : "", cmdname);
- session_reply(s, s->pending_tag, "NO", text);
+ session_reply(s, s->pending_tag, "NO", reply);
return;
}
if (was_storing || was_expunging)
s->mbox_highestmodseq = res->highestmodseq;
- /* RFC 7162 SS3.1.3 MODIFIED */
+ /* RFC 7162 section 3.1.3 MODIFIED */
if (was_storing && (s->store_modified_n > 0 ||
s->store_modified_alloc_failed)) {
char *rbuf = NULL, *text = NULL;
free(rbuf);
free(text);
- /* RFC 7162 SS3.1.3: an incomplete MODIFIED is refused */
+ /* RFC 7162 section 3.1.3: an incomplete MODIFIED is refused */
if (incomplete) {
char fail[64];
s->store_modified_cap = 0;
s->store_modified_alloc_failed = 0;
-
- /* RFC 7162 SS3.2.7, but never on CLOSE (SS3.2.8) */
+ /* RFC 7162 section 3.2.7, but never on CLOSE (section 3.2.8) */
if (was_expunging && !was_close && s->condstore_enabled &&
res->count > 0) {
- char text[64];
-
- snprintf(text, sizeof(text), "[HIGHESTMODSEQ %llu] %s "
+ snprintf(reply, sizeof(reply), "[HIGHESTMODSEQ %llu] %s "
"completed", (unsigned long long)res->highestmodseq,
cmdname);
- session_reply(s, s->pending_tag, "OK", text);
+ session_reply(s, s->pending_tag, "OK", reply);
return;
}
- /* RFC 9051 SS6.4.5 */
+ /* RFC 9051 section 6.4.5 */
if (s->fetch_incomplete) {
- char text[96];
-
s->fetch_incomplete = 0;
- snprintf(text, sizeof(text),
+ snprintf(reply, sizeof(reply),
"%s failed, some requested items could not be returned",
cmdname);
- session_reply(s, s->pending_tag, "NO", text);
+ session_reply(s, s->pending_tag, "NO", reply);
return;
}
- {
- char text[32];
-
- snprintf(text, sizeof(text), "%s completed", cmdname);
- session_reply(s, s->pending_tag, "OK", text);
- }
+ snprintf(reply, sizeof(reply), "%s completed", cmdname);
+ session_reply(s, s->pending_tag, "OK", reply);
}
blob - 591d448412ee231a8ee8bc46eda9c51afb1b6fdf
blob + 0e316e857ef102f15f9640ef5cc55c55eefb6c21
--- src/utf8.c
+++ src/utf8.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-/* RFC 5198 SS2: UTF-8, no C1 controls, no BOM */
+/* RFC 5198 section 2: UTF-8, no C1 controls, no BOM */
#include "utf8.h"
continue;
}
- /* RFC 3629 SS4's table, one branch per lead-byte class. */
+ /* RFC 3629 section 4's table, one branch per lead byte */
if (p[0] >= 0xc2 && p[0] <= 0xdf) {
need = 1; lo = 0x80; hi = 0xbf;
} else if (p[0] == 0xe0) {
if (p[i] < 0x80 || p[i] > 0xbf)
return (0);
- /* RFC 5198 SS2 item 3: the C1 controls MUST NOT appear. */
+ /* RFC 5198 section 2 item 3: no C1 controls */
if (p[0] == 0xc2 && p[1] <= 0x9f)
return (0);
- /* RFC 5198 SS2 item 5 bans a leading BOM; refused anywhere */
+ /* RFC 5198 section 2 item 5 bans a leading BOM; none at all */
if (p[0] == 0xef && p[1] == 0xbb && p[2] == 0xbf)
return (0);