970 Commits

Author SHA1 Message Date
Mechiel Lukkien
fd5167fdb3
imapserver: enable test that checked that an expunged message can still be read in sessions when they haven't processed the deletion yet.
We've been keeping track of references before we erase the message file for a
while now.
2025-04-11 18:27:42 +02:00
Mechiel Lukkien
1a6d268e1d
imapserver: check for UIDNEXT overflow when adding a message to a mailbox
Return an error, with instructions so a user may be able to work around the
issue.
2025-04-11 18:22:29 +02:00
Mechiel Lukkien
507ca73b96
imapserver: implement UIDONLY extension, RFC 9586
Once clients enable this extension, commands can no longer refer to "message
sequence numbers" (MSNs), but can only refer to messages with UIDs. This means
both sides no longer have to carefully keep their sequence numbers in sync
(error-prone), and don't have to keep track of a mapping of sequence numbers to
UIDs (saves resources).

With UIDONLY enabled, all FETCH responses are replaced with UIDFETCH response.
2025-04-11 11:45:49 +02:00
Mechiel Lukkien
8bab38eac4
imapserver: implement NOTIFY extension from RFC 5465
NOTIFY is like IDLE, but where IDLE watches just the selected mailbox, NOTIFY
can watch all mailboxes. With NOTIFY, a client can also ask a server to
immediately return configurable fetch attributes for new messages, e.g. a
message preview, certain header fields, or simply the entire message.

Mild testing with evolution and fairemail.
2025-04-11 10:06:34 +02:00
Mechiel Lukkien
5a7d5fce98
run ineffassign (fast) before staticcheck (slow) 2025-04-07 18:40:54 +02:00
Mechiel Lukkien
902de0e1f9
queue: in log lines about delivery, we had both "attempts" starting at 0 and "attempt" starting at 1, keep only "attempts" starting at 1
from eric l, thanks!
2025-04-07 13:35:42 +02:00
Mechiel Lukkien
39c21f80cd
imapserver: return proper response for FETCH of "BODY[1.MIME]" where 1 is a message
MIME returns the part headers. If 1 is a message, i.e. a message/rfc822 or
message/global, for example when top-level is a multipart/mixed, we were
returning the MIME headers from the message, not from the part.

We also shouldn't be returning a MIME-Version header or the separating newline
for MIME. Those are for MIME headers of a message, but the "MIME" fetch body
part is always about the part.

Found after looking into FETCH BODY handling for issue #327.
2025-04-07 12:15:13 +02:00
Mechiel Lukkien
462568d878
webmail: for "cid"/content-id's used in html, look for them in all other parts, not just when there is a multipart/related in the message
The gmail apps generate messages consisting of multipart/mixed, with text/html
referring to a sibling image/jpeg. We weren't resolving that cid before.

Related to issue #327.
2025-04-07 11:10:14 +02:00
Mechiel Lukkien
2defbce0bc
imapserver: return all the extensible fields for bodystructure, notably for content-disposition
The gmail iOS/Android app were showing mime image parts as (garbled) text
instead of rendering them as image. By returning all the optional fields in the
bodystructure fetch attribute, the gmail app renders the image as expected by
the user. So we now add all fields. We didn't before, because we weren't
keeping track of Content-MD5, Content-Language and Content-Location header
fields, since they aren't that useful.

Messages in mailboxes have to be reparsed:
	./mox reparse

Without reparsing, imap responses will claim the extra fields
(content-disposition) are absent for existing messages, instead of not claiming
anything at all, which is what we did before.

Accounts and all/some mailboxes can get their "uid validity" bumped ("./mox
bumpuidvalidity $account [$mailbox]"), which should trigger clients to load all
messages from scratch, but gmail doesn't appear to notice, so it would be
better to remove & add the account in gmail.

For issue #327, also relevant to issue #217.
2025-04-05 15:46:17 +02:00
Mechiel Lukkien
69d2699961
write base64 message parts with 76 data bytes on a line instead of 78
As required by RFC 2045 (MIME). The 78 byte lines work in practice, except that
SpamAssassin has rules that give messages with 78-byte lines spam points.

Mentioned by kjetilho on irc.
2025-04-03 10:22:15 +02:00
Mechiel Lukkien
00c8db98e6
start more function names/calls with x when they handle errors through panics
mostly the imapserver and smtpserver connection write and read methods.
2025-04-02 13:59:46 +02:00
Mechiel Lukkien
deb57462a4
update list of sponsors, add logo's and link to the nlnet projects 2025-04-02 11:24:59 +02:00
Mechiel Lukkien
479bf29124
imapserver: implement the MULTISEARCH extension, with its ESEARCH command 2025-03-31 18:34:23 +02:00
Mechiel Lukkien
5dcf674761
webmail: reconnect automatically in more cases
Before, we would only reconnect the SSE connection when the previous one lasted
10 minutes.  For some reason, firefox disconnects SSE connections when there is
any network change. Running the docker integration tests changes the network a
few time in quick succession, prevent further automatic reconnects.

This changes the "stop reconnection automatically" period from 10 minutes to 5
seconds.
2025-03-30 14:54:29 +02:00
Mechiel Lukkien
aba0061073
small tweak to docs and website, mentioning EIA in the context of internalized email 2025-03-30 11:03:06 +02:00
Mechiel Lukkien
cc5e3165ea
imapserver: implement "inprogress" response code (RFC 9585) for keepalive during long search
For long searches in big mailboxes, without any matches, we would previously
keep working and not say anything. Clients could interpret this silence as a
broken connection at some point. We now send a "we're still searching" untagged
OK responses with code INPROGRESS every 10 seconds while we're still searching,
to prevent the client from closing the connection. We also send how many
messages we've processed, and usually also how many we need to process in grand
total. Clients can use this to show a progress bar.
2025-03-30 10:43:02 +02:00
Mechiel Lukkien
3e128d744e
for the web interfaces, ensure the effective configured http paths end in a slash to prevent 404's and/or errors accessing the web interfaces
The default paths for the web interfaces, such as /admin/, /account/, /webmail/
and /webapi/ end with a slash. They should end with a slash because we use the
path when restricting cookies to just that web interface. You could configure
paths not ending with a slash, but due to using http.StripPrefix, and our
handler, some of those requests may not work properly.

We now warn if configured paths don't end with a trailing slash when parsing
the config file. We normally error out when such things happen, but users
probably have paths without trailing slashes configured, and we don't want to
break them on a future upgrade. We now use an effective path that includes the
trailing slash.

We would always redirect requests to the configured paths but without trailing
slash to the path with trailing slash, and that stays.

For issue #325 by odama626.
2025-03-29 22:00:55 +01:00
Mechiel Lukkien
3a3a11560e
web interfaces: don't include version number in html, only return it after authentication
second round for issue #322
2025-03-29 20:46:53 +01:00
Mechiel Lukkien
eeeabdc6de
fix build with previous commit that didn't sync frontend
not at my sharpest...
2025-03-29 20:16:05 +01:00
Mechiel Lukkien
3ac38aacca
imapserver: fix storing previews when requested over imap and they are missing from the database
found while testing.
2025-03-29 20:13:10 +01:00
Mechiel Lukkien
6ab31c15b7
imapserver: actually announce PREVIEW extension 2025-03-29 18:28:33 +01:00
Mechiel Lukkien
a5d74eb718
webmail: add buttons to download a message as eml, and export 1 or more messages as mbox/maildir in zip/tgz/tar, like for entire mailboxes
Download as eml is useful with firefox, because opening the raw message in a
new tab, and then downloading it, causes firefox to request the url without
cookies, causing it to save a "403 - forbidden" response.

Exporting a selection is useful during all kinds of testing. Makes it easy to
an entire thread, or just some messages.

The export popover now has buttons for each combination of mbox/maildir vs
zip/tgz/tar. Before you may have had to select the email format and archive
format first, followed by a click. Now it's just a click.
2025-03-29 18:10:23 +01:00
Mechiel Lukkien
d6e55b5f36
don't use strings.Lines, it's only available in go1.24 and we support go1.23 too 2025-03-28 18:20:18 +01:00
Mechiel Lukkien
68729fa5a3
in smtp banner and imap ID command response when unauthenticated, don't send the mox version number
Attackers scanning the internet can use it to easily create a database of
hosts, software and versions. Let's not make it too easy to find old versions
that may be vulnerable to potential bugs found in the future. We could try
hiding the name "mox" as well, but the banner will still be identifyable, so
there isn't much point, and the public knowing approximately which software is
running can be useful for debugging.

The ID command in IMAP is used by clients to announce their software and
version. We only respond with our version when the user is authenticated.

There are still ways to discover the version number. But they don't involve
standard banner scanning, so someone would have to specifically target mox. We
could tighten that in the future.

For issue #322, based on email. Thanks everyone for discussing.
2025-03-28 17:50:40 +01:00
Mechiel Lukkien
789e4875ca
update to latest bstore 2025-03-28 17:39:20 +01:00
Mechiel Lukkien
6bf80d91bc
sync frontend api doc/client
Forgot to build after change just before commit...
2025-03-28 17:39:20 +01:00
Mechiel Lukkien
aa631c604c
imapserver: implement PREVIEW extension (RFC 8970), and store previews in message database
We were already generating previews of plain text parts for the webmail
interface, but we didn't store them, so were generating the previews each time
messages were listed.

Now we store previews in the database for faster handling. And we also generate
previews for html parts if needed. We use the first part that has textual
content.

For IMAP, the previews can be requested by an IMAP client. When we get the
"LAZY" variant, which doesn't require us to generate a preview, we generate it
anyway, because it should be fast enough. So don't make clients first ask for
"PREVIEW (LAZY)" and then again a request for "PREVIEW".

We now also generate a preview when a message is added to the account. Except
for imports. It would slow us down, the previews aren't urgent, and they will
be generated on-demand at first-request.
2025-03-28 17:10:17 +01:00
Mechiel Lukkien
8b418a9ca2
update golang.org/x dependencies 2025-03-28 17:01:12 +01:00
Mechiel Lukkien
027e5754a0
update to go1.23 and replace golang.org/x/exp/maps with stdlib maps 2025-03-28 17:01:06 +01:00
Mechiel Lukkien
7a87522be0
rename variables, struct fields and functions to include an "x" when they can panic for handling errors
and document the convention in develop.txt.
spurred by running errcheck again (it has been a while). it still has too many
false to enable by default.
2025-03-24 16:12:22 +01:00
Mechiel Lukkien
a2c79e25c1
check and log errors more often in deferred cleanup calls, and log remote-induced errors at lower priority
We normally check errors for all operations. But for some cleanup calls, eg
"defer file.Close()", we didn't. Now we also check and log most of those.
Partially because those errors can point to some mishandling or unexpected code
paths (eg file unexpected already closed). And in part to make it easier to use
"errcheck" to find the real missing error checks, there is too much noise now.

The log.Check function can now be used unconditionally for checking and logging
about errors. It adjusts the log level if the error is caused by a network
connection being closed, or a context is canceled or its deadline reached, or a
socket deadline is reached.
2025-03-24 14:06:05 +01:00
Mechiel Lukkien
15a8ce8c0b
fix warnings by ineffassign, with a one actual issue
In store/search.go, we would make a copy of a byte array, but then still use
the original instead of the copy. Could result in search operations not finding
messages that do have the content, but under very unlikely conditions only.

We'll keep running ineffassign with "make check", useful enough.
2025-03-24 10:25:33 +01:00
Mechiel Lukkien
04b1f030b7
update to latest bstore, which now properly handles modifications during Query.ForEach 2025-03-24 10:02:50 +01:00
Mechiel Lukkien
88ec5c6fbe
add rfc 4155 about mbox files, and cross-reference in the import/export code for mbox files 2025-03-23 13:59:09 +01:00
Mechiel Lukkien
a68a9d8a48
check whether mailboxes have message/etc counts through an "upgrade" boolean flag
Instead of using the per-mailbox flag, and going through all mailboxes when
opening an account.
2025-03-23 12:52:59 +01:00
Mechiel Lukkien
b37faa06bd
After queueing a message in the web api's, prevent context cancelation from completing message changes
Adding to the queue is done in a transaction, the queue db file is mox-global.
Appending the message to the Sent folder, removing it from Drafts, marking the
original message as answered/forwarded, is done in a separate database
transaction that gets the ctx passed in. If the ctx was canceled in between,
the queueing was finished, but the rest wasn't completed.

Reported by mteege, thanks!
2025-03-23 11:07:39 +01:00
Mechiel Lukkien
b0e4dcdb61
sync to latest autocert 2025-03-21 21:47:59 +01:00
Mechiel Lukkien
773d8cc959
update to latest github.com/mjl-/adns, synced to go1.24.1 2025-03-21 18:42:02 +01:00
Mechiel Lukkien
70aedddc90
webmail: when composing, no longer remove the last remaining To address with the ctrl+backspace shortcut
On reply, with too many Cc/Bcc, I usually hit ctrl+backspace a few time. I just
want to clear the addresses, but I practically always still want a To address.
2025-03-21 13:51:53 +01:00
Mechiel Lukkien
297e83188c
Check for queued messages when removing an address, and more completely cleanup accounts when removing.
When removing an address, we want to make sure any queued messages for the
account still still have their address associated with the account. E.g.
through a catchall address.

Before removing an account, we fail deliveries still in the queue for the
account. We remove any addresses on the suppression list (which are stored in
the queue database, not the account database file that is removed completely).
We also clear all sessions for the webmail/webaccount interfaces. For the
webmail, further operations will fail, and the reconnection attempt will cause
the login popup with a message about an unknown session token.
2025-03-21 13:36:10 +01:00
Mechiel Lukkien
75036c3a71
Before moving message files in imapserver and webmail API, ensure the message destination directory for the newly assigned IDs exist.
Example symptom, when deleting a message in the webmail (which moves to Trash):

        l=error m="duplicating message in old mailbox for current sessions" err="link data/accounts/mjl/msg/I/368638 data/accounts/mjl/msg/J/368640: no such file or directory" pkg=webmail

Problem introduced a few weeks ago, where moving messages starting duplicating
the message first, and the copy is erased once all references (in IMAP
sessions) to the old mailbox have been removed.
2025-03-21 10:18:39 +01:00
Mechiel Lukkien
99f9eb438f
Minor cleanup: use the ModSeq from the Mailbox in a ChangeMailboxAdd, no need to add the ModSeq again 2025-03-20 00:10:47 +01:00
Mechiel Lukkien
9ca50ab207
imapserver: When trying to replace a message in a non-existent mailbox, do still consume the message if it is a non-synchronized literal
Not likely to happen in the wild.
2025-03-19 22:00:34 +01:00
Mechiel Lukkien
5294a63c26
When logging structs, do log fields of type time.Time (timestamps)
The simplistic logging approach we've followed so far is to not log struct
fields that are themselves structs, which time.Time is. So we skipped, but do
log it now.
2025-03-19 21:52:31 +01:00
Mechiel Lukkien
719dc2bee1
webmail: Don't abort SSE connection when a metadata/annotation change is made (broadcasted)
Missing case...
2025-03-16 14:02:45 +01:00
Mechiel Lukkien
26793e407a
imapserver: Fix broadcasting change when modifying metadata key
We were not broadcasting the correct change, at least the modseq was missing in
case of an update.
2025-03-16 13:57:44 +01:00
Mechiel Lukkien
ac4b006ecd
When removing an account, wait for the last account reference has gone away before removing the files.
The intent to remove the account is stored in the database. At startup, if
there are any such referenes, they are applied by removing the account
directories and the entry in the database. This ensures the account directory
is properly removed, even on incomplete shutdown.

Don't add an account when its directory already exits.
2025-03-15 14:20:35 +01:00
Mechiel Lukkien
c4255a96f8
In tests, make initializing store/, its switchboard and an account more consistent.
Initialize store and switchboard first, then open account, and close in reverse
order.

Replace all "CheckClosed" calls with "WaitClosed", future changings will keep
an account reference open for a bit after the last regular close, so we can't
know that an account should be closed during tests.

Remove one parameter from the (still too long) "start test server" function in
imapserver testing code.
2025-03-15 11:15:23 +01:00
Mechiel Lukkien
eadbda027c
Fix bug gathering "changes" to broadcast during a mailbox rename in certain situations
We weren't appending the individual changes to the slice, but the entire slice.
Since "Change" is an "any", this isn't a type error. So make a Change a
non-empty interface (I had seen an issue like this coming, should have made it
an interface then, at least now we have a reasonable method, to get the modseq
of a change).

Found while working on an imap webpush prototype.
2025-03-15 10:45:35 +01:00
Mechiel Lukkien
0cf0bfb8a6
We won't be implementing IMAP UNAUTHENTICATE.
Doesn't seem like it's a common thing to do. And it's just a bit risky, it's
too easy to forget to clear some part of the authentication state on a
connection (especially future changes that forget to update clear a new field
during unauthenticate). If a strong use case ever pops up, we can reconsider.

Also update the roadmap a bit.
2025-03-12 10:01:00 +01:00