2023-01-30 16:27:06 +03:00
// Package smtpserver implements an SMTP server for submission and incoming delivery of mail messages.
package smtpserver
import (
"bufio"
"bytes"
"context"
2024-04-24 12:35:07 +03:00
"crypto/ed25519"
2023-02-05 18:29:03 +03:00
"crypto/md5"
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
cryptorand "crypto/rand"
2023-01-30 16:27:06 +03:00
"crypto/rsa"
2023-02-05 14:30:14 +03:00
"crypto/sha1"
"crypto/sha256"
2023-01-30 16:27:06 +03:00
"crypto/tls"
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
"crypto/x509"
2023-01-30 16:27:06 +03:00
"encoding/base64"
"errors"
"fmt"
2023-02-05 14:30:14 +03:00
"hash"
2023-01-30 16:27:06 +03:00
"io"
2024-02-08 16:49:01 +03:00
"log/slog"
2023-02-08 00:56:03 +03:00
"math"
2023-01-30 16:27:06 +03:00
"net"
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
"net/textproto"
2023-01-30 16:27:06 +03:00
"os"
"runtime/debug"
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
"slices"
2023-08-10 11:29:06 +03:00
"sort"
2023-01-30 16:27:06 +03:00
"strings"
"sync"
"time"
2024-03-31 16:23:53 +03:00
"unicode"
2023-01-30 16:27:06 +03:00
2023-08-10 11:29:06 +03:00
"golang.org/x/exp/maps"
2024-03-09 01:29:15 +03:00
"golang.org/x/text/unicode/norm"
2023-08-10 11:29:06 +03:00
2023-01-30 16:27:06 +03:00
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
2023-01-31 02:22:26 +03:00
"github.com/mjl-/bstore"
2023-01-30 16:27:06 +03:00
"github.com/mjl-/mox/config"
"github.com/mjl-/mox/dkim"
"github.com/mjl-/mox/dmarc"
"github.com/mjl-/mox/dmarcdb"
2023-11-01 19:55:40 +03:00
"github.com/mjl-/mox/dmarcrpt"
2023-01-30 16:27:06 +03:00
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/dsn"
"github.com/mjl-/mox/iprev"
"github.com/mjl-/mox/message"
"github.com/mjl-/mox/metrics"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/moxio"
"github.com/mjl-/mox/moxvar"
"github.com/mjl-/mox/publicsuffix"
"github.com/mjl-/mox/queue"
2023-02-08 00:56:03 +03:00
"github.com/mjl-/mox/ratelimit"
2023-01-31 02:22:26 +03:00
"github.com/mjl-/mox/scram"
2023-01-30 16:27:06 +03:00
"github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/spf"
"github.com/mjl-/mox/store"
2024-11-29 14:43:21 +03:00
"github.com/mjl-/mox/tlsrpt"
2023-01-30 16:27:06 +03:00
"github.com/mjl-/mox/tlsrptdb"
)
// We use panic and recover for error handling while executing commands.
// These errors signal the connection must be closed.
2023-11-02 17:56:01 +03:00
var errIO = errors . New ( "io error" )
2023-02-08 00:56:03 +03:00
2023-03-12 12:38:02 +03:00
// If set, regular delivery/submit is sidestepped, email is accepted and
// delivered to the account named mox.
var Localserve bool
2023-02-08 00:56:03 +03:00
var limiterConnectionRate , limiterConnections * ratelimit . Limiter
// For delivery rate limiting. Variable because changed during tests.
var limitIPMasked1MessagesPerMinute int = 500
var limitIPMasked1SizePerMinute int64 = 1000 * 1024 * 1024
2024-03-05 22:10:28 +03:00
// Maximum number of RCPT TO commands (i.e. recipients) for a single message
// delivery. Must be at least 100. Announced in LIMIT extension.
const rcptToLimit = 1000
2023-02-08 00:56:03 +03:00
func init ( ) {
// Also called by tests, so they don't trigger the rate limiter.
limitersInit ( )
}
func limitersInit ( ) {
mox . LimitersInit ( )
// todo future: make these configurable
limiterConnectionRate = & ratelimit . Limiter {
WindowLimits : [ ] ratelimit . WindowLimit {
{
Window : time . Minute ,
Limits : [ ... ] int64 { 300 , 900 , 2700 } ,
} ,
} ,
}
limiterConnections = & ratelimit . Limiter {
WindowLimits : [ ] ratelimit . WindowLimit {
{
Window : time . Duration ( math . MaxInt64 ) , // All of time.
Limits : [ ... ] int64 { 30 , 90 , 270 } ,
} ,
} ,
}
}
2023-01-30 16:27:06 +03:00
2023-02-08 23:45:32 +03:00
var (
2023-03-10 12:23:43 +03:00
// Delays for bad/suspicious behaviour. Zero during tests.
2023-07-01 15:24:28 +03:00
badClientDelay = time . Second // Before reads and after 1-byte writes for probably spammers.
authFailDelay = time . Second // Response to authentication failure.
unknownRecipientsDelay = 5 * time . Second // Response when all recipients are unknown.
firstTimeSenderDelayDefault = 15 * time . Second // Before accepting message from first-time sender.
2023-02-08 23:45:32 +03:00
)
2023-01-30 16:27:06 +03:00
type codes struct {
code int
secode string // Enhanced code, but without the leading major int from code.
}
var (
metricConnection = promauto . NewCounterVec (
prometheus . CounterOpts {
Name : "mox_smtpserver_connection_total" ,
Help : "Incoming SMTP connections." ,
} ,
[ ] string {
"kind" , // "deliver" or "submit"
} ,
)
metricCommands = promauto . NewHistogramVec (
prometheus . HistogramOpts {
Name : "mox_smtpserver_command_duration_seconds" ,
Help : "SMTP server command duration and result codes in seconds." ,
Buckets : [ ] float64 { 0.001 , 0.005 , 0.01 , 0.05 , 0.100 , 0.5 , 1 , 5 , 10 , 20 , 30 , 60 , 120 } ,
} ,
[ ] string {
"kind" , // "deliver" or "submit"
"cmd" ,
"code" ,
"ecode" ,
} ,
)
metricDelivery = promauto . NewCounterVec (
prometheus . CounterOpts {
Name : "mox_smtpserver_delivery_total" ,
Help : "SMTP incoming message delivery from external source, not submission. Result values: delivered, reject, unknownuser, accounterror, delivererror. Reason indicates why a message was rejected/accepted." ,
} ,
[ ] string {
"result" ,
"reason" ,
} ,
)
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
// Similar between ../webmail/webmail.go:/metricSubmission and ../smtpserver/server.go:/metricSubmission and ../webapisrv/server.go:/metricSubmission
2023-01-30 16:27:06 +03:00
metricSubmission = promauto . NewCounterVec (
prometheus . CounterOpts {
Name : "mox_smtpserver_submission_total" ,
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
Help : "SMTP server incoming submission results, known values (those ending with error are server errors): ok, badmessage, badfrom, badheader, messagelimiterror, recipientlimiterror, localserveerror, queueerror." ,
2023-01-30 16:27:06 +03:00
} ,
[ ] string {
"result" ,
} ,
)
metricServerErrors = promauto . NewCounterVec (
prometheus . CounterOpts {
Name : "mox_smtpserver_errors_total" ,
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
Help : "SMTP server errors, known values: dkimsign, queuedsn." ,
2023-01-30 16:27:06 +03:00
} ,
[ ] string {
"error" ,
} ,
)
2024-11-29 14:43:21 +03:00
metricDeliveryStarttls = promauto . NewCounter (
prometheus . CounterOpts {
Name : "mox_smtpserver_delivery_starttls_total" ,
Help : "Total number of STARTTLS handshakes for incoming deliveries." ,
} ,
)
metricDeliveryStarttlsErrors = promauto . NewCounterVec (
prometheus . CounterOpts {
Name : "mox_smtpserver_delivery_starttls_errors_total" ,
Help : "Errors with TLS handshake during STARTTLS for incoming deliveries." ,
} ,
[ ] string {
"reason" , // "eof", "sslv2", "unsupportedversions", "nottls", "alert-<num>-<msg>", "other"
} ,
)
2023-01-30 16:27:06 +03:00
)
2023-11-09 19:15:46 +03:00
var jitterRand = mox . NewPseudoRand ( )
2023-01-30 16:27:06 +03:00
2023-07-01 15:24:28 +03:00
func durationDefault ( delay * time . Duration , def time . Duration ) time . Duration {
if delay == nil {
return def
}
return * delay
}
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
// Listen initializes network listeners for incoming SMTP connection.
// The listeners are stored for a later call to Serve.
func Listen ( ) {
2023-08-10 11:29:06 +03:00
names := maps . Keys ( mox . Conf . Static . Listeners )
sort . Strings ( names )
for _ , name := range names {
listener := mox . Conf . Static . Listeners [ name ]
2024-08-23 12:04:21 +03:00
var tlsConfig , tlsConfigDelivery * tls . Config
2023-01-30 16:27:06 +03:00
if listener . TLS != nil {
tlsConfig = listener . TLS . Config
2024-08-23 12:04:21 +03:00
// For SMTP delivery, if we get a TLS handshake for an SNI hostname that we don't
// allow, we'll fallback to a certificate for the listener hostname instead of
// causing the connection to fail. May improve interoperability.
tlsConfigDelivery = listener . TLS . ConfigFallback
2023-01-30 16:27:06 +03:00
}
maxMsgSize := listener . SMTPMaxMessageSize
if maxMsgSize == 0 {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
maxMsgSize = config . DefaultMaxMsgSize
2023-01-30 16:27:06 +03:00
}
if listener . SMTP . Enabled {
hostname := mox . Conf . Static . HostnameDomain
if listener . Hostname != "" {
hostname = listener . HostnameDomain
}
port := config . Port ( listener . SMTP . Port , 25 )
for _ , ip := range listener . IPs {
2023-07-01 15:24:28 +03:00
firstTimeSenderDelay := durationDefault ( listener . SMTP . FirstTimeSenderDelay , firstTimeSenderDelayDefault )
2024-12-06 16:50:02 +03:00
if tlsConfigDelivery != nil {
tlsConfigDelivery = tlsConfigDelivery . Clone ( )
// Default setting is currently to have session tickets disabled, to work around
// TLS interoperability issues with incoming deliveries from Microsoft. See
// https://github.com/golang/go/issues/70232.
tlsConfigDelivery . SessionTicketsDisabled = listener . SMTP . TLSSessionTicketsDisabled == nil || * listener . SMTP . TLSSessionTicketsDisabled
}
2024-08-23 12:04:21 +03:00
listen1 ( "smtp" , name , ip , port , hostname , tlsConfigDelivery , false , false , maxMsgSize , false , listener . SMTP . RequireSTARTTLS , ! listener . SMTP . NoRequireTLS , listener . SMTP . DNSBLZones , firstTimeSenderDelay )
2023-01-30 16:27:06 +03:00
}
}
if listener . Submission . Enabled {
hostname := mox . Conf . Static . HostnameDomain
if listener . Hostname != "" {
hostname = listener . HostnameDomain
}
port := config . Port ( listener . Submission . Port , 587 )
for _ , ip := range listener . IPs {
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
listen1 ( "submission" , name , ip , port , hostname , tlsConfig , true , false , maxMsgSize , ! listener . Submission . NoRequireSTARTTLS , ! listener . Submission . NoRequireSTARTTLS , true , nil , 0 )
2023-01-30 16:27:06 +03:00
}
}
if listener . Submissions . Enabled {
hostname := mox . Conf . Static . HostnameDomain
if listener . Hostname != "" {
hostname = listener . HostnameDomain
}
port := config . Port ( listener . Submissions . Port , 465 )
for _ , ip := range listener . IPs {
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
listen1 ( "submissions" , name , ip , port , hostname , tlsConfig , true , true , maxMsgSize , true , true , true , nil , 0 )
2023-01-30 16:27:06 +03:00
}
}
}
}
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
var servers [ ] func ( )
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
func listen1 ( protocol , name , ip string , port int , hostname dns . Domain , tlsConfig * tls . Config , submission , xtls bool , maxMessageSize int64 , requireTLSForAuth , requireTLSForDelivery , requireTLS bool , dnsBLs [ ] dns . Domain , firstTimeSenderDelay time . Duration ) {
2023-12-05 15:35:58 +03:00
log := mlog . New ( "smtpserver" , nil )
2023-01-30 16:27:06 +03:00
addr := net . JoinHostPort ( ip , fmt . Sprintf ( "%d" , port ) )
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
if os . Getuid ( ) == 0 {
2023-12-05 18:06:50 +03:00
log . Print ( "listening for smtp" ,
slog . String ( "listener" , name ) ,
slog . String ( "address" , addr ) ,
slog . String ( "protocol" , protocol ) )
2023-01-30 16:27:06 +03:00
}
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
network := mox . Network ( ip )
ln , err := mox . Listen ( network , addr )
2023-01-30 16:27:06 +03:00
if err != nil {
2023-12-05 15:35:58 +03:00
log . Fatalx ( "smtp: listen for smtp" , err , slog . String ( "protocol" , protocol ) , slog . String ( "listener" , name ) )
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
// Each listener gets its own copy of the config, so session keys between different
// ports on same listener aren't shared. We rotate session keys explicitly in this
// base TLS config because each connection clones the TLS config before using. The
// base TLS config would never get automatically managed/rotated session keys.
if tlsConfig != nil {
tlsConfig = tlsConfig . Clone ( )
mox . StartTLSSessionTicketKeyRefresher ( mox . Shutdown , log , tlsConfig )
2023-01-30 16:27:06 +03:00
}
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
serve := func ( ) {
for {
conn , err := ln . Accept ( )
if err != nil {
2023-12-05 15:35:58 +03:00
log . Infox ( "smtp: accept" , err , slog . String ( "protocol" , protocol ) , slog . String ( "listener" , name ) )
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
continue
}
2023-12-05 15:35:58 +03:00
// Package is set on the resolver by the dkim/spf/dmarc/etc packages.
resolver := dns . StrictResolver { Log : log . Logger }
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
go serve ( name , mox . Cid ( ) , hostname , tlsConfig , conn , resolver , submission , xtls , maxMessageSize , requireTLSForAuth , requireTLSForDelivery , requireTLS , dnsBLs , firstTimeSenderDelay )
2023-01-30 16:27:06 +03:00
}
change mox to start as root, bind to network sockets, then drop to regular unprivileged mox user
makes it easier to run on bsd's, where you cannot (easily?) let non-root users
bind to ports <1024. starting as root also paves the way for future improvements
with privilege separation.
unfortunately, this requires changes to how you start mox. though mox will help
by automatically fix up dir/file permissions/ownership.
if you start mox from the systemd unit file, you should update it so it starts
as root and adds a few additional capabilities:
# first update the mox binary, then, as root:
./mox config printservice >mox.service
systemctl daemon-reload
systemctl restart mox
journalctl -f -u mox &
# you should see mox start up, with messages about fixing permissions on dirs/files.
if you used the recommended config/ and data/ directory, in a directory just for
mox, and with the mox user called "mox", this should be enough.
if you don't want mox to modify dir/file permissions, set "NoFixPermissions:
true" in mox.conf.
if you named the mox user something else than mox, e.g. "_mox", add "User: _mox"
to mox.conf.
if you created a shared service user as originally suggested, you may want to
get rid of that as it is no longer useful and may get in the way. e.g. if you
had /home/service/mox with a "service" user, that service user can no longer
access any files: only mox and root can.
this also adds scripts for building mox docker images for alpine-supported
platforms.
the "restart" subcommand has been removed. it wasn't all that useful and got in
the way.
and another change: when adding a domain while mtasts isn't enabled, don't add
the per-domain mtasts config, as it would cause failure to add the domain.
based on report from setting up mox on openbsd from mteege.
and based on issue #3. thanks for the feedback!
2023-02-27 14:19:55 +03:00
}
servers = append ( servers , serve )
}
// Serve starts serving on all listeners, launching a goroutine per listener.
func Serve ( ) {
for _ , serve := range servers {
go serve ( )
2023-01-30 16:27:06 +03:00
}
}
type conn struct {
cid int64
// OrigConn is the original (TCP) connection. We'll read from/write to conn, which
// can be wrapped in a tls.Server. We close origConn instead of conn because
// closing the TLS connection would send a TLS close notification, which may block
// for 5s if the server isn't reading it (because it is also sending it).
origConn net . Conn
conn net . Conn
tls bool
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
extRequireTLS bool // Whether to announce and allow the REQUIRETLS extension.
2023-01-30 16:27:06 +03:00
resolver dns . Resolver
r * bufio . Reader
w * bufio . Writer
2023-02-03 22:33:19 +03:00
tr * moxio . TraceReader // Kept for changing trace level during cmd/auth/data.
tw * moxio . TraceWriter
2023-02-08 23:45:32 +03:00
slow bool // If set, reads are done with a 1 second sleep, and writes are done 1 byte at a time, to keep spammers busy.
2023-01-30 16:27:06 +03:00
lastlog time . Time // Used for printing the delta time since the previous logging for this connection.
submission bool // ../rfc/6409:19 applies
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
baseTLSConfig * tls . Config
2023-01-30 16:27:06 +03:00
localIP net . IP
remoteIP net . IP
hostname dns . Domain
2023-12-05 15:35:58 +03:00
log mlog . Log
2023-01-30 16:27:06 +03:00
maxMessageSize int64
requireTLSForAuth bool
implement outgoing tls reports
we were already accepting, processing and displaying incoming tls reports. now
we start tracking TLS connection and security-policy-related errors for
outgoing message deliveries as well. we send reports once a day, to the
reporting addresses specified in TLSRPT records (rua) of a policy domain. these
reports are about MTA-STS policies and/or DANE policies, and about
STARTTLS-related failures.
sending reports is enabled by default, but can be disabled through setting
NoOutgoingTLSReports in mox.conf.
only at the end of the implementation process came the realization that the
TLSRPT policy domain for DANE (MX) hosts are separate from the TLSRPT policy
for the recipient domain, and that MTA-STS and DANE TLS/policy results are
typically delivered in separate reports. so MX hosts need their own TLSRPT
policies.
config for the per-host TLSRPT policy should be added to mox.conf for existing
installs, in field HostTLSRPT. it is automatically configured by quickstart for
new installs. with a HostTLSRPT config, the "dns records" and "dns check" admin
pages now suggest the per-host TLSRPT record. by creating that record, you're
requesting TLS reports about your MX host.
gathering all the TLS/policy results is somewhat tricky. the tentacles go
throughout the code. the positive result is that the TLS/policy-related code
had to be cleaned up a bit. for example, the smtpclient TLS modes now reflect
reality better, with independent settings about whether PKIX and/or DANE
verification has to be done, and/or whether verification errors have to be
ignored (e.g. for tls-required: no header). also, cached mtasts policies of
mode "none" are now cleaned up once the MTA-STS DNS record goes away.
2023-11-09 19:40:46 +03:00
requireTLSForDelivery bool // If set, delivery is only allowed with TLS (STARTTLS), except if delivery is to a TLS reporting address.
2023-01-30 16:27:06 +03:00
cmd string // Current command.
cmdStart time . Time // Start of current command.
2023-03-10 12:23:43 +03:00
ncmds int // Number of commands processed. Used to abort connection when first incoming command is unknown/invalid.
2023-01-30 16:27:06 +03:00
dnsBLs [ ] dns . Domain
2023-07-01 15:24:28 +03:00
firstTimeSenderDelay time . Duration
2023-01-30 16:27:06 +03:00
// If non-zero, taken into account during Read and Write. Set while processing DATA
// command, we don't want the entire delivery to take too long.
deadline time . Time
hello dns . IPDomain // Claimed remote name. Can be ip address for ehlo.
ehlo bool // If set, we had EHLO instead of HELO.
authFailed int // Number of failed auth attempts. For slowing down remote with many failures.
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
authSASL bool // Whether SASL authentication was done.
authTLS bool // Whether we did TLS client cert authentication.
2023-01-30 16:27:06 +03:00
username string // Only when authenticated.
account * store . Account // Only when authenticated.
// We track good/bad message transactions to disconnect spammers trying to guess addresses.
transactionGood int
transactionBad int
// Message transaction.
2024-02-10 19:55:56 +03:00
mailFrom * smtp . Path
requireTLS * bool // MAIL FROM with REQUIRETLS set.
futureRelease time . Time // MAIL FROM with HOLDFOR or HOLDUNTIL.
futureReleaseRequest string // For use in DSNs, either "for;" or "until;" plus original value. ../rfc/4865:305
has8bitmime bool // If MAIL FROM parameter BODY=8BITMIME was sent. Required for SMTPUTF8.
2024-03-31 16:23:53 +03:00
smtputf8 bool // todo future: we should keep track of this per recipient. perhaps only a specific recipient requires smtputf8, e.g. due to a utf8 localpart.
msgsmtputf8 bool // Is SMTPUTF8 required for the received message. Default to the same value as `smtputf8`, but is re-evaluated after the whole message (envelope and data) is received.
2024-04-24 20:15:30 +03:00
recipients [ ] recipient
2023-01-30 16:27:06 +03:00
}
type rcptAccount struct {
2024-11-01 12:38:31 +03:00
AccountName string
Destination config . Destination
CanonicalAddress string // Optional catchall part stripped and/or lowercased.
2023-01-30 16:27:06 +03:00
}
2024-04-24 20:15:30 +03:00
type rcptAlias struct {
2024-11-01 12:38:31 +03:00
Alias config . Alias
CanonicalAddress string // Optional catchall part stripped and/or lowercased.
2024-04-24 20:15:30 +03:00
}
type recipient struct {
2024-11-01 12:38:31 +03:00
Addr smtp . Path
2024-04-24 20:15:30 +03:00
// If account and alias are both not set, this is not for a local address. This is
// normal for submission, where messages are added to the queue. For incoming
// deliveries, this will result in an error.
2024-11-01 12:38:31 +03:00
Account * rcptAccount // If set, recipient address is for this local account.
Alias * rcptAlias // If set, for a local alias.
2024-04-24 20:15:30 +03:00
}
2023-01-30 16:27:06 +03:00
func isClosed ( err error ) bool {
return errors . Is ( err , errIO ) || moxio . IsClosed ( err )
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
// makeTLSConfig makes a new tls config that is bound to the connection for
// possible client certificate authentication in case of submission.
func ( c * conn ) makeTLSConfig ( ) * tls . Config {
if ! c . submission {
return c . baseTLSConfig
}
// We clone the config so we can set VerifyPeerCertificate below to a method bound
// to this connection. Earlier, we set session keys explicitly on the base TLS
// config, so they can be used for this connection too.
tlsConf := c . baseTLSConfig . Clone ( )
// Allow client certificate authentication, for use with the sasl "external"
// authentication mechanism.
tlsConf . ClientAuth = tls . RequestClientCert
// We verify the client certificate during the handshake. The TLS handshake is
// initiated explicitly for incoming connections and during starttls, so we can
// immediately extract the account name and address used for authentication.
tlsConf . VerifyPeerCertificate = c . tlsClientAuthVerifyPeerCert
return tlsConf
}
// tlsClientAuthVerifyPeerCert can be used as tls.Config.VerifyPeerCertificate, and
// sets authentication-related fields on conn. This is not called on resumed TLS
// connections.
func ( c * conn ) tlsClientAuthVerifyPeerCert ( rawCerts [ ] [ ] byte , verifiedChains [ ] [ ] * x509 . Certificate ) error {
if len ( rawCerts ) == 0 {
return nil
}
// If we had too many authentication failures from this IP, don't attempt
// authentication. If this is a new incoming connetion, it is closed after the TLS
// handshake.
if ! mox . LimiterFailedAuth . CanAdd ( c . remoteIP , time . Now ( ) , 1 ) {
return nil
}
cert , err := x509 . ParseCertificate ( rawCerts [ 0 ] )
if err != nil {
c . log . Debugx ( "parsing tls client certificate" , err )
return err
}
if err := c . tlsClientAuthVerifyPeerCertParsed ( cert ) ; err != nil {
c . log . Debugx ( "verifying tls client certificate" , err )
return fmt . Errorf ( "verifying client certificate: %w" , err )
}
return nil
}
// tlsClientAuthVerifyPeerCertParsed verifies a client certificate. Called both for
// fresh and resumed TLS connections.
func ( c * conn ) tlsClientAuthVerifyPeerCertParsed ( cert * x509 . Certificate ) error {
if c . account != nil {
return fmt . Errorf ( "cannot authenticate with tls client certificate after previous authentication" )
}
authResult := "error"
defer func ( ) {
metrics . AuthenticationInc ( "submission" , "tlsclientauth" , authResult )
if authResult == "ok" {
mox . LimiterFailedAuth . Reset ( c . remoteIP , time . Now ( ) )
} else {
mox . LimiterFailedAuth . Add ( c . remoteIP , time . Now ( ) , 1 )
}
} ( )
// For many failed auth attempts, slow down verification attempts.
if c . authFailed > 3 && authFailDelay > 0 {
mox . Sleep ( mox . Context , time . Duration ( c . authFailed - 3 ) * authFailDelay )
}
c . authFailed ++ // Compensated on success.
defer func ( ) {
// On the 3rd failed authentication, start responding slowly. Successful auth will
// cause fast responses again.
if c . authFailed >= 3 {
c . setSlow ( true )
}
} ( )
shabuf := sha256 . Sum256 ( cert . RawSubjectPublicKeyInfo )
fp := base64 . RawURLEncoding . EncodeToString ( shabuf [ : ] )
pubKey , err := store . TLSPublicKeyGet ( context . TODO ( ) , fp )
if err != nil {
if err == bstore . ErrAbsent {
authResult = "badcreds"
}
return fmt . Errorf ( "looking up tls public key with fingerprint %s: %v" , fp , err )
}
// Verify account exists and still matches address.
acc , _ , err := store . OpenEmail ( c . log , pubKey . LoginAddress )
if err != nil {
return fmt . Errorf ( "opening account for address %s for public key %s: %w" , pubKey . LoginAddress , fp , err )
}
defer func ( ) {
if acc != nil {
err := acc . Close ( )
c . log . Check ( err , "close account" )
}
} ( )
if acc . Name != pubKey . Account {
return fmt . Errorf ( "tls client public key %s is for account %s, but email address %s is for account %s" , fp , pubKey . Account , pubKey . LoginAddress , acc . Name )
}
authResult = "ok"
c . authFailed = 0
c . account = acc
acc = nil // Prevent cleanup by defer.
c . username = pubKey . LoginAddress
c . authTLS = true
c . log . Debug ( "tls client authenticated with client certificate" ,
slog . String ( "fingerprint" , fp ) ,
slog . String ( "username" , c . username ) ,
slog . String ( "account" , c . account . Name ) ,
slog . Any ( "remote" , c . remoteIP ) )
return nil
}
// xtlsHandshakeAndAuthenticate performs the TLS handshake, and verifies a client
// certificate if present.
func ( c * conn ) xtlsHandshakeAndAuthenticate ( conn net . Conn ) {
tlsConn := tls . Server ( conn , c . makeTLSConfig ( ) )
c . conn = tlsConn
cidctx := context . WithValue ( mox . Context , mlog . CidKey , c . cid )
ctx , cancel := context . WithTimeout ( cidctx , time . Minute )
defer cancel ( )
c . log . Debug ( "starting tls server handshake" )
if ! c . submission {
metricDeliveryStarttls . Inc ( )
}
if err := tlsConn . HandshakeContext ( ctx ) ; err != nil {
if ! c . submission {
// Errors from crypto/tls mostly aren't typed. We'll have to look for strings...
reason := "other"
if errors . Is ( err , io . EOF ) {
reason = "eof"
} else if alert , ok := mox . AsTLSAlert ( err ) ; ok {
reason = tlsrpt . FormatAlert ( alert )
} else {
s := err . Error ( )
if strings . Contains ( s , "tls: client offered only unsupported versions" ) {
reason = "unsupportedversions"
} else if strings . Contains ( s , "tls: first record does not look like a TLS handshake" ) {
reason = "nottls"
} else if strings . Contains ( s , "tls: unsupported SSLv2 handshake received" ) {
reason = "sslv2"
}
}
metricDeliveryStarttlsErrors . WithLabelValues ( reason ) . Inc ( )
}
panic ( fmt . Errorf ( "tls handshake: %s (%w)" , err , errIO ) )
}
cancel ( )
cs := tlsConn . ConnectionState ( )
if cs . DidResume && len ( cs . PeerCertificates ) > 0 {
// Verify client after session resumption.
err := c . tlsClientAuthVerifyPeerCertParsed ( cs . PeerCertificates [ 0 ] )
if err != nil {
panic ( fmt . Errorf ( "tls verify client certificate after resumption: %s (%w)" , err , errIO ) )
}
}
attrs := [ ] slog . Attr {
slog . Any ( "version" , tlsVersion ( cs . Version ) ) ,
slog . String ( "ciphersuite" , tls . CipherSuiteName ( cs . CipherSuite ) ) ,
slog . String ( "sni" , cs . ServerName ) ,
slog . Bool ( "resumed" , cs . DidResume ) ,
slog . Int ( "clientcerts" , len ( cs . PeerCertificates ) ) ,
}
if c . account != nil {
attrs = append ( attrs ,
slog . String ( "account" , c . account . Name ) ,
slog . String ( "username" , c . username ) ,
)
}
c . log . Debug ( "tls handshake completed" , attrs ... )
}
type tlsVersion uint16
func ( v tlsVersion ) String ( ) string {
return strings . ReplaceAll ( strings . ToLower ( tls . VersionName ( uint16 ( v ) ) ) , " " , "-" )
}
2023-01-30 16:27:06 +03:00
// completely reset connection state as if greeting has just been sent.
// ../rfc/3207:210
func ( c * conn ) reset ( ) {
c . ehlo = false
c . hello = dns . IPDomain { }
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
if ! c . authTLS {
c . username = ""
if c . account != nil {
err := c . account . Close ( )
c . log . Check ( err , "closing account" )
}
c . account = nil
2023-01-30 16:27:06 +03:00
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . authSASL = false
2023-01-30 16:27:06 +03:00
c . rset ( )
}
// for rset command, and a few more cases that reset the mail transaction state.
// ../rfc/5321:2502
func ( c * conn ) rset ( ) {
c . mailFrom = nil
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
c . requireTLS = nil
2024-02-10 19:55:56 +03:00
c . futureRelease = time . Time { }
c . futureReleaseRequest = ""
2023-01-30 16:27:06 +03:00
c . has8bitmime = false
c . smtputf8 = false
2024-03-31 16:23:53 +03:00
c . msgsmtputf8 = false
2023-01-30 16:27:06 +03:00
c . recipients = nil
}
func ( c * conn ) earliestDeadline ( d time . Duration ) time . Time {
e := time . Now ( ) . Add ( d )
if ! c . deadline . IsZero ( ) && c . deadline . Before ( e ) {
return c . deadline
}
return e
}
func ( c * conn ) xcheckAuth ( ) {
if c . submission && c . account == nil {
// ../rfc/4954:623
xsmtpUserErrorf ( smtp . C530SecurityRequired , smtp . SePol7Other0 , "authentication required" )
}
}
2023-12-05 15:35:58 +03:00
func ( c * conn ) xtrace ( level slog . Level ) func ( ) {
2023-02-03 22:33:19 +03:00
c . xflush ( )
c . tr . SetTrace ( level )
c . tw . SetTrace ( level )
return func ( ) {
c . xflush ( )
c . tr . SetTrace ( mlog . LevelTrace )
c . tw . SetTrace ( mlog . LevelTrace )
}
}
2023-02-08 23:45:32 +03:00
// setSlow marks the connection slow (or now), so reads are done with 3 second
// delay for each read, and writes are done at 1 byte per second, to try to slow
// down spammers.
func ( c * conn ) setSlow ( on bool ) {
if on && ! c . slow {
c . log . Debug ( "connection changed to slow" )
} else if ! on && c . slow {
c . log . Debug ( "connection restored to regular pace" )
}
c . slow = on
}
2023-01-30 16:27:06 +03:00
// Write writes to the connection. It panics on i/o errors, which is handled by the
// connection command loop.
func ( c * conn ) Write ( buf [ ] byte ) ( int , error ) {
2023-02-08 23:45:32 +03:00
chunk := len ( buf )
if c . slow {
chunk = 1
2023-01-30 16:27:06 +03:00
}
2023-12-14 19:59:22 +03:00
// We set a single deadline for Write and Read. This may be a TLS connection.
// SetDeadline works on the underlying connection. If we wouldn't touch the read
// deadline, and only set the write deadline and do a bunch of writes, the TLS
// library would still have to do reads on the underlying connection, and may reach
// a read deadline that was set for some earlier read.
// We have one deadline for the whole write. In case of slow writing, we'll write
// the last chunk in one go, so remote smtp clients don't abort the connection for
// being slow.
deadline := c . earliestDeadline ( 30 * time . Second )
if err := c . conn . SetDeadline ( deadline ) ; err != nil {
c . log . Errorx ( "setting deadline for write" , err )
}
2023-02-08 23:45:32 +03:00
var n int
for len ( buf ) > 0 {
nn , err := c . conn . Write ( buf [ : chunk ] )
if err != nil {
panic ( fmt . Errorf ( "write: %s (%w)" , err , errIO ) )
}
n += nn
buf = buf [ chunk : ]
if len ( buf ) > 0 && badClientDelay > 0 {
mox . Sleep ( mox . Context , badClientDelay )
2023-12-14 19:59:22 +03:00
// Make sure we don't take too long, otherwise the remote SMTP client may close the
// connection.
if time . Until ( deadline ) < 2 * badClientDelay {
chunk = len ( buf )
}
2023-02-08 23:45:32 +03:00
}
2023-01-30 16:27:06 +03:00
}
2023-02-08 23:45:32 +03:00
return n , nil
2023-01-30 16:27:06 +03:00
}
// Read reads from the connection. It panics on i/o errors, which is handled by the
// connection command loop.
func ( c * conn ) Read ( buf [ ] byte ) ( int , error ) {
2023-02-08 23:45:32 +03:00
if c . slow && badClientDelay > 0 {
mox . Sleep ( mox . Context , badClientDelay )
}
2023-01-30 16:27:06 +03:00
// todo future: make deadline configurable for callers, and through config file? ../rfc/5321:3610 ../rfc/6409:492
// See comment about Deadline instead of individual read/write deadlines at Write.
if err := c . conn . SetDeadline ( c . earliestDeadline ( 30 * time . Second ) ) ; err != nil {
c . log . Errorx ( "setting deadline for read" , err )
}
n , err := c . conn . Read ( buf )
if err != nil {
panic ( fmt . Errorf ( "read: %s (%w)" , err , errIO ) )
}
return n , err
}
// Cache of line buffers for reading commands.
// Filled on demand.
var bufpool = moxio . NewBufpool ( 8 , 2 * 1024 )
func ( c * conn ) readline ( ) string {
2023-12-05 15:35:58 +03:00
line , err := bufpool . Readline ( c . log , c . r )
2023-01-30 16:27:06 +03:00
if err != nil && errors . Is ( err , moxio . ErrLineTooLong ) {
2023-02-25 15:07:32 +03:00
c . writecodeline ( smtp . C500BadSyntax , smtp . SeProto5Other0 , "line too long, smtp max is 512, we reached 2048" , nil )
2023-01-30 16:27:06 +03:00
panic ( fmt . Errorf ( "%s (%w)" , err , errIO ) )
} else if err != nil {
panic ( fmt . Errorf ( "%s (%w)" , err , errIO ) )
}
return line
}
// Buffered-write command response line to connection with codes and msg.
// Err is not sent to remote but is used for logging and can be empty.
func ( c * conn ) bwritecodeline ( code int , secode string , msg string , err error ) {
var ecode string
if secode != "" {
ecode = fmt . Sprintf ( "%d.%s" , code / 100 , secode )
}
metricCommands . WithLabelValues ( c . kind ( ) , c . cmd , fmt . Sprintf ( "%d" , code ) , ecode ) . Observe ( float64 ( time . Since ( c . cmdStart ) ) / float64 ( time . Second ) )
2023-12-05 18:06:50 +03:00
c . log . Debugx ( "smtp command result" , err ,
slog . String ( "kind" , c . kind ( ) ) ,
slog . String ( "cmd" , c . cmd ) ,
slog . Int ( "code" , code ) ,
slog . String ( "ecode" , ecode ) ,
slog . Duration ( "duration" , time . Since ( c . cmdStart ) ) )
2023-01-30 16:27:06 +03:00
var sep string
if ecode != "" {
sep = " "
}
// Separate by newline and wrap long lines.
lines := strings . Split ( msg , "\n" )
for i , line := range lines {
// ../rfc/5321:3506 ../rfc/5321:2583 ../rfc/5321:2756
var prelen = 3 + 1 + len ( ecode ) + len ( sep )
for prelen + len ( line ) > 510 {
e := 510 - prelen
for ; e > 400 && line [ e ] != ' ' ; e -- {
}
// todo future: understand if ecode should be on each line. won't hurt. at least as long as we don't do expn or vrfy.
c . bwritelinef ( "%d-%s%s%s" , code , ecode , sep , line [ : e ] )
line = line [ e : ]
}
spdash := " "
if i < len ( lines ) - 1 {
spdash = "-"
}
c . bwritelinef ( "%d%s%s%s%s" , code , spdash , ecode , sep , line )
}
}
// Buffered-write a formatted response line to connection.
func ( c * conn ) bwritelinef ( format string , args ... any ) {
msg := fmt . Sprintf ( format , args ... )
fmt . Fprint ( c . w , msg + "\r\n" )
}
// Flush pending buffered writes to connection.
func ( c * conn ) xflush ( ) {
c . w . Flush ( ) // Errors will have caused a panic in Write.
}
// Write (with flush) a response line with codes and message. err is not written, used for logging and can be nil.
func ( c * conn ) writecodeline ( code int , secode string , msg string , err error ) {
c . bwritecodeline ( code , secode , msg , err )
c . xflush ( )
}
// Write (with flush) a formatted response line to connection.
func ( c * conn ) writelinef ( format string , args ... any ) {
c . bwritelinef ( format , args ... )
c . xflush ( )
}
var cleanClose struct { } // Sentinel value for panic/recover indicating clean close of connection.
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
func serve ( listenerName string , cid int64 , hostname dns . Domain , tlsConfig * tls . Config , nc net . Conn , resolver dns . Resolver , submission , xtls bool , maxMessageSize int64 , requireTLSForAuth , requireTLSForDelivery , requireTLS bool , dnsBLs [ ] dns . Domain , firstTimeSenderDelay time . Duration ) {
2023-01-30 16:27:06 +03:00
var localIP , remoteIP net . IP
if a , ok := nc . LocalAddr ( ) . ( * net . TCPAddr ) ; ok {
localIP = a . IP
} else {
// For net.Pipe, during tests.
localIP = net . ParseIP ( "127.0.0.10" )
}
if a , ok := nc . RemoteAddr ( ) . ( * net . TCPAddr ) ; ok {
remoteIP = a . IP
} else {
// For net.Pipe, during tests.
remoteIP = net . ParseIP ( "127.0.0.10" )
}
c := & conn {
cid : cid ,
origConn : nc ,
conn : nc ,
submission : submission ,
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
tls : xtls ,
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
extRequireTLS : requireTLS ,
2023-01-30 16:27:06 +03:00
resolver : resolver ,
lastlog : time . Now ( ) ,
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
baseTLSConfig : tlsConfig ,
2023-01-30 16:27:06 +03:00
localIP : localIP ,
remoteIP : remoteIP ,
hostname : hostname ,
maxMessageSize : maxMessageSize ,
requireTLSForAuth : requireTLSForAuth ,
requireTLSForDelivery : requireTLSForDelivery ,
dnsBLs : dnsBLs ,
2023-07-01 15:24:28 +03:00
firstTimeSenderDelay : firstTimeSenderDelay ,
2023-01-30 16:27:06 +03:00
}
2023-12-05 15:35:58 +03:00
var logmutex sync . Mutex
c . log = mlog . New ( "smtpserver" , nil ) . WithFunc ( func ( ) [ ] slog . Attr {
logmutex . Lock ( )
defer logmutex . Unlock ( )
2023-01-30 16:27:06 +03:00
now := time . Now ( )
2023-12-05 15:35:58 +03:00
l := [ ] slog . Attr {
slog . Int64 ( "cid" , c . cid ) ,
slog . Duration ( "delta" , now . Sub ( c . lastlog ) ) ,
2023-01-30 16:27:06 +03:00
}
c . lastlog = now
if c . username != "" {
2023-12-05 15:35:58 +03:00
l = append ( l , slog . String ( "username" , c . username ) )
2023-01-30 16:27:06 +03:00
}
return l
} )
2023-02-03 22:33:19 +03:00
c . tr = moxio . NewTraceReader ( c . log , "RC: " , c )
c . r = bufio . NewReader ( c . tr )
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . tw = moxio . NewTraceWriter ( c . log , "LS: " , c )
2023-02-03 22:33:19 +03:00
c . w = bufio . NewWriter ( c . tw )
2023-01-30 16:27:06 +03:00
metricConnection . WithLabelValues ( c . kind ( ) ) . Inc ( )
2023-12-05 18:06:50 +03:00
c . log . Info ( "new connection" ,
slog . Any ( "remote" , c . conn . RemoteAddr ( ) ) ,
slog . Any ( "local" , c . conn . LocalAddr ( ) ) ,
slog . Bool ( "submission" , submission ) ,
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
slog . Bool ( "tls" , xtls ) ,
2023-12-05 18:06:50 +03:00
slog . String ( "listener" , listenerName ) )
2023-01-30 16:27:06 +03:00
defer func ( ) {
c . origConn . Close ( ) // Close actual TCP socket, regardless of TLS on top.
2023-02-16 15:22:00 +03:00
c . conn . Close ( ) // If TLS, will try to write alert notification to already closed socket, returning error quickly.
2023-01-30 16:27:06 +03:00
if c . account != nil {
2023-02-16 15:22:00 +03:00
err := c . account . Close ( )
c . log . Check ( err , "closing account" )
2023-01-30 16:27:06 +03:00
c . account = nil
}
x := recover ( )
if x == nil || x == cleanClose {
c . log . Info ( "connection closed" )
} else if err , ok := x . ( error ) ; ok && isClosed ( err ) {
c . log . Infox ( "connection closed" , err )
} else {
2023-12-05 15:35:58 +03:00
c . log . Error ( "unhandled panic" , slog . Any ( "err" , x ) )
2023-01-30 16:27:06 +03:00
debug . PrintStack ( )
2023-09-15 17:47:17 +03:00
metrics . PanicInc ( metrics . Smtpserver )
2023-01-30 16:27:06 +03:00
}
} ( )
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
if xtls {
// Start TLS on connection. We perform the handshake explicitly, so we can set a
// timeout, do client certificate authentication, log TLS details afterwards.
c . xtlsHandshakeAndAuthenticate ( c . conn )
}
2023-01-30 16:27:06 +03:00
select {
2023-02-16 11:57:27 +03:00
case <- mox . Shutdown . Done ( ) :
2023-01-30 16:27:06 +03:00
// ../rfc/5321:2811 ../rfc/5321:1666 ../rfc/3463:420
c . writecodeline ( smtp . C421ServiceUnavail , smtp . SeSys3NotAccepting2 , "shutting down" , nil )
return
default :
}
2023-02-08 00:56:03 +03:00
if ! limiterConnectionRate . Add ( c . remoteIP , time . Now ( ) , 1 ) {
c . writecodeline ( smtp . C421ServiceUnavail , smtp . SePol7Other0 , "connection rate from your ip or network too high, slow down please" , nil )
return
}
// If remote IP/network resulted in too many authentication failures, refuse to serve.
if submission && ! mox . LimiterFailedAuth . CanAdd ( c . remoteIP , time . Now ( ) , 1 ) {
2023-02-13 15:53:47 +03:00
metrics . AuthenticationRatelimitedInc ( "submission" )
2023-12-05 15:35:58 +03:00
c . log . Debug ( "refusing connection due to many auth failures" , slog . Any ( "remoteip" , c . remoteIP ) )
2023-02-08 00:56:03 +03:00
c . writecodeline ( smtp . C421ServiceUnavail , smtp . SePol7Other0 , "too many auth failures" , nil )
return
}
if ! limiterConnections . Add ( c . remoteIP , time . Now ( ) , 1 ) {
2023-12-05 15:35:58 +03:00
c . log . Debug ( "refusing connection due to many open connections" , slog . Any ( "remoteip" , c . remoteIP ) )
2023-02-08 00:56:03 +03:00
c . writecodeline ( smtp . C421ServiceUnavail , smtp . SePol7Other0 , "too many open connections from your ip or network" , nil )
return
}
defer limiterConnections . Add ( c . remoteIP , time . Now ( ) , - 1 )
2023-01-30 16:27:06 +03:00
// We register and unregister the original connection, in case c.conn is replaced
// with a TLS connection later on.
mox . Connections . Register ( nc , "smtp" , listenerName )
defer mox . Connections . Unregister ( nc )
// ../rfc/5321:964 ../rfc/5321:4294 about announcing software and version
// Syntax: ../rfc/5321:2586
// We include the string ESMTP. https://cr.yp.to/smtp/greeting.html recommends it.
// Should not be too relevant nowadays, but does not hurt and default blackbox
// exporter SMTP health check expects it.
c . writelinef ( "%d %s ESMTP mox %s" , smtp . C220ServiceReady , c . hostname . ASCII , moxvar . Version )
for {
command ( c )
// If another command is present, don't flush our buffered response yet. Holding
// off will cause us to respond with a single packet.
n := c . r . Buffered ( )
if n > 0 {
buf , err := c . r . Peek ( n )
if err == nil && bytes . IndexByte ( buf , '\n' ) >= 0 {
continue
}
}
c . xflush ( )
}
}
var commands = map [ string ] func ( c * conn , p * parser ) {
"helo" : ( * conn ) . cmdHelo ,
"ehlo" : ( * conn ) . cmdEhlo ,
"starttls" : ( * conn ) . cmdStarttls ,
"auth" : ( * conn ) . cmdAuth ,
"mail" : ( * conn ) . cmdMail ,
"rcpt" : ( * conn ) . cmdRcpt ,
"data" : ( * conn ) . cmdData ,
"rset" : ( * conn ) . cmdRset ,
"vrfy" : ( * conn ) . cmdVrfy ,
"expn" : ( * conn ) . cmdExpn ,
"help" : ( * conn ) . cmdHelp ,
"noop" : ( * conn ) . cmdNoop ,
"quit" : ( * conn ) . cmdQuit ,
}
func command ( c * conn ) {
defer func ( ) {
x := recover ( )
if x == nil {
return
}
err , ok := x . ( error )
if ! ok {
panic ( x )
}
if isClosed ( err ) {
panic ( err )
}
var serr smtpError
if errors . As ( err , & serr ) {
2023-03-10 13:32:34 +03:00
c . writecodeline ( serr . code , serr . secode , fmt . Sprintf ( "%s (%s)" , serr . errmsg , mox . ReceivedID ( c . cid ) ) , serr . err )
2023-01-30 16:27:06 +03:00
if serr . printStack {
2024-11-11 01:13:38 +03:00
c . log . Errorx ( "smtp error" , serr . err , slog . Int ( "code" , serr . code ) , slog . String ( "secode" , serr . secode ) )
2023-01-30 16:27:06 +03:00
debug . PrintStack ( )
}
} else {
// Other type of panic, we pass it on, aborting the connection.
c . log . Errorx ( "command panic" , err )
2023-04-20 15:16:56 +03:00
panic ( err )
2023-01-30 16:27:06 +03:00
}
} ( )
// todo future: we could wait for either a line or shutdown, and just close the connection on shutdown.
line := c . readline ( )
t := strings . SplitN ( line , " " , 2 )
var args string
if len ( t ) == 2 {
args = " " + t [ 1 ]
}
cmd := t [ 0 ]
cmdl := strings . ToLower ( cmd )
// todo future: should we return an error for lines that are too long? perhaps for submission or in a pedantic mode. we would have to take extensions for MAIL into account. ../rfc/5321:3500 ../rfc/5321:3552
select {
2023-02-16 11:57:27 +03:00
case <- mox . Shutdown . Done ( ) :
2023-01-30 16:27:06 +03:00
// ../rfc/5321:2811 ../rfc/5321:1666 ../rfc/3463:420
c . writecodeline ( smtp . C421ServiceUnavail , smtp . SeSys3NotAccepting2 , "shutting down" , nil )
panic ( errIO )
default :
}
c . cmd = cmdl
c . cmdStart = time . Now ( )
p := newParser ( args , c . smtputf8 , c )
fn , ok := commands [ cmdl ]
if ! ok {
2023-08-09 09:12:59 +03:00
c . cmd = "(unknown)"
2023-03-10 12:23:43 +03:00
if c . ncmds == 0 {
// Other side is likely speaking something else than SMTP, send error message and
// stop processing because there is a good chance whatever they sent has multiple
// lines.
c . writecodeline ( smtp . C500BadSyntax , smtp . SeProto5Syntax2 , "please try again speaking smtp" , nil )
panic ( errIO )
}
2023-01-30 16:27:06 +03:00
// note: not "command not implemented", see ../rfc/5321:2934 ../rfc/5321:2539
xsmtpUserErrorf ( smtp . C500BadSyntax , smtp . SeProto5BadCmdOrSeq1 , "unknown command" )
}
2023-03-10 12:23:43 +03:00
c . ncmds ++
2023-01-30 16:27:06 +03:00
fn ( c , p )
}
// For use in metric labels.
func ( c * conn ) kind ( ) string {
if c . submission {
return "submission"
}
return "smtp"
}
func ( c * conn ) xneedHello ( ) {
if c . hello . IsZero ( ) {
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "no ehlo/helo yet" )
}
}
implement outgoing tls reports
we were already accepting, processing and displaying incoming tls reports. now
we start tracking TLS connection and security-policy-related errors for
outgoing message deliveries as well. we send reports once a day, to the
reporting addresses specified in TLSRPT records (rua) of a policy domain. these
reports are about MTA-STS policies and/or DANE policies, and about
STARTTLS-related failures.
sending reports is enabled by default, but can be disabled through setting
NoOutgoingTLSReports in mox.conf.
only at the end of the implementation process came the realization that the
TLSRPT policy domain for DANE (MX) hosts are separate from the TLSRPT policy
for the recipient domain, and that MTA-STS and DANE TLS/policy results are
typically delivered in separate reports. so MX hosts need their own TLSRPT
policies.
config for the per-host TLSRPT policy should be added to mox.conf for existing
installs, in field HostTLSRPT. it is automatically configured by quickstart for
new installs. with a HostTLSRPT config, the "dns records" and "dns check" admin
pages now suggest the per-host TLSRPT record. by creating that record, you're
requesting TLS reports about your MX host.
gathering all the TLS/policy results is somewhat tricky. the tentacles go
throughout the code. the positive result is that the TLS/policy-related code
had to be cleaned up a bit. for example, the smtpclient TLS modes now reflect
reality better, with independent settings about whether PKIX and/or DANE
verification has to be done, and/or whether verification errors have to be
ignored (e.g. for tls-required: no header). also, cached mtasts policies of
mode "none" are now cleaned up once the MTA-STS DNS record goes away.
2023-11-09 19:40:46 +03:00
// If smtp server is configured to require TLS for all mail delivery (except to TLS
// reporting address), abort command.
func ( c * conn ) xneedTLSForDelivery ( rcpt smtp . Path ) {
// For TLS reports, we allow the message in even without TLS, because there may be
// TLS interopability problems. ../rfc/8460:316
if c . requireTLSForDelivery && ! c . tls && ! isTLSReportRecipient ( rcpt ) {
2023-01-30 16:27:06 +03:00
// ../rfc/3207:148
xsmtpUserErrorf ( smtp . C530SecurityRequired , smtp . SePol7Other0 , "STARTTLS required for mail delivery" )
}
}
implement outgoing tls reports
we were already accepting, processing and displaying incoming tls reports. now
we start tracking TLS connection and security-policy-related errors for
outgoing message deliveries as well. we send reports once a day, to the
reporting addresses specified in TLSRPT records (rua) of a policy domain. these
reports are about MTA-STS policies and/or DANE policies, and about
STARTTLS-related failures.
sending reports is enabled by default, but can be disabled through setting
NoOutgoingTLSReports in mox.conf.
only at the end of the implementation process came the realization that the
TLSRPT policy domain for DANE (MX) hosts are separate from the TLSRPT policy
for the recipient domain, and that MTA-STS and DANE TLS/policy results are
typically delivered in separate reports. so MX hosts need their own TLSRPT
policies.
config for the per-host TLSRPT policy should be added to mox.conf for existing
installs, in field HostTLSRPT. it is automatically configured by quickstart for
new installs. with a HostTLSRPT config, the "dns records" and "dns check" admin
pages now suggest the per-host TLSRPT record. by creating that record, you're
requesting TLS reports about your MX host.
gathering all the TLS/policy results is somewhat tricky. the tentacles go
throughout the code. the positive result is that the TLS/policy-related code
had to be cleaned up a bit. for example, the smtpclient TLS modes now reflect
reality better, with independent settings about whether PKIX and/or DANE
verification has to be done, and/or whether verification errors have to be
ignored (e.g. for tls-required: no header). also, cached mtasts policies of
mode "none" are now cleaned up once the MTA-STS DNS record goes away.
2023-11-09 19:40:46 +03:00
func isTLSReportRecipient ( rcpt smtp . Path ) bool {
2024-04-24 20:15:30 +03:00
_ , _ , _ , dest , err := mox . LookupAddress ( rcpt . Localpart , rcpt . IPDomain . Domain , false , false )
implement outgoing tls reports
we were already accepting, processing and displaying incoming tls reports. now
we start tracking TLS connection and security-policy-related errors for
outgoing message deliveries as well. we send reports once a day, to the
reporting addresses specified in TLSRPT records (rua) of a policy domain. these
reports are about MTA-STS policies and/or DANE policies, and about
STARTTLS-related failures.
sending reports is enabled by default, but can be disabled through setting
NoOutgoingTLSReports in mox.conf.
only at the end of the implementation process came the realization that the
TLSRPT policy domain for DANE (MX) hosts are separate from the TLSRPT policy
for the recipient domain, and that MTA-STS and DANE TLS/policy results are
typically delivered in separate reports. so MX hosts need their own TLSRPT
policies.
config for the per-host TLSRPT policy should be added to mox.conf for existing
installs, in field HostTLSRPT. it is automatically configured by quickstart for
new installs. with a HostTLSRPT config, the "dns records" and "dns check" admin
pages now suggest the per-host TLSRPT record. by creating that record, you're
requesting TLS reports about your MX host.
gathering all the TLS/policy results is somewhat tricky. the tentacles go
throughout the code. the positive result is that the TLS/policy-related code
had to be cleaned up a bit. for example, the smtpclient TLS modes now reflect
reality better, with independent settings about whether PKIX and/or DANE
verification has to be done, and/or whether verification errors have to be
ignored (e.g. for tls-required: no header). also, cached mtasts policies of
mode "none" are now cleaned up once the MTA-STS DNS record goes away.
2023-11-09 19:40:46 +03:00
return err == nil && ( dest . HostTLSReports || dest . DomainTLSReports )
}
2023-01-30 16:27:06 +03:00
func ( c * conn ) cmdHelo ( p * parser ) {
c . cmdHello ( p , false )
}
func ( c * conn ) cmdEhlo ( p * parser ) {
c . cmdHello ( p , true )
}
// ../rfc/5321:1783
func ( c * conn ) cmdHello ( p * parser , ehlo bool ) {
var remote dns . IPDomain
2023-12-05 23:13:57 +03:00
if c . submission && ! mox . Pedantic {
2023-08-25 16:29:02 +03:00
// Mail clients regularly put bogus information in the hostname/ip. For submission,
// the value is of no use, so there is not much point in annoying the user with
// errors they cannot fix themselves. Except when in pedantic mode.
remote = dns . IPDomain { IP : c . remoteIP }
2023-01-30 16:27:06 +03:00
} else {
2023-08-25 16:29:02 +03:00
p . xspace ( )
if ehlo {
remote = p . xipdomain ( true )
} else {
remote = dns . IPDomain { Domain : p . xdomain ( ) }
2023-01-30 16:27:06 +03:00
// Verify a remote domain name has an A or AAAA record, CNAME not allowed. ../rfc/5321:722
cidctx := context . WithValue ( mox . Context , mlog . CidKey , c . cid )
ctx , cancel := context . WithTimeout ( cidctx , time . Minute )
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
_ , _ , err := c . resolver . LookupIPAddr ( ctx , remote . Domain . ASCII + "." )
2023-01-30 16:27:06 +03:00
cancel ( )
if dns . IsNotFound ( err ) {
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SeProto5Other0 , "your ehlo domain does not resolve to an IP address" )
}
// For success or temporary resolve errors, we'll just continue.
}
2023-08-25 16:29:02 +03:00
// ../rfc/5321:1827
// Though a few paragraphs earlier is a claim additional data can occur for address
// literals (IP addresses), although the ABNF in that document does not allow it.
// We allow additional text, but only if space-separated.
if len ( remote . IP ) > 0 && p . space ( ) {
p . remainder ( ) // ../rfc/5321:1802 ../rfc/2821:1632
}
p . xend ( )
2023-01-30 16:27:06 +03:00
}
// Reset state as if RSET command has been issued. ../rfc/5321:2093 ../rfc/5321:2453
c . rset ( )
c . ehlo = ehlo
c . hello = remote
// https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml
c . bwritelinef ( "250-%s" , c . hostname . ASCII )
c . bwritelinef ( "250-PIPELINING" ) // ../rfc/2920:108
c . bwritelinef ( "250-SIZE %d" , c . maxMessageSize ) // ../rfc/1870:70
// ../rfc/3207:237
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
if ! c . tls && c . baseTLSConfig != nil {
2023-01-30 16:27:06 +03:00
// ../rfc/3207:90
c . bwritelinef ( "250-STARTTLS" )
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
} else if c . extRequireTLS {
// ../rfc/8689:202
// ../rfc/8689:143
c . bwritelinef ( "250-REQUIRETLS" )
2023-01-30 16:27:06 +03:00
}
if c . submission {
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
var mechs string
2023-01-30 16:27:06 +03:00
// ../rfc/4954:123
if c . tls || ! c . requireTLSForAuth {
2023-12-24 01:07:21 +03:00
// We always mention the SCRAM PLUS variants, even if TLS is not active: It is a
// hint to the client that a TLS connection can use TLS channel binding during
// authentication. The client should select the bare variant when TLS isn't
// present, and also not indicate the server supports the PLUS variant in that
// case, or it would trigger the mechanism downgrade detection.
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
mechs = "SCRAM-SHA-256-PLUS SCRAM-SHA-256 SCRAM-SHA-1-PLUS SCRAM-SHA-1 CRAM-MD5 PLAIN LOGIN"
2023-01-30 16:27:06 +03:00
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
if c . tls && len ( c . conn . ( * tls . Conn ) . ConnectionState ( ) . PeerCertificates ) > 0 {
mechs = "EXTERNAL " + mechs
}
c . bwritelinef ( "250-AUTH %s" , mechs )
2024-02-10 19:55:56 +03:00
// ../rfc/4865:127
t := time . Now ( ) . Add ( queue . FutureReleaseIntervalMax ) . UTC ( ) // ../rfc/4865:98
c . bwritelinef ( "250-FUTURERELEASE %d %s" , queue . FutureReleaseIntervalMax / time . Second , t . Format ( time . RFC3339 ) )
2023-01-30 16:27:06 +03:00
}
c . bwritelinef ( "250-ENHANCEDSTATUSCODES" ) // ../rfc/2034:71
// todo future? c.writelinef("250-DSN")
2024-03-05 22:10:28 +03:00
c . bwritelinef ( "250-8BITMIME" ) // ../rfc/6152:86
2024-03-07 12:56:58 +03:00
c . bwritelinef ( "250-LIMITS RCPTMAX=%d" , rcptToLimit ) // ../rfc/9422:301
2024-03-05 22:10:28 +03:00
c . bwritecodeline ( 250 , "" , "SMTPUTF8" , nil ) // ../rfc/6531:201
2023-01-30 16:27:06 +03:00
c . xflush ( )
}
// ../rfc/3207:96
func ( c * conn ) cmdStarttls ( p * parser ) {
c . xneedHello ( )
p . xend ( )
if c . tls {
// ../rfc/3207:235
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "already speaking tls" )
}
if c . account != nil {
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "cannot starttls after authentication" )
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
if c . baseTLSConfig == nil {
2024-09-15 18:18:50 +03:00
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "starttls not offered" )
}
2023-01-30 16:27:06 +03:00
// We don't want to do TLS on top of c.r because it also prints protocol traces: We
// don't want to log the TLS stream. So we'll do TLS on the underlying connection,
// but make sure any bytes already read and in the buffer are used for the TLS
// handshake.
conn := c . conn
if n := c . r . Buffered ( ) ; n > 0 {
conn = & moxio . PrefixConn {
PrefixReader : io . LimitReader ( c . r , int64 ( n ) ) ,
Conn : conn ,
}
}
2023-11-13 12:26:31 +03:00
// We add the cid to the output, to help debugging in case of a failing TLS connection.
c . writecodeline ( smtp . C220ServiceReady , smtp . SeOther00 , "go! (" + mox . ReceivedID ( c . cid ) + ")" , nil )
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . xtlsHandshakeAndAuthenticate ( conn )
2023-01-30 16:27:06 +03:00
c . reset ( ) // ../rfc/3207:210
c . tls = true
}
// ../rfc/4954:139
func ( c * conn ) cmdAuth ( p * parser ) {
c . xneedHello ( )
if ! c . submission {
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "authentication only allowed on submission ports" )
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
if c . authSASL {
2023-01-30 16:27:06 +03:00
// ../rfc/4954:152
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "already authenticated" )
}
if c . mailFrom != nil {
// ../rfc/4954:157
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "authentication not allowed during mail transaction" )
}
2024-03-05 12:40:40 +03:00
// If authentication fails due to missing derived secrets, we don't hold it against
// the connection. There is no way to indicate server support for an authentication
// mechanism, but that a mechanism won't work for an account.
var missingDerivedSecrets bool
2023-01-30 16:27:06 +03:00
// For many failed auth attempts, slow down verification attempts.
// Dropping the connection could also work, but more so when we have a connection rate limiter.
// ../rfc/4954:770
2023-03-10 12:23:43 +03:00
if c . authFailed > 3 && authFailDelay > 0 {
2023-01-30 16:27:06 +03:00
// ../rfc/4954:770
2023-03-10 12:23:43 +03:00
mox . Sleep ( mox . Context , time . Duration ( c . authFailed - 3 ) * authFailDelay )
2023-01-30 16:27:06 +03:00
}
c . authFailed ++ // Compensated on success.
2023-02-08 23:45:32 +03:00
defer func ( ) {
2024-03-05 12:40:40 +03:00
if missingDerivedSecrets {
c . authFailed --
}
2023-02-08 23:45:32 +03:00
// On the 3rd failed authentication, start responding slowly. Successful auth will
// cause fast responses again.
if c . authFailed >= 3 {
c . setSlow ( true )
}
} ( )
2023-01-30 16:27:06 +03:00
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
var authVariant string // Only known strings, used in metrics.
2023-01-30 16:27:06 +03:00
authResult := "error"
defer func ( ) {
metrics . AuthenticationInc ( "submission" , authVariant , authResult )
2024-03-05 12:40:40 +03:00
if authResult == "ok" {
2023-02-08 00:56:03 +03:00
mox . LimiterFailedAuth . Reset ( c . remoteIP , time . Now ( ) )
2024-03-05 12:40:40 +03:00
} else if ! missingDerivedSecrets {
2023-02-08 00:56:03 +03:00
mox . LimiterFailedAuth . Add ( c . remoteIP , time . Now ( ) , 1 )
}
2023-01-30 16:27:06 +03:00
} ( )
// ../rfc/4954:699
p . xspace ( )
mech := p . xsaslMech ( )
2024-07-18 22:17:33 +03:00
// Read the first parameter, either as initial parameter or by sending a
// continuation with the optional encChal (must already be base64-encoded).
xreadInitial := func ( encChal string ) [ ] byte {
2023-01-30 16:27:06 +03:00
var auth string
if p . empty ( ) {
2024-07-18 22:17:33 +03:00
c . writelinef ( "%d %s" , smtp . C334ContinueAuth , encChal ) // ../rfc/4954:205
2023-01-30 16:27:06 +03:00
// todo future: handle max length of 12288 octets and return proper responde codes otherwise ../rfc/4954:253
auth = c . readline ( )
if auth == "*" {
// ../rfc/4954:193
authResult = "aborted"
xsmtpUserErrorf ( smtp . C501BadParamSyntax , smtp . SeProto5Other0 , "authentication aborted" )
}
} else {
p . xspace ( )
2023-12-05 23:13:57 +03:00
if ! mox . Pedantic {
2023-10-13 22:35:03 +03:00
// Windows Mail 16005.14326.21606.0 sends two spaces between "AUTH PLAIN" and the
// base64 data.
for p . space ( ) {
}
}
2023-01-30 16:27:06 +03:00
auth = p . remainder ( )
if auth == "" {
// ../rfc/4954:235
xsmtpUserErrorf ( smtp . C501BadParamSyntax , smtp . SeProto5Syntax2 , "missing initial auth base64 parameter after space" )
} else if auth == "=" {
// ../rfc/4954:214
auth = "" // Base64 decode below will result in empty buffer.
}
}
buf , err := base64 . StdEncoding . DecodeString ( auth )
if err != nil {
// ../rfc/4954:235
xsmtpUserErrorf ( smtp . C501BadParamSyntax , smtp . SeProto5Syntax2 , "invalid base64: %s" , err )
}
2023-01-31 02:22:26 +03:00
return buf
}
xreadContinuation := func ( ) [ ] byte {
line := c . readline ( )
if line == "*" {
authResult = "aborted"
xsmtpUserErrorf ( smtp . C501BadParamSyntax , smtp . SeProto5Other0 , "authentication aborted" )
}
buf , err := base64 . StdEncoding . DecodeString ( line )
if err != nil {
// ../rfc/4954:235
xsmtpUserErrorf ( smtp . C501BadParamSyntax , smtp . SeProto5Syntax2 , "invalid base64: %s" , err )
}
return buf
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
// The various authentication mechanisms set account and username. We may already
// have an account and username from TLS client authentication. Afterwards, we
// check that the account is the same.
var account * store . Account
var username string
defer func ( ) {
if account != nil {
err := account . Close ( )
c . log . Check ( err , "close account" )
}
} ( )
2023-01-31 02:22:26 +03:00
switch mech {
case "PLAIN" :
authVariant = "plain"
// ../rfc/4954:343
// ../rfc/4954:326
if ! c . tls && c . requireTLSForAuth {
xsmtpUserErrorf ( smtp . C538EncReqForAuth , smtp . SePol7EncReqForAuth11 , "authentication requires tls" )
}
2023-02-03 22:33:19 +03:00
// Password is in line in plain text, so hide it.
defer c . xtrace ( mlog . LevelTraceauth ) ( )
2024-07-18 22:17:33 +03:00
buf := xreadInitial ( "" )
2023-02-03 22:33:19 +03:00
c . xtrace ( mlog . LevelTrace ) // Restore.
2023-01-30 16:27:06 +03:00
plain := bytes . Split ( buf , [ ] byte { 0 } )
if len ( plain ) != 3 {
xsmtpUserErrorf ( smtp . C501BadParamSyntax , smtp . SeProto5BadParams4 , "auth data should have 3 nul-separated tokens, got %d" , len ( plain ) )
}
2024-03-09 01:29:15 +03:00
authz := norm . NFC . String ( string ( plain [ 0 ] ) )
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
username = norm . NFC . String ( string ( plain [ 1 ] ) )
2023-01-30 16:27:06 +03:00
password := string ( plain [ 2 ] )
2023-02-08 23:45:32 +03:00
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
if authz != "" && authz != username {
2023-02-08 23:45:32 +03:00
authResult = "badcreds"
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "cannot assume other role" )
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
var err error
account , err = store . OpenEmailAuth ( c . log , username , password )
2023-01-30 16:27:06 +03:00
if err != nil && errors . Is ( err , store . ErrUnknownCredentials ) {
// ../rfc/4954:274
authResult = "badcreds"
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2023-01-30 16:27:06 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "bad user/pass" )
}
xcheckf ( err , "verifying credentials" )
2023-11-22 23:44:55 +03:00
case "LOGIN" :
// LOGIN is obsoleted in favor of PLAIN, only implemented to support legacy
// clients, see Internet-Draft (I-D):
// https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
authVariant = "login"
// ../rfc/4954:343
// ../rfc/4954:326
if ! c . tls && c . requireTLSForAuth {
xsmtpUserErrorf ( smtp . C538EncReqForAuth , smtp . SePol7EncReqForAuth11 , "authentication requires tls" )
}
2024-07-18 22:17:33 +03:00
// Read user name. The I-D says the client should ignore the server challenge, but
// also that some clients may require challenge "Username:" instead of "User
2024-10-03 21:29:40 +03:00
// Name". We can't sent both... Servers most commonly return "Username:" and
// "Password:", so we do the same.
2023-11-22 23:44:55 +03:00
// I-D says maximum length must be 64 bytes. We allow more, for long user names
// (domains).
2024-10-03 21:29:40 +03:00
encChal := base64 . StdEncoding . EncodeToString ( [ ] byte ( "Username:" ) )
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
username = string ( xreadInitial ( encChal ) )
2024-03-09 01:29:15 +03:00
username = norm . NFC . String ( username )
2023-11-22 23:44:55 +03:00
// Again, client should ignore the challenge, we send the same as the example in
// the I-D.
2024-10-03 21:29:40 +03:00
c . writelinef ( "%d %s" , smtp . C334ContinueAuth , base64 . StdEncoding . EncodeToString ( [ ] byte ( "Password:" ) ) )
2023-11-22 23:44:55 +03:00
// Password is in line in plain text, so hide it.
defer c . xtrace ( mlog . LevelTraceauth ) ( )
password := string ( xreadContinuation ( ) )
c . xtrace ( mlog . LevelTrace ) // Restore.
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
var err error
account , err = store . OpenEmailAuth ( c . log , username , password )
2023-11-22 23:44:55 +03:00
if err != nil && errors . Is ( err , store . ErrUnknownCredentials ) {
// ../rfc/4954:274
authResult = "badcreds"
2023-12-05 15:35:58 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2023-11-22 23:44:55 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "bad user/pass" )
}
xcheckf ( err , "verifying credentials" )
2023-02-05 18:29:03 +03:00
case "CRAM-MD5" :
authVariant = strings . ToLower ( mech )
p . xempty ( )
// ../rfc/2195:82
chal := fmt . Sprintf ( "<%d.%d@%s>" , uint64 ( mox . CryptoRandInt ( ) ) , time . Now ( ) . UnixNano ( ) , mox . Conf . Static . HostnameDomain . ASCII )
c . writelinef ( "%d %s" , smtp . C334ContinueAuth , base64 . StdEncoding . EncodeToString ( [ ] byte ( chal ) ) )
resp := xreadContinuation ( )
t := strings . Split ( string ( resp ) , " " )
if len ( t ) != 2 || len ( t [ 1 ] ) != 2 * md5 . Size {
xsmtpUserErrorf ( smtp . C501BadParamSyntax , smtp . SeProto5BadParams4 , "malformed cram-md5 response" )
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
username = norm . NFC . String ( t [ 0 ] )
c . log . Debug ( "cram-md5 auth" , slog . String ( "username" , username ) )
var err error
account , _ , err = store . OpenEmail ( c . log , username )
2024-10-03 16:18:09 +03:00
if err != nil && errors . Is ( err , store . ErrUnknownCredentials ) {
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2024-10-03 16:18:09 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "bad user/pass" )
2023-02-05 18:29:03 +03:00
}
xcheckf ( err , "looking up address" )
var ipadhash , opadhash hash . Hash
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
account . WithRLock ( func ( ) {
err := account . DB . Read ( context . TODO ( ) , func ( tx * bstore . Tx ) error {
2023-02-05 18:29:03 +03:00
password , err := bstore . QueryTx [ store . Password ] ( tx ) . Get ( )
if err == bstore . ErrAbsent {
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2023-02-05 18:29:03 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "bad user/pass" )
}
if err != nil {
return err
}
ipadhash = password . CRAMMD5 . Ipad
opadhash = password . CRAMMD5 . Opad
return nil
} )
xcheckf ( err , "tx read" )
} )
if ipadhash == nil || opadhash == nil {
2024-03-05 12:40:40 +03:00
missingDerivedSecrets = true
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Info ( "cram-md5 auth attempt without derived secrets set, save password again to store secrets" , slog . String ( "username" , username ) )
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2023-02-05 18:29:03 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "bad user/pass" )
}
// ../rfc/2195:138 ../rfc/2104:142
ipadhash . Write ( [ ] byte ( chal ) )
opadhash . Write ( ipadhash . Sum ( nil ) )
digest := fmt . Sprintf ( "%x" , opadhash . Sum ( nil ) )
if digest != t [ 1 ] {
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2023-02-05 18:29:03 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "bad user/pass" )
}
2023-12-24 01:07:21 +03:00
case "SCRAM-SHA-256-PLUS" , "SCRAM-SHA-256" , "SCRAM-SHA-1-PLUS" , "SCRAM-SHA-1" :
2023-01-31 02:22:26 +03:00
// todo: improve handling of errors during scram. e.g. invalid parameters. should we abort the imap command, or continue until the end and respond with a scram-level error?
// todo: use single implementation between ../imapserver/server.go and ../smtpserver/server.go
2023-12-24 01:07:21 +03:00
// Passwords cannot be retrieved or replayed from the trace.
2023-02-05 14:30:14 +03:00
authVariant = strings . ToLower ( mech )
var h func ( ) hash . Hash
2023-12-24 01:07:21 +03:00
switch authVariant {
case "scram-sha-1" , "scram-sha-1-plus" :
2023-02-05 14:30:14 +03:00
h = sha1 . New
2023-12-24 01:07:21 +03:00
case "scram-sha-256" , "scram-sha-256-plus" :
2023-02-05 14:30:14 +03:00
h = sha256 . New
2023-12-24 01:07:21 +03:00
default :
xsmtpServerErrorf ( codes { smtp . C554TransactionFailed , smtp . SeSys3Other0 } , "missing scram auth method case" )
2023-02-05 14:30:14 +03:00
}
2023-01-31 02:22:26 +03:00
2023-12-24 01:07:21 +03:00
var cs * tls . ConnectionState
channelBindingRequired := strings . HasSuffix ( authVariant , "-plus" )
if channelBindingRequired && ! c . tls {
// ../rfc/4954:630
xsmtpUserErrorf ( smtp . C538EncReqForAuth , smtp . SePol7EncReqForAuth11 , "scram plus mechanism requires tls connection" )
}
if c . tls {
xcs := c . conn . ( * tls . Conn ) . ConnectionState ( )
cs = & xcs
}
2024-07-18 22:17:33 +03:00
c0 := xreadInitial ( "" )
2023-12-24 01:07:21 +03:00
ss , err := scram . NewServer ( h , c0 , cs , channelBindingRequired )
2024-10-03 16:18:09 +03:00
if err != nil {
c . log . Infox ( "scram protocol error" , err , slog . Any ( "remote" , c . remoteIP ) )
xsmtpUserErrorf ( smtp . C455BadParams , smtp . SePol7Other0 , "scram protocol error: %s" , err )
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
username = norm . NFC . String ( ss . Authentication )
c . log . Debug ( "scram auth" , slog . String ( "authentication" , username ) )
account , _ , err = store . OpenEmail ( c . log , username )
2023-01-31 02:22:26 +03:00
if err != nil {
// todo: we could continue scram with a generated salt, deterministically generated
// from the username. that way we don't have to store anything but attackers cannot
// learn if an account exists. same for absent scram saltedpassword below.
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2023-01-31 02:22:26 +03:00
xsmtpUserErrorf ( smtp . C454TempAuthFail , smtp . SeSys3Other0 , "scram not possible" )
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
if ss . Authorization != "" && ss . Authorization != username {
2023-01-31 02:22:26 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "authentication with authorization for different user not supported" )
}
2023-02-05 14:30:14 +03:00
var xscram store . SCRAM
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
account . WithRLock ( func ( ) {
err := account . DB . Read ( context . TODO ( ) , func ( tx * bstore . Tx ) error {
2023-02-05 14:30:14 +03:00
password , err := bstore . QueryTx [ store . Password ] ( tx ) . Get ( )
2024-03-05 12:40:40 +03:00
if err == bstore . ErrAbsent {
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2024-03-05 12:40:40 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "bad user/pass" )
}
xcheckf ( err , "fetching credentials" )
2023-12-24 01:07:21 +03:00
switch authVariant {
case "scram-sha-1" , "scram-sha-1-plus" :
2023-02-05 14:30:14 +03:00
xscram = password . SCRAMSHA1
2023-12-24 01:07:21 +03:00
case "scram-sha-256" , "scram-sha-256-plus" :
2023-02-05 14:30:14 +03:00
xscram = password . SCRAMSHA256
2023-12-24 01:07:21 +03:00
default :
xsmtpServerErrorf ( codes { smtp . C554TransactionFailed , smtp . SeSys3Other0 } , "missing scram auth credentials case" )
2023-02-05 14:30:14 +03:00
}
2024-03-05 12:40:40 +03:00
if len ( xscram . Salt ) == 0 || xscram . Iterations == 0 || len ( xscram . SaltedPassword ) == 0 {
missingDerivedSecrets = true
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Info ( "scram auth attempt without derived secrets set, save password again to store secrets" , slog . String ( "address" , username ) )
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2023-01-31 02:22:26 +03:00
xsmtpUserErrorf ( smtp . C454TempAuthFail , smtp . SeSys3Other0 , "scram not possible" )
}
2024-03-05 12:40:40 +03:00
return nil
2023-01-31 02:22:26 +03:00
} )
xcheckf ( err , "read tx" )
} )
2023-02-05 14:30:14 +03:00
s1 , err := ss . ServerFirst ( xscram . Iterations , xscram . Salt )
2023-01-31 02:22:26 +03:00
xcheckf ( err , "scram first server step" )
c . writelinef ( "%d %s" , smtp . C334ContinueAuth , base64 . StdEncoding . EncodeToString ( [ ] byte ( s1 ) ) ) // ../rfc/4954:187
c2 := xreadContinuation ( )
2023-02-05 14:30:14 +03:00
s3 , err := ss . Finish ( c2 , xscram . SaltedPassword )
2023-01-31 02:22:26 +03:00
if len ( s3 ) > 0 {
c . writelinef ( "%d %s" , smtp . C334ContinueAuth , base64 . StdEncoding . EncodeToString ( [ ] byte ( s3 ) ) ) // ../rfc/4954:187
}
if err != nil {
c . readline ( ) // Should be "*" for cancellation.
if errors . Is ( err , scram . ErrInvalidProof ) {
authResult = "badcreds"
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Info ( "failed authentication attempt" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2023-01-31 02:22:26 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "bad credentials" )
2024-10-03 16:18:09 +03:00
} else if errors . Is ( err , scram . ErrChannelBindingsDontMatch ) {
authResult = "badchanbind"
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Warn ( "bad channel binding during authentication, potential mitm" , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2024-10-03 16:18:09 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7MsgIntegrity7 , "channel bindings do not match, potential mitm" )
} else if errors . Is ( err , scram . ErrInvalidEncoding ) {
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
c . log . Infox ( "bad scram protocol message" , err , slog . String ( "username" , username ) , slog . Any ( "remote" , c . remoteIP ) )
2024-10-03 16:18:09 +03:00
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7Other0 , "bad scram protocol message" )
2023-01-31 02:22:26 +03:00
}
xcheckf ( err , "server final" )
}
// Client must still respond, but there is nothing to say. See ../rfc/9051:6221
// The message should be empty. todo: should we require it is empty?
xreadContinuation ( )
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
case "EXTERNAL" :
authVariant = strings . ToLower ( mech )
// ../rfc/4422:1618
buf := xreadInitial ( "" )
username = string ( buf )
if ! c . tls {
// ../rfc/4954:630
xsmtpUserErrorf ( smtp . C538EncReqForAuth , smtp . SePol7EncReqForAuth11 , "tls required for tls client certificate authentication" )
}
if c . account == nil {
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "missing client certificate, required for tls client certificate authentication" )
}
if username == "" {
username = c . username
}
var err error
account , _ , err = store . OpenEmail ( c . log , username )
xcheckf ( err , "looking up username from tls client authentication" )
2023-01-31 02:22:26 +03:00
2023-01-30 16:27:06 +03:00
default :
// ../rfc/4954:176
xsmtpUserErrorf ( smtp . C504ParamNotImpl , smtp . SeProto5BadParams4 , "mechanism %s not supported" , mech )
}
implement tls client certificate authentication
the imap & smtp servers now allow logging in with tls client authentication and
the "external" sasl authentication mechanism. email clients like thunderbird,
fairemail, k9, macos mail implement it. this seems to be the most secure among
the authentication mechanism commonly implemented by clients. a useful property
is that an account can have a separate tls public key for each device/email
client. with tls client cert auth, authentication is also bound to the tls
connection. a mitm cannot pass the credentials on to another tls connection,
similar to scram-*-plus. though part of scram-*-plus is that clients verify
that the server knows the client credentials.
for tls client auth with imap, we send a "preauth" untagged message by default.
that puts the connection in authenticated state. given the imap connection
state machine, further authentication commands are not allowed. some clients
don't recognize the preauth message, and try to authenticate anyway, which
fails. a tls public key has a config option to disable preauth, keeping new
connections in unauthenticated state, to work with such email clients.
for smtp (submission), we don't require an explicit auth command.
both for imap and smtp, we allow a client to authenticate with another
mechanism than "external". in that case, credentials are verified, and have to
be for the same account as the tls client auth, but the adress can be another
one than the login address configured with the tls public key.
only the public key is used to identify the account that is authenticating. we
ignore the rest of the certificate. expiration dates, names, constraints, etc
are not verified. no certificate authorities are involved.
users can upload their own (minimal) certificate. the account web interface
shows openssl commands you can run to generate a private key, minimal cert, and
a p12 file (the format that email clients seem to like...) containing both
private key and certificate.
the imapclient & smtpclient packages can now also use tls client auth. and so
does "mox sendmail", either with a pem file with private key and certificate,
or with just an ed25519 private key.
there are new subcommands "mox config tlspubkey ..." for
adding/removing/listing tls public keys from the cli, by the admin.
2024-12-06 00:41:49 +03:00
// We may already have TLS credentials. We allow an additional SASL authentication,
// possibly with different username, but the account must be the same.
if c . account != nil {
if account != c . account {
c . log . Debug ( "sasl authentication for different account than tls client authentication, aborting connection" ,
slog . String ( "saslmechanism" , authVariant ) ,
slog . String ( "saslaccount" , account . Name ) ,
slog . String ( "tlsaccount" , c . account . Name ) ,
slog . String ( "saslusername" , username ) ,
slog . String ( "tlsusername" , c . username ) ,
)
xsmtpUserErrorf ( smtp . C535AuthBadCreds , smtp . SePol7AuthBadCreds8 , "authentication failed, tls client certificate public key belongs to another account" )
} else if username != c . username {
c . log . Debug ( "sasl authentication for different username than tls client certificate authentication, switching to sasl username" ,
slog . String ( "saslmechanism" , authVariant ) ,
slog . String ( "saslusername" , username ) ,
slog . String ( "tlsusername" , c . username ) ,
slog . String ( "account" , c . account . Name ) ,
)
}
} else {
c . account = account
account = nil // Prevent cleanup.
}
c . username = username
authResult = "ok"
c . authSASL = true
c . authFailed = 0
c . setSlow ( false )
// ../rfc/4954:276
c . writecodeline ( smtp . C235AuthSuccess , smtp . SePol7Other0 , "nice" , nil )
2023-01-30 16:27:06 +03:00
}
// ../rfc/5321:1879 ../rfc/5321:1025
func ( c * conn ) cmdMail ( p * parser ) {
// requirements for maximum line length:
// ../rfc/5321:3500 (base max of 512 including crlf) ../rfc/4954:134 (+500) ../rfc/1870:92 (+26) ../rfc/6152:90 (none specified) ../rfc/6531:231 (+10)
2024-02-10 19:55:56 +03:00
// todo future: enforce? doesn't really seem worth it...
2023-01-30 16:27:06 +03:00
if c . transactionBad > 10 && c . transactionGood == 0 {
// If we get many bad transactions, it's probably a spammer that is guessing user names.
// Useful in combination with rate limiting.
// ../rfc/5321:4349
c . writecodeline ( smtp . C550MailboxUnavail , smtp . SeAddr1Other0 , "too many failures" , nil )
panic ( errIO )
}
c . xneedHello ( )
c . xcheckAuth ( )
if c . mailFrom != nil {
// ../rfc/5321:2507, though ../rfc/5321:1029 contradicts, implying a MAIL would also reset, but ../rfc/5321:1160 decides.
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "already have MAIL" )
}
// Ensure clear transaction state on failure.
defer func ( ) {
x := recover ( )
if x != nil {
// ../rfc/5321:2514
c . rset ( )
panic ( x )
}
} ( )
p . xtake ( " FROM:" )
2023-12-11 17:34:11 +03:00
// note: no space allowed after colon. ../rfc/5321:1093
// Microsoft Outlook 365 Apps for Enterprise sends it with submission. For delivery
// it is mostly used by spammers, but has been seen with legitimate senders too.
2023-12-05 23:13:57 +03:00
if ! mox . Pedantic {
2023-07-28 21:43:44 +03:00
p . space ( )
}
2023-01-30 16:27:06 +03:00
rawRevPath := p . xrawReversePath ( )
paramSeen := map [ string ] bool { }
for p . space ( ) {
// ../rfc/5321:2273
key := p . xparamKeyword ( )
K := strings . ToUpper ( key )
if paramSeen [ K ] {
// e.g. ../rfc/6152:128
xsmtpUserErrorf ( smtp . C501BadParamSyntax , smtp . SeProto5BadParams4 , "duplicate param %q" , key )
}
paramSeen [ K ] = true
switch K {
case "SIZE" :
p . xtake ( "=" )
2024-02-10 19:55:56 +03:00
size := p . xnumber ( 20 , true ) // ../rfc/1870:90
2023-01-30 16:27:06 +03:00
if size > c . maxMessageSize {
// ../rfc/1870:136 ../rfc/3463:382
ecode := smtp . SeSys3MsgLimitExceeded4
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
if size < config . DefaultMaxMsgSize {
2023-01-30 16:27:06 +03:00
ecode = smtp . SeMailbox2MsgLimitExceeded3
}
xsmtpUserErrorf ( smtp . C552MailboxFull , ecode , "message too large" )
}
// We won't verify the message is exactly the size the remote claims. Buf if it is
// larger, we'll abort the transaction when remote crosses the boundary.
case "BODY" :
p . xtake ( "=" )
// ../rfc/6152:90
v := p . xparamValue ( )
switch strings . ToUpper ( v ) {
case "7BIT" :
c . has8bitmime = false
case "8BITMIME" :
c . has8bitmime = true
default :
xsmtpUserErrorf ( smtp . C555UnrecognizedAddrParams , smtp . SeProto5BadParams4 , "unrecognized parameter %q" , key )
}
case "AUTH" :
// ../rfc/4954:455
// We act as if we don't trust the client to specify a mailbox. Instead, we always
// check the rfc5321.mailfrom and rfc5322.from before accepting the submission.
// ../rfc/4954:538
// ../rfc/4954:704
// todo future: should we accept utf-8-addr-xtext if there is no smtputf8, and utf-8 if there is? need to find a spec ../rfc/6533:259
p . xtake ( "=" )
p . xtake ( "<" )
p . xtext ( )
p . xtake ( ">" )
case "SMTPUTF8" :
// ../rfc/6531:213
c . smtputf8 = true
2024-03-31 16:23:53 +03:00
c . msgsmtputf8 = true
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
case "REQUIRETLS" :
// ../rfc/8689:155
if ! c . tls {
xsmtpUserErrorf ( smtp . C530SecurityRequired , smtp . SePol7EncNeeded10 , "requiretls only allowed on tls-encrypted connections" )
} else if ! c . extRequireTLS {
xsmtpUserErrorf ( smtp . C555UnrecognizedAddrParams , smtp . SeSys3NotSupported3 , "REQUIRETLS not allowed for this connection" )
}
v := true
c . requireTLS = & v
2024-02-10 19:55:56 +03:00
case "HOLDFOR" , "HOLDUNTIL" :
// Only for submission ../rfc/4865:163
if ! c . submission {
xsmtpUserErrorf ( smtp . C555UnrecognizedAddrParams , smtp . SeSys3NotSupported3 , "unrecognized parameter %q" , key )
}
if K == "HOLDFOR" && paramSeen [ "HOLDUNTIL" ] || K == "HOLDUNTIL" && paramSeen [ "HOLDFOR" ] {
// ../rfc/4865:260
xsmtpUserErrorf ( smtp . C501BadParamSyntax , smtp . SeProto5BadParams4 , "cannot use both HOLDUNTIL and HOLFOR" )
}
p . xtake ( "=" )
// ../rfc/4865:263 ../rfc/4865:267 We are not following the advice of treating
// semantic errors as syntax errors
if K == "HOLDFOR" {
n := p . xnumber ( 9 , false ) // ../rfc/4865:92
if n > int64 ( queue . FutureReleaseIntervalMax / time . Second ) {
// ../rfc/4865:250
xsmtpUserErrorf ( smtp . C554TransactionFailed , smtp . SeProto5BadParams4 , "future release interval too far in the future" )
}
c . futureRelease = time . Now ( ) . Add ( time . Duration ( n ) * time . Second )
c . futureReleaseRequest = fmt . Sprintf ( "for;%d" , n )
} else {
t , s := p . xdatetimeutc ( )
ival := time . Until ( t )
if ival <= 0 {
// Likely a mistake by the user.
xsmtpUserErrorf ( smtp . C554TransactionFailed , smtp . SeProto5BadParams4 , "requested future release time is in the past" )
} else if ival > queue . FutureReleaseIntervalMax {
// ../rfc/4865:255
xsmtpUserErrorf ( smtp . C554TransactionFailed , smtp . SeProto5BadParams4 , "requested future release time is too far in the future" )
}
c . futureRelease = t
c . futureReleaseRequest = "until;" + s
}
2023-01-30 16:27:06 +03:00
default :
// ../rfc/5321:2230
xsmtpUserErrorf ( smtp . C555UnrecognizedAddrParams , smtp . SeSys3NotSupported3 , "unrecognized parameter %q" , key )
}
}
// We now know if we have to parse the address with support for utf8.
pp := newParser ( rawRevPath , c . smtputf8 , c )
rpath := pp . xbareReversePath ( )
pp . xempty ( )
pp = nil
p . xend ( )
// For submission, check if reverse path is allowed. I.e. authenticated account
// must have the rpath configured. We do a check again on rfc5322.from during DATA.
rpathAllowed := func ( ) bool {
// ../rfc/6409:349
if rpath . IsZero ( ) {
return true
}
2024-04-24 20:15:30 +03:00
accName , _ , _ , _ , err := mox . LookupAddress ( rpath . Localpart , rpath . IPDomain . Domain , false , false )
2023-01-30 16:27:06 +03:00
return err == nil && accName == c . account . Name
}
if ! c . submission && ! rpath . IPDomain . Domain . IsZero ( ) {
// If rpath domain has null MX record or is otherwise not accepting email, reject.
// ../rfc/7505:181
// ../rfc/5321:4045
cidctx := context . WithValue ( mox . Context , mlog . CidKey , c . cid )
ctx , cancel := context . WithTimeout ( cidctx , time . Minute )
valid , err := checkMXRecords ( ctx , c . resolver , rpath . IPDomain . Domain )
cancel ( )
if err != nil {
c . log . Infox ( "temporary reject for temporary mx lookup error" , err )
xsmtpServerErrorf ( codes { smtp . C451LocalErr , smtp . SeNet4Other0 } , "cannot verify mx records for mailfrom domain" )
} else if ! valid {
c . log . Info ( "permanent reject because mailfrom domain does not accept mail" )
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SePol7SenderHasNullMX27 , "mailfrom domain not configured for mail" )
}
}
if c . submission && ( len ( rpath . IPDomain . IP ) > 0 || ! rpathAllowed ( ) ) {
// ../rfc/6409:522
2023-12-05 15:35:58 +03:00
c . log . Info ( "submission with unconfigured mailfrom" , slog . String ( "user" , c . username ) , slog . String ( "mailfrom" , rpath . String ( ) ) )
2023-01-30 16:27:06 +03:00
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SePol7DeliveryUnauth1 , "must match authenticated user" )
} else if ! c . submission && len ( rpath . IPDomain . IP ) > 0 {
// todo future: allow if the IP is the same as this connection is coming from? does later code allow this?
2023-12-05 15:35:58 +03:00
c . log . Info ( "delivery from address without domain" , slog . String ( "mailfrom" , rpath . String ( ) ) )
2023-01-30 16:27:06 +03:00
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SePol7Other0 , "domain name required" )
}
2023-03-12 12:38:02 +03:00
if Localserve && strings . HasPrefix ( string ( rpath . Localpart ) , "mailfrom" ) {
c . xlocalserveError ( rpath . Localpart )
}
2023-01-30 16:27:06 +03:00
c . mailFrom = & rpath
c . bwritecodeline ( smtp . C250Completed , smtp . SeAddr1Other0 , "looking good" , nil )
}
// ../rfc/5321:1916 ../rfc/5321:1054
func ( c * conn ) cmdRcpt ( p * parser ) {
c . xneedHello ( )
c . xcheckAuth ( )
if c . mailFrom == nil {
// ../rfc/5321:1088
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "missing MAIL FROM" )
}
// ../rfc/5321:1985
p . xtake ( " TO:" )
2023-12-11 17:34:11 +03:00
// note: no space allowed after colon. ../rfc/5321:1093
// Microsoft Outlook 365 Apps for Enterprise sends it with submission. For delivery
// it is mostly used by spammers, but has been seen with legitimate senders too.
2023-12-05 23:13:57 +03:00
if ! mox . Pedantic {
2023-07-28 21:43:44 +03:00
p . space ( )
}
2023-01-30 16:27:06 +03:00
var fpath smtp . Path
if p . take ( "<POSTMASTER>" ) {
fpath = smtp . Path { Localpart : "postmaster" }
} else {
fpath = p . xforwardPath ( )
}
for p . space ( ) {
// ../rfc/5321:2275
key := p . xparamKeyword ( )
2023-07-24 14:55:36 +03:00
// K := strings.ToUpper(key)
2023-01-30 16:27:06 +03:00
// todo future: DSN, ../rfc/3461, with "NOTIFY"
2023-07-24 14:55:36 +03:00
// ../rfc/5321:2230
xsmtpUserErrorf ( smtp . C555UnrecognizedAddrParams , smtp . SeSys3NotSupported3 , "unrecognized parameter %q" , key )
2023-01-30 16:27:06 +03:00
}
p . xend ( )
implement outgoing tls reports
we were already accepting, processing and displaying incoming tls reports. now
we start tracking TLS connection and security-policy-related errors for
outgoing message deliveries as well. we send reports once a day, to the
reporting addresses specified in TLSRPT records (rua) of a policy domain. these
reports are about MTA-STS policies and/or DANE policies, and about
STARTTLS-related failures.
sending reports is enabled by default, but can be disabled through setting
NoOutgoingTLSReports in mox.conf.
only at the end of the implementation process came the realization that the
TLSRPT policy domain for DANE (MX) hosts are separate from the TLSRPT policy
for the recipient domain, and that MTA-STS and DANE TLS/policy results are
typically delivered in separate reports. so MX hosts need their own TLSRPT
policies.
config for the per-host TLSRPT policy should be added to mox.conf for existing
installs, in field HostTLSRPT. it is automatically configured by quickstart for
new installs. with a HostTLSRPT config, the "dns records" and "dns check" admin
pages now suggest the per-host TLSRPT record. by creating that record, you're
requesting TLS reports about your MX host.
gathering all the TLS/policy results is somewhat tricky. the tentacles go
throughout the code. the positive result is that the TLS/policy-related code
had to be cleaned up a bit. for example, the smtpclient TLS modes now reflect
reality better, with independent settings about whether PKIX and/or DANE
verification has to be done, and/or whether verification errors have to be
ignored (e.g. for tls-required: no header). also, cached mtasts policies of
mode "none" are now cleaned up once the MTA-STS DNS record goes away.
2023-11-09 19:40:46 +03:00
// Check if TLS is enabled if required. It's not great that sender/recipient
// addresses may have been exposed in plaintext before we can reject delivery. The
// recipient could be the tls reporting addresses, which must always be able to
// receive in plain text.
c . xneedTLSForDelivery ( fpath )
2023-01-30 16:27:06 +03:00
// todo future: for submission, should we do explicit verification that domains are fully qualified? also for mail from. ../rfc/6409:420
2024-03-05 22:10:28 +03:00
if len ( c . recipients ) >= rcptToLimit {
2023-01-30 16:27:06 +03:00
// ../rfc/5321:3535 ../rfc/5321:3571
2024-03-05 22:10:28 +03:00
xsmtpUserErrorf ( smtp . C452StorageFull , smtp . SeProto5TooManyRcpts3 , "max of %d recipients reached" , rcptToLimit )
2023-01-30 16:27:06 +03:00
}
// We don't want to allow delivery to multiple recipients with a null reverse path.
// Why would anyone send like that? Null reverse path is intended for delivery
// notifications, they should go to a single recipient.
if ! c . submission && len ( c . recipients ) > 0 && c . mailFrom . IsZero ( ) {
xsmtpUserErrorf ( smtp . C452StorageFull , smtp . SeProto5TooManyRcpts3 , "only one recipient allowed with null reverse address" )
}
// Do not accept multiple recipients if remote does not pass SPF. Because we don't
// want to generate DSNs to unverified domains. This is the moment we
// can refuse individual recipients, DATA will be too late. Because mail
// servers must handle a max recipient limit gracefully and still send to the
// recipients that are accepted, this should not cause problems. Though we are in
// violation because the limit must be >= 100.
// ../rfc/5321:3598
// ../rfc/5321:4045
// Also see ../rfc/7489:2214
2023-03-12 12:38:02 +03:00
if ! c . submission && len ( c . recipients ) == 1 && ! Localserve {
2023-01-30 16:27:06 +03:00
// note: because of check above, mailFrom cannot be the null address.
var pass bool
d := c . mailFrom . IPDomain . Domain
if ! d . IsZero ( ) {
// todo: use this spf result for DATA.
spfArgs := spf . Args {
RemoteIP : c . remoteIP ,
MailFromLocalpart : c . mailFrom . Localpart ,
MailFromDomain : d ,
HelloDomain : c . hello ,
LocalIP : c . localIP ,
LocalHostname : c . hostname ,
}
cidctx := context . WithValue ( mox . Context , mlog . CidKey , c . cid )
spfctx , spfcancel := context . WithTimeout ( cidctx , time . Minute )
defer spfcancel ( )
2023-12-05 15:35:58 +03:00
receivedSPF , _ , _ , _ , err := spf . Verify ( spfctx , c . log . Logger , c . resolver , spfArgs )
2023-01-30 16:27:06 +03:00
spfcancel ( )
if err != nil {
2023-02-06 13:00:11 +03:00
c . log . Errorx ( "spf verify for multiple recipients" , err )
2023-01-30 16:27:06 +03:00
}
pass = receivedSPF . Identity == spf . ReceivedMailFrom && receivedSPF . Result == spf . StatusPass
}
if ! pass {
xsmtpUserErrorf ( smtp . C452StorageFull , smtp . SeProto5TooManyRcpts3 , "only one recipient allowed without spf pass" )
}
}
2024-04-24 12:35:07 +03:00
if Localserve && strings . HasPrefix ( string ( fpath . Localpart ) , "rcptto" ) {
c . xlocalserveError ( fpath . Localpart )
}
2023-03-12 12:38:02 +03:00
2024-04-24 12:35:07 +03:00
if len ( fpath . IPDomain . IP ) > 0 {
2023-01-30 16:27:06 +03:00
if ! c . submission {
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SeAddr1UnknownDestMailbox1 , "not accepting email for ip" )
}
2024-04-24 20:15:30 +03:00
c . recipients = append ( c . recipients , recipient { fpath , nil , nil } )
} else if accountName , alias , canonical , addr , err := mox . LookupAddress ( fpath . Localpart , fpath . IPDomain . Domain , true , true ) ; err == nil {
// note: a bare postmaster, without domain, is handled by LookupAddress. ../rfc/5321:735
if alias != nil {
c . recipients = append ( c . recipients , recipient { fpath , nil , & rcptAlias { * alias , canonical } } )
} else {
c . recipients = append ( c . recipients , recipient { fpath , & rcptAccount { accountName , addr , canonical } , nil } )
}
2024-04-24 12:35:07 +03:00
} else if Localserve {
// If the address isn't known, and we are in localserve, deliver to the mox user.
// If account or destination doesn't exist, it will be handled during delivery. For
// submissions, which is the common case, we'll deliver to the logged in user,
// which is typically the mox user.
acc , _ := mox . Conf . Account ( "mox" )
dest := acc . Destinations [ "mox@localhost" ]
2024-04-24 20:15:30 +03:00
c . recipients = append ( c . recipients , recipient { fpath , & rcptAccount { "mox" , dest , "mox@localhost" } , nil } )
2023-01-30 16:27:06 +03:00
} else if errors . Is ( err , mox . ErrDomainNotFound ) {
if ! c . submission {
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SeAddr1UnknownDestMailbox1 , "not accepting email for domain" )
}
// We'll be delivering this email.
2024-04-24 20:15:30 +03:00
c . recipients = append ( c . recipients , recipient { fpath , nil , nil } )
} else if errors . Is ( err , mox . ErrAddressNotFound ) {
2023-01-30 16:27:06 +03:00
if c . submission {
// For submission, we're transparent about which user exists. Should be fine for the typical small-scale deploy.
// ../rfc/5321:1071
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SeAddr1UnknownDestMailbox1 , "no such user" )
}
// We pretend to accept. We don't want to let remote know the user does not exist
// until after DATA. Because then remote has committed to sending a message.
// note: not local for !c.submission is the signal this address is in error.
2024-04-24 20:15:30 +03:00
c . recipients = append ( c . recipients , recipient { fpath , nil , nil } )
2023-01-30 16:27:06 +03:00
} else {
2023-12-05 15:35:58 +03:00
c . log . Errorx ( "looking up account for delivery" , err , slog . Any ( "rcptto" , fpath ) )
2023-01-30 16:27:06 +03:00
xsmtpServerErrorf ( codes { smtp . C451LocalErr , smtp . SeSys3Other0 } , "error processing" )
}
c . bwritecodeline ( smtp . C250Completed , smtp . SeAddr1Other0 , "now on the list" , nil )
}
2024-12-07 15:05:09 +03:00
func hasNonASCII ( s string ) bool {
for _ , c := range [ ] byte ( s ) {
if c > unicode . MaxASCII {
2024-03-31 16:23:53 +03:00
return true
}
}
2024-12-07 15:05:09 +03:00
return false
}
2024-03-31 16:23:53 +03:00
2024-12-07 15:05:09 +03:00
// ../rfc/6531:497
func ( c * conn ) isSMTPUTF8Required ( part * message . Part ) bool {
2024-03-31 16:30:24 +03:00
// Check "MAIL FROM".
2024-12-07 15:05:09 +03:00
if hasNonASCII ( string ( c . mailFrom . Localpart ) ) {
2024-03-31 16:23:53 +03:00
return true
}
2024-03-31 16:30:24 +03:00
// Check all "RCPT TO".
2024-03-31 16:23:53 +03:00
for _ , rcpt := range c . recipients {
2024-12-07 15:05:09 +03:00
if hasNonASCII ( string ( rcpt . Addr . Localpart ) ) {
2024-03-31 16:23:53 +03:00
return true
}
}
2024-12-07 15:05:09 +03:00
2024-03-31 16:30:24 +03:00
// Check header in all message parts.
2024-12-07 15:05:09 +03:00
smtputf8 , err := part . NeedsSMTPUTF8 ( )
xcheckf ( err , "checking if smtputf8 is required" )
return smtputf8
2024-03-31 16:23:53 +03:00
}
2023-01-30 16:27:06 +03:00
// ../rfc/5321:1992 ../rfc/5321:1098
func ( c * conn ) cmdData ( p * parser ) {
c . xneedHello ( )
c . xcheckAuth ( )
if c . mailFrom == nil {
// ../rfc/5321:1130
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "missing MAIL FROM" )
}
if len ( c . recipients ) == 0 {
// ../rfc/5321:1130
xsmtpUserErrorf ( smtp . C503BadCmdSeq , smtp . SeProto5BadCmdOrSeq1 , "missing RCPT TO" )
}
// ../rfc/5321:2066
p . xend ( )
// todo future: we could start a reader for a single line. we would then create a context that would be canceled on i/o errors.
// Entire delivery should be done within 30 minutes, or we abort.
cidctx := context . WithValue ( mox . Context , mlog . CidKey , c . cid )
cmdctx , cmdcancel := context . WithTimeout ( cidctx , 30 * time . Minute )
defer cmdcancel ( )
// Deadline is taken into account by Read and Write.
c . deadline , _ = cmdctx . Deadline ( )
defer func ( ) {
c . deadline = time . Time { }
} ( )
// ../rfc/5321:1994
c . writelinef ( "354 see you at the bare dot" )
2023-02-03 22:33:19 +03:00
// Mark as tracedata.
defer c . xtrace ( mlog . LevelTracedata ) ( )
2023-01-30 16:27:06 +03:00
// We read the data into a temporary file. We limit the size and do basic analysis while reading.
2023-12-05 15:35:58 +03:00
dataFile , err := store . CreateMessageTemp ( c . log , "smtp-deliver" )
2023-01-30 16:27:06 +03:00
if err != nil {
xsmtpServerErrorf ( errCodes ( smtp . C451LocalErr , smtp . SeSys3Other0 , err ) , "creating temporary file for message: %s" , err )
}
2023-11-01 20:57:38 +03:00
defer store . CloseRemoveTempFile ( c . log , dataFile , "smtpserver delivered message" )
2023-08-11 15:07:49 +03:00
msgWriter := message . NewWriter ( dataFile )
2023-01-30 16:27:06 +03:00
dr := smtp . NewDataReader ( c . r )
2023-02-03 22:33:19 +03:00
n , err := io . Copy ( & limitWriter { maxSize : c . maxMessageSize , w : msgWriter } , dr )
c . xtrace ( mlog . LevelTrace ) // Restore.
if err != nil {
2023-01-30 16:27:06 +03:00
if errors . Is ( err , errMessageTooLarge ) {
// ../rfc/1870:136 and ../rfc/3463:382
ecode := smtp . SeSys3MsgLimitExceeded4
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
if n < config . DefaultMaxMsgSize {
2023-01-30 16:27:06 +03:00
ecode = smtp . SeMailbox2MsgLimitExceeded3
}
2023-02-25 15:07:32 +03:00
c . writecodeline ( smtp . C451LocalErr , ecode , fmt . Sprintf ( "error copying data to file (%s)" , mox . ReceivedID ( c . cid ) ) , err )
2023-01-30 16:27:06 +03:00
panic ( fmt . Errorf ( "remote sent too much DATA: %w" , errIO ) )
}
2024-01-01 20:30:31 +03:00
if errors . Is ( err , smtp . ErrCRLF ) {
c . writecodeline ( smtp . C500BadSyntax , smtp . SeProto5Syntax2 , fmt . Sprintf ( "invalid bare \\r or \\n, may be smtp smuggling (%s)" , mox . ReceivedID ( c . cid ) ) , err )
return
}
2023-01-30 16:27:06 +03:00
// Something is failing on our side. We want to let remote know. So write an error response,
// then discard the remaining data so the remote client is more likely to see our
// response. Our write is synchronous, there is a risk no window/buffer space is
// available and our write blocks us from reading remaining data, leading to
// deadlock. We have a timeout on our connection writes though, so worst case we'll
// abort the connection due to expiration.
2023-02-25 15:07:32 +03:00
c . writecodeline ( smtp . C451LocalErr , smtp . SeSys3Other0 , fmt . Sprintf ( "error copying data to file (%s)" , mox . ReceivedID ( c . cid ) ) , err )
2023-01-30 16:27:06 +03:00
io . Copy ( io . Discard , dr )
return
}
// Basic sanity checks on messages before we send them out to the world. Just
// trying to be strict in what we do to others and liberal in what we accept.
if c . submission {
2023-08-11 15:07:49 +03:00
if ! msgWriter . HaveBody {
2023-01-30 16:27:06 +03:00
// ../rfc/6409:541
xsmtpUserErrorf ( smtp . C554TransactionFailed , smtp . SeMsg6Other0 , "message requires both header and body section" )
}
2023-03-12 17:16:01 +03:00
// Check only for pedantic mode because ios mail will attempt to send smtputf8 with
// non-ascii in message from localpart without using 8bitmime.
2023-12-05 23:13:57 +03:00
if mox . Pedantic && msgWriter . Has8bit && ! c . has8bitmime {
2023-01-30 16:27:06 +03:00
// ../rfc/5321:906
xsmtpUserErrorf ( smtp . C500BadSyntax , smtp . SeMsg6Other0 , "message with non-us-ascii requires 8bitmime extension" )
}
}
2023-12-05 23:13:57 +03:00
if Localserve && mox . Pedantic {
2023-03-12 23:42:28 +03:00
// Require that message can be parsed fully.
2023-12-05 15:35:58 +03:00
p , err := message . Parse ( c . log . Logger , false , dataFile )
2023-03-12 23:42:28 +03:00
if err == nil {
2023-12-05 15:35:58 +03:00
err = p . Walk ( c . log . Logger , nil )
2023-03-12 23:42:28 +03:00
}
if err != nil {
// ../rfc/6409:541
xsmtpUserErrorf ( smtp . C554TransactionFailed , smtp . SeMsg6Other0 , "malformed message: %v" , err )
}
}
2024-03-31 16:23:53 +03:00
// Now that we have all the whole message (envelope + data), we can check if the SMTPUTF8 extension is required.
var part * message . Part
if c . smtputf8 || c . submission || mox . Pedantic {
// Try to parse the message.
// Do nothing if something bad happen during Parse and Walk, just keep the current value for c.msgsmtputf8.
p , err := message . Parse ( c . log . Logger , true , dataFile )
if err == nil {
// Message parsed without error. Keep the result to avoid parsing the message again.
part = & p
err = part . Walk ( c . log . Logger , nil )
if err == nil {
c . msgsmtputf8 = c . isSMTPUTF8Required ( part )
}
}
2024-11-25 15:25:12 +03:00
if err != nil {
c . log . Debugx ( "parsing message for smtputf8 check" , err )
}
2024-03-31 16:23:53 +03:00
if c . smtputf8 != c . msgsmtputf8 {
c . log . Debug ( "smtputf8 flag changed" , slog . Bool ( "smtputf8" , c . smtputf8 ) , slog . Bool ( "msgsmtputf8" , c . msgsmtputf8 ) )
}
}
if ! c . smtputf8 && c . msgsmtputf8 && mox . Pedantic {
metricSubmission . WithLabelValues ( "missingsmtputf8" ) . Inc ( )
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SeMsg6Other0 , "smtputf8 extension is required but was not added to the MAIL command" )
}
2023-01-30 16:27:06 +03:00
// Prepare "Received" header.
// ../rfc/5321:2051 ../rfc/5321:3302
// ../rfc/5321:3311 ../rfc/6531:578
var recvFrom string
var iprevStatus iprev . Status // Only for delivery, not submission.
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
var iprevAuthentic bool
2023-01-30 16:27:06 +03:00
if c . submission {
// Hide internal hosts.
// todo future: make this a config option, where admins specify ip ranges that they don't want exposed. also see ../rfc/5321:4321
2024-03-31 16:23:53 +03:00
recvFrom = message . HeaderCommentDomain ( mox . Conf . Static . HostnameDomain , c . msgsmtputf8 )
2023-01-30 16:27:06 +03:00
} else {
if len ( c . hello . IP ) > 0 {
recvFrom = smtp . AddressLiteral ( c . hello . IP )
} else {
// ASCII-only version added after the extended-domain syntax below, because the
// comment belongs to "BY" which comes immediately after "FROM".
2024-03-31 16:23:53 +03:00
recvFrom = c . hello . Domain . XName ( c . msgsmtputf8 )
2023-01-30 16:27:06 +03:00
}
iprevctx , iprevcancel := context . WithTimeout ( cmdctx , time . Minute )
var revName string
var revNames [ ] string
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
iprevStatus , revName , revNames , iprevAuthentic , err = iprev . Lookup ( iprevctx , c . resolver , c . remoteIP )
2023-01-30 16:27:06 +03:00
iprevcancel ( )
if err != nil {
2023-12-05 15:35:58 +03:00
c . log . Infox ( "reverse-forward lookup" , err , slog . Any ( "remoteip" , c . remoteIP ) )
2023-01-30 16:27:06 +03:00
}
2023-12-05 15:35:58 +03:00
c . log . Debug ( "dns iprev check" , slog . Any ( "addr" , c . remoteIP ) , slog . Any ( "status" , iprevStatus ) )
2023-01-30 16:27:06 +03:00
var name string
if revName != "" {
name = revName
} else if len ( revNames ) > 0 {
name = revNames [ 0 ]
}
name = strings . TrimSuffix ( name , "." )
recvFrom += " ("
2024-03-31 16:23:53 +03:00
if name != "" && name != c . hello . Domain . XName ( c . msgsmtputf8 ) {
2023-01-30 16:27:06 +03:00
recvFrom += name + " "
}
recvFrom += smtp . AddressLiteral ( c . remoteIP ) + ")"
2024-03-31 16:23:53 +03:00
if c . msgsmtputf8 && c . hello . Domain . Unicode != "" {
2023-01-30 16:27:06 +03:00
recvFrom += " (" + c . hello . Domain . ASCII + ")"
}
}
2024-03-31 16:23:53 +03:00
recvBy := mox . Conf . Static . HostnameDomain . XName ( c . msgsmtputf8 )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
recvBy += " (" + smtp . AddressLiteral ( c . localIP ) + ")" // todo: hide ip if internal?
2024-03-31 16:23:53 +03:00
if c . msgsmtputf8 && mox . Conf . Static . HostnameDomain . Unicode != "" {
2023-01-30 16:27:06 +03:00
// This syntax is part of "VIA".
recvBy += " (" + mox . Conf . Static . HostnameDomain . ASCII + ")"
}
// ../rfc/3848:34 ../rfc/6531:791
with := "SMTP"
2024-03-31 16:23:53 +03:00
if c . msgsmtputf8 {
2023-01-30 16:27:06 +03:00
with = "UTF8SMTP"
} else if c . ehlo {
with = "ESMTP"
}
if c . tls {
with += "S"
}
if c . account != nil {
// ../rfc/4954:660
with += "A"
}
// Assume transaction does not succeed. If it does, we'll compensate.
c . transactionBad ++
recvHdrFor := func ( rcptTo string ) string {
recvHdr := & message . HeaderWriter { }
// For additional Received-header clauses, see:
// https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml#table-mail-parameters-8
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
withComment := ""
if c . requireTLS != nil && * c . requireTLS {
// Comment is actually part of ID ABNF rule. ../rfc/5321:3336
withComment = " (requiretls)"
}
recvHdr . Add ( " " , "Received:" , "from" , recvFrom , "by" , recvBy , "via" , "tcp" , "with" , with + withComment , "id" , mox . ReceivedID ( c . cid ) ) // ../rfc/5321:3158
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
if c . tls {
tlsConn := c . conn . ( * tls . Conn )
2023-12-05 15:35:58 +03:00
tlsComment := mox . TLSReceivedComment ( c . log , tlsConn . ConnectionState ( ) )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
recvHdr . Add ( " " , tlsComment ... )
}
queue: deliver to multiple recipients in a single smtp transaction
transferring the data only once. we only do this when the recipient domains
are the same. when queuing, we now take care to set the same NextAttempt
timestamp, so queued messages are actually eligable for combined delivery.
this adds a DeliverMultiple to the smtp client. for pipelined requests, it will
send all RCPT TO (and MAIL and DATA) in one go, and handles the various
responses and error conditions, returning either an overal error, or per
recipient smtp responses. the results of the smtp LIMITS extension are also
available in the smtp client now.
this also takes the "LIMITS RCPTMAX" smtp extension into account: if the server
only accepts a single recipient, we won't send multiple.
if a server doesn't announce a RCPTMAX limit, but still has one (like mox does
for non-spf-verified transactions), we'll recognize code 452 and 552 (for
historic reasons) as temporary error, and try again in a separate transaction
immediately after. we don't yet implement "LIMITS MAILMAX", doesn't seem likely
in practice.
2024-03-07 12:07:53 +03:00
// We leave out an empty "for" clause. This is empty for messages submitted to
// multiple recipients, so the message stays identical and a single smtp
// transaction can deliver, only transferring the data once.
if rcptTo != "" {
recvHdr . Add ( " " , "for" , "<" + rcptTo + ">;" )
}
recvHdr . Add ( " " , time . Now ( ) . Format ( message . RFC5322Z ) )
2023-01-30 16:27:06 +03:00
return recvHdr . String ( )
}
// Submission is easiest because user is trusted. Far fewer checks to make. So
// handle it first, and leave the rest of the function for handling wild west
// internet traffic.
if c . submission {
2024-03-31 16:23:53 +03:00
c . submit ( cmdctx , recvHdrFor , msgWriter , dataFile , part )
2023-01-30 16:27:06 +03:00
} else {
make mox compile on windows, without "mox serve" but with working "mox localserve"
getting mox to compile required changing code in only a few places where
package "syscall" was used: for accessing file access times and for umask
handling. an open problem is how to start a process as an unprivileged user on
windows. that's why "mox serve" isn't implemented yet. and just finding a way
to implement it now may not be good enough in the near future: we may want to
starting using a more complete privilege separation approach, with a process
handling sensitive tasks (handling private keys, authentication), where we may
want to pass file descriptors between processes. how would that work on
windows?
anyway, getting mox to compile for windows doesn't mean it works properly on
windows. the largest issue: mox would normally open a file, rename or remove
it, and finally close it. this happens during message delivery. that doesn't
work on windows, the rename/remove would fail because the file is still open.
so this commit swaps many "remove" and "close" calls. renames are a longer
story: message delivery had two ways to deliver: with "consuming" the
(temporary) message file (which would rename it to its final destination), and
without consuming (by hardlinking the file, falling back to copying). the last
delivery to a recipient of a message (and the only one in the common case of a
single recipient) would consume the message, and the earlier recipients would
not. during delivery, the already open message file was used, to parse the
message. we still want to use that open message file, and the caller now stays
responsible for closing it, but we no longer try to rename (consume) the file.
we always hardlink (or copy) during delivery (this works on windows), and the
caller is responsible for closing and removing (in that order) the original
temporary file. this does cost one syscall more. but it makes the delivery code
(responsibilities) a bit simpler.
there is one more obvious issue: the file system path separator. mox already
used the "filepath" package to join paths in many places, but not everywhere.
and it still used strings with slashes for local file access. with this commit,
the code now uses filepath.FromSlash for path strings with slashes, uses
"filepath" in a few more places where it previously didn't. also switches from
"filepath" to regular "path" package when handling mailbox names in a few
places, because those always use forward slashes, regardless of local file
system conventions. windows can handle forward slashes when opening files, so
test code that passes path strings with forward slashes straight to go stdlib
file i/o functions are left unchanged to reduce code churn. the regular
non-test code, or test code that uses path strings in places other than
standard i/o functions, does have the paths converted for consistent paths
(otherwise we would end up with paths with mixed forward/backward slashes in
log messages).
windows cannot dup a listening socket. for "mox localserve", it isn't
important, and we can work around the issue. the current approach for "mox
serve" (forking a process and passing file descriptors of listening sockets on
"privileged" ports) won't work on windows. perhaps it isn't needed on windows,
and any user can listen on "privileged" ports? that would be welcome.
on windows, os.Open cannot open a directory, so we cannot call Sync on it after
message delivery. a cursory internet search indicates that directories cannot
be synced on windows. the story is probably much more nuanced than that, with
long deep technical details/discussions/disagreement/confusion, like on unix.
for "mox localserve" we can get away with making syncdir a no-op.
2023-10-14 11:54:07 +03:00
c . deliver ( cmdctx , recvHdrFor , msgWriter , iprevStatus , iprevAuthentic , dataFile )
2023-01-30 16:27:06 +03:00
}
}
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
// Check if a message has unambiguous "TLS-Required: No" header. Messages must not
// contain multiple TLS-Required headers. The only valid value is "no". But we'll
// accept multiple headers as long as all they are all "no".
// ../rfc/8689:223
func hasTLSRequiredNo ( h textproto . MIMEHeader ) bool {
l := h . Values ( "Tls-Required" )
if len ( l ) == 0 {
return false
}
for _ , v := range l {
if ! strings . EqualFold ( v , "no" ) {
return false
}
}
return true
}
2023-03-29 22:11:43 +03:00
// submit is used for mail from authenticated users that we will try to deliver.
2024-03-31 16:23:53 +03:00
func ( c * conn ) submit ( ctx context . Context , recvHdrFor func ( string ) string , msgWriter * message . Writer , dataFile * os . File , part * message . Part ) {
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
// Similar between ../smtpserver/server.go:/submit\( and ../webmail/api.go:/MessageSubmit\( and ../webapisrv/server.go:/Send\(
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
2023-01-30 16:27:06 +03:00
var msgPrefix [ ] byte
// Check that user is only sending email as one of its configured identities. Not
// for other users.
2023-08-10 13:18:05 +03:00
// We don't check the Sender field, there is no expectation of verification, ../rfc/7489:2948
// and with Resent headers it seems valid to have someone else as Sender. ../rfc/5322:1578
2024-03-31 16:23:53 +03:00
msgFrom , _ , header , err := message . From ( c . log . Logger , true , dataFile , part )
2023-01-30 16:27:06 +03:00
if err != nil {
metricSubmission . WithLabelValues ( "badmessage" ) . Inc ( )
2023-12-05 15:35:58 +03:00
c . log . Infox ( "parsing message From address" , err , slog . String ( "user" , c . username ) )
2023-01-30 16:27:06 +03:00
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SeMsg6Other0 , "cannot parse header or From address: %v" , err )
}
2024-04-24 20:15:30 +03:00
if ! mox . AllowMsgFrom ( c . account . Name , msgFrom ) {
2023-01-30 16:27:06 +03:00
// ../rfc/6409:522
metricSubmission . WithLabelValues ( "badfrom" ) . Inc ( )
2024-04-24 20:15:30 +03:00
c . log . Infox ( "verifying message from address" , mox . ErrAddressNotFound , slog . String ( "user" , c . username ) , slog . Any ( "msgfrom" , msgFrom ) )
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SePol7DeliveryUnauth1 , "message from address must belong to authenticated user" )
2023-01-30 16:27:06 +03:00
}
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
// TLS-Required: No header makes us not enforce recipient domain's TLS policy.
// ../rfc/8689:206
// Only when requiretls smtp extension wasn't used. ../rfc/8689:246
if c . requireTLS == nil && hasTLSRequiredNo ( header ) {
v := false
c . requireTLS = & v
}
2023-01-30 16:27:06 +03:00
// Outgoing messages should not have a Return-Path header. The final receiving mail
// server will add it.
// ../rfc/5321:3233
2023-12-20 23:01:47 +03:00
if mox . Pedantic && header . Values ( "Return-Path" ) != nil {
2023-01-30 16:27:06 +03:00
metricSubmission . WithLabelValues ( "badheader" ) . Inc ( )
2023-12-20 23:01:47 +03:00
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SeMsg6Other0 , "message should not have Return-Path header" )
2023-01-30 16:27:06 +03:00
}
// Add Message-Id header if missing.
// ../rfc/5321:4131 ../rfc/6409:751
2023-07-23 18:56:39 +03:00
messageID := header . Get ( "Message-Id" )
if messageID == "" {
2024-03-31 16:23:53 +03:00
messageID = mox . MessageIDGen ( c . msgsmtputf8 )
2023-07-23 18:56:39 +03:00
msgPrefix = append ( msgPrefix , fmt . Sprintf ( "Message-Id: <%s>\r\n" , messageID ) ... )
2023-01-30 16:27:06 +03:00
}
// ../rfc/6409:745
if header . Get ( "Date" ) == "" {
msgPrefix = append ( msgPrefix , "Date: " + time . Now ( ) . Format ( message . RFC5322Z ) + "\r\n" ... )
}
2024-04-24 12:35:07 +03:00
// Check outgoing message rate limit.
2023-05-22 15:40:36 +03:00
err = c . account . DB . Read ( ctx , func ( tx * bstore . Tx ) error {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
rcpts := make ( [ ] smtp . Path , len ( c . recipients ) )
for i , r := range c . recipients {
2024-11-01 12:38:31 +03:00
rcpts [ i ] = r . Addr
2023-03-28 21:50:36 +03:00
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
msglimit , rcptlimit , err := c . account . SendLimitReached ( tx , rcpts )
xcheckf ( err , "checking sender limit" )
if msglimit >= 0 {
metricSubmission . WithLabelValues ( "messagelimiterror" ) . Inc ( )
xsmtpUserErrorf ( smtp . C451LocalErr , smtp . SePol7DeliveryUnauth1 , "max number of messages (%d) over past 24h reached, try increasing per-account setting MaxOutgoingMessagesPerDay" , msglimit )
} else if rcptlimit >= 0 {
2023-03-28 21:50:36 +03:00
metricSubmission . WithLabelValues ( "recipientlimiterror" ) . Inc ( )
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
xsmtpUserErrorf ( smtp . C451LocalErr , smtp . SePol7DeliveryUnauth1 , "max number of new/first-time recipients (%d) over past 24h reached, try increasing per-account setting MaxFirstTimeRecipientsPerDay" , rcptlimit )
2023-03-28 21:50:36 +03:00
}
return nil
} )
xcheckf ( err , "read-only transaction" )
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
// We gather any X-Mox-Extra-* headers into the "extra" data during queueing, which
// will make it into any webhook we deliver.
// todo: remove the X-Mox-Extra-* headers from the message. we don't currently rewrite the message...
// todo: should we not canonicalize keys?
var extra map [ string ] string
for k , vl := range header {
if ! strings . HasPrefix ( k , "X-Mox-Extra-" ) {
continue
}
if extra == nil {
extra = map [ string ] string { }
}
xk := k [ len ( "X-Mox-Extra-" ) : ]
// We don't allow duplicate keys.
if _ , ok := extra [ xk ] ; ok || len ( vl ) > 1 {
xsmtpUserErrorf ( smtp . C554TransactionFailed , smtp . SeMsg6Other0 , "duplicate x-mox-extra- key %q" , xk )
}
extra [ xk ] = vl [ len ( vl ) - 1 ]
}
2023-01-30 16:27:06 +03:00
// todo future: in a pedantic mode, we can parse the headers, and return an error if rcpt is only in To or Cc header, and not in the non-empty Bcc header. indicates a client that doesn't blind those bcc's.
// Add DKIM signatures.
2023-03-30 11:38:36 +03:00
confDom , ok := mox . Conf . Domain ( msgFrom . Domain )
2023-01-30 16:27:06 +03:00
if ! ok {
2023-12-05 15:35:58 +03:00
c . log . Error ( "domain disappeared" , slog . Any ( "domain" , msgFrom . Domain ) )
2023-01-30 16:27:06 +03:00
xsmtpServerErrorf ( codes { smtp . C451LocalErr , smtp . SeSys3Other0 } , "internal error" )
}
2023-12-05 23:13:57 +03:00
selectors := mox . DKIMSelectors ( confDom . DKIM )
if len ( selectors ) > 0 {
2024-04-24 20:15:30 +03:00
canonical := mox . CanonicalLocalpart ( msgFrom . Localpart , confDom )
if dkimHeaders , err := dkim . Sign ( ctx , c . log . Logger , canonical , msgFrom . Domain , selectors , c . msgsmtputf8 , store . FileMsgReader ( msgPrefix , dataFile ) ) ; err != nil {
2023-12-05 15:35:58 +03:00
c . log . Errorx ( "dkim sign for domain" , err , slog . Any ( "domain" , msgFrom . Domain ) )
2023-01-30 16:27:06 +03:00
metricServerErrors . WithLabelValues ( "dkimsign" ) . Inc ( )
} else {
msgPrefix = append ( msgPrefix , [ ] byte ( dkimHeaders ) ... )
}
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
authResults := message . AuthResults {
2024-03-31 16:23:53 +03:00
Hostname : mox . Conf . Static . HostnameDomain . XName ( c . msgsmtputf8 ) ,
Comment : mox . Conf . Static . HostnameDomain . ASCIIExtra ( c . msgsmtputf8 ) ,
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
Methods : [ ] message . AuthMethod {
2023-01-30 16:27:06 +03:00
{
Method : "auth" ,
Result : "pass" ,
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
Props : [ ] message . AuthProp {
2024-03-31 16:23:53 +03:00
message . MakeAuthProp ( "smtp" , "mailfrom" , c . mailFrom . XString ( c . msgsmtputf8 ) , true , c . mailFrom . ASCIIExtra ( c . msgsmtputf8 ) ) ,
2023-01-30 16:27:06 +03:00
} ,
} ,
} ,
}
msgPrefix = append ( msgPrefix , [ ] byte ( authResults . Header ( ) ) ... )
2023-08-10 12:28:57 +03:00
// We always deliver through the queue. It would be more efficient to deliver
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
// directly for local accounts, but we don't want to circumvent all the anti-spam
// measures. Accounts on a single mox instance should be allowed to block each
// other.
accConf , _ := c . account . Conf ( )
loginAddr , err := smtp . ParseAddress ( c . username )
xcheckf ( err , "parsing login address" )
useFromID := slices . Contains ( accConf . ParsedFromIDLoginAddresses , loginAddr )
var localpartBase string
2024-04-28 14:18:25 +03:00
var fromID string
var genFromID bool
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
if useFromID {
2024-04-28 14:18:25 +03:00
// With submission, user can bring their own fromid.
t := strings . SplitN ( string ( c . mailFrom . Localpart ) , confDom . LocalpartCatchallSeparator , 2 )
localpartBase = t [ 0 ]
if len ( t ) == 2 {
fromID = t [ 1 ]
if fromID != "" && len ( c . recipients ) > 1 {
xsmtpServerErrorf ( codes { smtp . C554TransactionFailed , smtp . SeProto5TooManyRcpts3 } , "cannot send to multiple recipients with chosen fromid" )
}
} else {
genFromID = true
}
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
}
queue: deliver to multiple recipients in a single smtp transaction
transferring the data only once. we only do this when the recipient domains
are the same. when queuing, we now take care to set the same NextAttempt
timestamp, so queued messages are actually eligable for combined delivery.
this adds a DeliverMultiple to the smtp client. for pipelined requests, it will
send all RCPT TO (and MAIL and DATA) in one go, and handles the various
responses and error conditions, returning either an overal error, or per
recipient smtp responses. the results of the smtp LIMITS extension are also
available in the smtp client now.
this also takes the "LIMITS RCPTMAX" smtp extension into account: if the server
only accepts a single recipient, we won't send multiple.
if a server doesn't announce a RCPTMAX limit, but still has one (like mox does
for non-spf-verified transactions), we'll recognize code 452 and 552 (for
historic reasons) as temporary error, and try again in a separate transaction
immediately after. we don't yet implement "LIMITS MAILMAX", doesn't seem likely
in practice.
2024-03-07 12:07:53 +03:00
now := time . Now ( )
2024-03-05 22:10:28 +03:00
qml := make ( [ ] queue . Msg , len ( c . recipients ) )
2024-04-24 20:15:30 +03:00
for i , rcpt := range c . recipients {
2023-08-10 12:28:57 +03:00
if Localserve {
2024-11-01 12:38:31 +03:00
code , timeout := mox . LocalserveNeedsError ( rcpt . Addr . Localpart )
2023-08-10 12:28:57 +03:00
if timeout {
c . log . Info ( "timing out submission due to special localpart" )
mox . Sleep ( mox . Context , time . Hour )
xsmtpServerErrorf ( codes { smtp . C451LocalErr , smtp . SeSys3Other0 } , "timing out submission due to special localpart" )
} else if code != 0 {
2023-12-05 15:35:58 +03:00
c . log . Info ( "failure due to special localpart" , slog . Int ( "code" , code ) )
2023-08-10 12:28:57 +03:00
xsmtpServerErrorf ( codes { code , smtp . SeOther00 } , "failure with code %d due to special localpart" , code )
2023-03-12 12:38:02 +03:00
}
2023-01-30 16:27:06 +03:00
}
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
fp := * c . mailFrom
if useFromID {
2024-04-28 14:18:25 +03:00
if genFromID {
fromID = xrandomID ( 16 )
}
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
fp . Localpart = smtp . Localpart ( localpartBase + confDom . LocalpartCatchallSeparator + fromID )
}
queue: deliver to multiple recipients in a single smtp transaction
transferring the data only once. we only do this when the recipient domains
are the same. when queuing, we now take care to set the same NextAttempt
timestamp, so queued messages are actually eligable for combined delivery.
this adds a DeliverMultiple to the smtp client. for pipelined requests, it will
send all RCPT TO (and MAIL and DATA) in one go, and handles the various
responses and error conditions, returning either an overal error, or per
recipient smtp responses. the results of the smtp LIMITS extension are also
available in the smtp client now.
this also takes the "LIMITS RCPTMAX" smtp extension into account: if the server
only accepts a single recipient, we won't send multiple.
if a server doesn't announce a RCPTMAX limit, but still has one (like mox does
for non-spf-verified transactions), we'll recognize code 452 and 552 (for
historic reasons) as temporary error, and try again in a separate transaction
immediately after. we don't yet implement "LIMITS MAILMAX", doesn't seem likely
in practice.
2024-03-07 12:07:53 +03:00
// For multiple recipients, we don't make each message prefix unique, leaving out
// the "for" clause in the Received header. This allows the queue to deliver the
// messages in a single smtp transaction.
var rcptTo string
if len ( c . recipients ) == 1 {
2024-11-01 12:38:31 +03:00
rcptTo = rcpt . Addr . String ( )
queue: deliver to multiple recipients in a single smtp transaction
transferring the data only once. we only do this when the recipient domains
are the same. when queuing, we now take care to set the same NextAttempt
timestamp, so queued messages are actually eligable for combined delivery.
this adds a DeliverMultiple to the smtp client. for pipelined requests, it will
send all RCPT TO (and MAIL and DATA) in one go, and handles the various
responses and error conditions, returning either an overal error, or per
recipient smtp responses. the results of the smtp LIMITS extension are also
available in the smtp client now.
this also takes the "LIMITS RCPTMAX" smtp extension into account: if the server
only accepts a single recipient, we won't send multiple.
if a server doesn't announce a RCPTMAX limit, but still has one (like mox does
for non-spf-verified transactions), we'll recognize code 452 and 552 (for
historic reasons) as temporary error, and try again in a separate transaction
immediately after. we don't yet implement "LIMITS MAILMAX", doesn't seem likely
in practice.
2024-03-07 12:07:53 +03:00
}
xmsgPrefix := append ( [ ] byte ( recvHdrFor ( rcptTo ) ) , msgPrefix ... )
2023-08-10 12:28:57 +03:00
msgSize := int64 ( len ( xmsgPrefix ) ) + msgWriter . Size
2024-11-01 12:38:31 +03:00
qm := queue . MakeMsg ( fp , rcpt . Addr , msgWriter . Has8bit , c . msgsmtputf8 , msgSize , messageID , xmsgPrefix , c . requireTLS , now , header . Get ( "Subject" ) )
2024-02-10 19:55:56 +03:00
if ! c . futureRelease . IsZero ( ) {
qm . NextAttempt = c . futureRelease
qm . FutureReleaseRequest = c . futureReleaseRequest
}
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
qm . FromID = fromID
qm . Extra = extra
2024-03-05 22:10:28 +03:00
qml [ i ] = qm
}
// todo: it would be good to have a limit on messages (count and total size) a user has in the queue. also/especially with futurerelease. ../rfc/4865:387
2024-04-28 14:18:25 +03:00
if err := queue . Add ( ctx , c . log , c . account . Name , dataFile , qml ... ) ; err != nil && errors . Is ( err , queue . ErrFromID ) && ! genFromID {
// todo: should we return this error during the "rcpt to" command?
// secode is not an exact match, but seems closest.
xsmtpServerErrorf ( errCodes ( smtp . C554TransactionFailed , smtp . SeAddr1SenderSyntax7 , err ) , "bad fromid in smtp mail from address: %s" , err )
} else if err != nil {
2024-03-05 22:10:28 +03:00
// Aborting the transaction is not great. But continuing and generating DSNs will
// probably result in errors as well...
metricSubmission . WithLabelValues ( "queueerror" ) . Inc ( )
c . log . Errorx ( "queuing message" , err )
xsmtpServerErrorf ( errCodes ( smtp . C451LocalErr , smtp . SeSys3Other0 , err ) , "error delivering message: %v" , err )
}
metricSubmission . WithLabelValues ( "ok" ) . Inc ( )
2024-04-24 20:15:30 +03:00
for i , rcpt := range c . recipients {
2024-03-05 22:10:28 +03:00
c . log . Info ( "messages queued for delivery" ,
2023-12-05 18:06:50 +03:00
slog . Any ( "mailfrom" , * c . mailFrom ) ,
2024-11-01 12:38:31 +03:00
slog . Any ( "rcptto" , rcpt . Addr ) ,
2023-12-05 18:06:50 +03:00
slog . Bool ( "smtputf8" , c . smtputf8 ) ,
2024-03-31 16:23:53 +03:00
slog . Bool ( "msgsmtputf8" , c . msgsmtputf8 ) ,
2024-03-05 22:10:28 +03:00
slog . Int64 ( "msgsize" , qml [ i ] . Size ) )
2023-01-30 16:27:06 +03:00
}
2023-08-10 12:28:57 +03:00
2024-03-05 22:10:28 +03:00
err = c . account . DB . Write ( ctx , func ( tx * bstore . Tx ) error {
2024-04-24 20:15:30 +03:00
for _ , rcpt := range c . recipients {
2024-11-01 12:38:31 +03:00
outgoing := store . Outgoing { Recipient : rcpt . Addr . XString ( true ) }
2024-03-05 22:10:28 +03:00
if err := tx . Insert ( & outgoing ) ; err != nil {
return fmt . Errorf ( "adding outgoing message: %v" , err )
}
}
return nil
} )
xcheckf ( err , "adding outgoing messages" )
2023-01-30 16:27:06 +03:00
c . transactionGood ++
c . transactionBad -- // Compensate for early earlier pessimistic increase.
c . rset ( )
c . writecodeline ( smtp . C250Completed , smtp . SeMailbox2Other0 , "it is done" , nil )
}
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
func xrandomID ( n int ) string {
return base64 . RawURLEncoding . EncodeToString ( xrandom ( n ) )
}
func xrandom ( n int ) [ ] byte {
buf := make ( [ ] byte , n )
x , err := cryptorand . Read ( buf )
xcheckf ( err , "read random" )
if x != n {
xcheckf ( errors . New ( "short random read" ) , "read random" )
}
return buf
}
2023-03-12 12:38:02 +03:00
func ipmasked ( ip net . IP ) ( string , string , string ) {
if ip . To4 ( ) != nil {
m1 := ip . String ( )
m2 := ip . Mask ( net . CIDRMask ( 26 , 32 ) ) . String ( )
m3 := ip . Mask ( net . CIDRMask ( 21 , 32 ) ) . String ( )
return m1 , m2 , m3
}
m1 := ip . Mask ( net . CIDRMask ( 64 , 128 ) ) . String ( )
m2 := ip . Mask ( net . CIDRMask ( 48 , 128 ) ) . String ( )
m3 := ip . Mask ( net . CIDRMask ( 32 , 128 ) ) . String ( )
return m1 , m2 , m3
}
func ( c * conn ) xlocalserveError ( lp smtp . Localpart ) {
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
code , timeout := mox . LocalserveNeedsError ( lp )
2023-03-12 12:38:02 +03:00
if timeout {
c . log . Info ( "timing out due to special localpart" )
mox . Sleep ( mox . Context , time . Hour )
xsmtpServerErrorf ( codes { smtp . C451LocalErr , smtp . SeSys3Other0 } , "timing out command due to special localpart" )
} else if code != 0 {
2023-12-05 15:35:58 +03:00
c . log . Info ( "failure due to special localpart" , slog . Int ( "code" , code ) )
2023-03-12 12:38:02 +03:00
metricDelivery . WithLabelValues ( "delivererror" , "localserve" ) . Inc ( )
xsmtpServerErrorf ( codes { code , smtp . SeOther00 } , "failure with code %d due to special localpart" , code )
}
}
2023-01-30 16:27:06 +03:00
// deliver is called for incoming messages from external, typically untrusted
// sources. i.e. not submitted by authenticated users.
make mox compile on windows, without "mox serve" but with working "mox localserve"
getting mox to compile required changing code in only a few places where
package "syscall" was used: for accessing file access times and for umask
handling. an open problem is how to start a process as an unprivileged user on
windows. that's why "mox serve" isn't implemented yet. and just finding a way
to implement it now may not be good enough in the near future: we may want to
starting using a more complete privilege separation approach, with a process
handling sensitive tasks (handling private keys, authentication), where we may
want to pass file descriptors between processes. how would that work on
windows?
anyway, getting mox to compile for windows doesn't mean it works properly on
windows. the largest issue: mox would normally open a file, rename or remove
it, and finally close it. this happens during message delivery. that doesn't
work on windows, the rename/remove would fail because the file is still open.
so this commit swaps many "remove" and "close" calls. renames are a longer
story: message delivery had two ways to deliver: with "consuming" the
(temporary) message file (which would rename it to its final destination), and
without consuming (by hardlinking the file, falling back to copying). the last
delivery to a recipient of a message (and the only one in the common case of a
single recipient) would consume the message, and the earlier recipients would
not. during delivery, the already open message file was used, to parse the
message. we still want to use that open message file, and the caller now stays
responsible for closing it, but we no longer try to rename (consume) the file.
we always hardlink (or copy) during delivery (this works on windows), and the
caller is responsible for closing and removing (in that order) the original
temporary file. this does cost one syscall more. but it makes the delivery code
(responsibilities) a bit simpler.
there is one more obvious issue: the file system path separator. mox already
used the "filepath" package to join paths in many places, but not everywhere.
and it still used strings with slashes for local file access. with this commit,
the code now uses filepath.FromSlash for path strings with slashes, uses
"filepath" in a few more places where it previously didn't. also switches from
"filepath" to regular "path" package when handling mailbox names in a few
places, because those always use forward slashes, regardless of local file
system conventions. windows can handle forward slashes when opening files, so
test code that passes path strings with forward slashes straight to go stdlib
file i/o functions are left unchanged to reduce code churn. the regular
non-test code, or test code that uses path strings in places other than
standard i/o functions, does have the paths converted for consistent paths
(otherwise we would end up with paths with mixed forward/backward slashes in
log messages).
windows cannot dup a listening socket. for "mox localserve", it isn't
important, and we can work around the issue. the current approach for "mox
serve" (forking a process and passing file descriptors of listening sockets on
"privileged" ports) won't work on windows. perhaps it isn't needed on windows,
and any user can listen on "privileged" ports? that would be welcome.
on windows, os.Open cannot open a directory, so we cannot call Sync on it after
message delivery. a cursory internet search indicates that directories cannot
be synced on windows. the story is probably much more nuanced than that, with
long deep technical details/discussions/disagreement/confusion, like on unix.
for "mox localserve" we can get away with making syncdir a no-op.
2023-10-14 11:54:07 +03:00
func ( c * conn ) deliver ( ctx context . Context , recvHdrFor func ( string ) string , msgWriter * message . Writer , iprevStatus iprev . Status , iprevAuthentic bool , dataFile * os . File ) {
2023-01-30 16:27:06 +03:00
// todo: in decision making process, if we run into (some) temporary errors, attempt to continue. if we decide to accept, all good. if we decide to reject, we'll make it a temporary reject.
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
var msgFrom smtp . Address
var envelope * message . Envelope
var headers textproto . MIMEHeader
var isDSN bool
part , err := message . Parse ( c . log . Logger , false , dataFile )
if err == nil {
// todo: is it enough to check only the the content-type header? in other places we look at the content-types of the parts before considering a message a dsn. should we change other places to this simpler check?
isDSN = part . MediaType == "MULTIPART" && part . MediaSubType == "REPORT" && strings . EqualFold ( part . ContentTypeParams [ "report-type" ] , "delivery-status" )
msgFrom , envelope , headers , err = message . From ( c . log . Logger , false , dataFile , & part )
}
2023-01-30 16:27:06 +03:00
if err != nil {
c . log . Infox ( "parsing message for From address" , err )
}
// Basic loop detection. ../rfc/5321:4065 ../rfc/5321:1526
if len ( headers . Values ( "Received" ) ) > 100 {
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SeNet4Loop6 , "loop detected, more than 100 Received headers" )
}
implement "requiretls", rfc 8689
with requiretls, the tls verification mode/rules for email deliveries can be
changed by the sender/submitter. in two ways:
1. "requiretls" smtp extension to always enforce verified tls (with mta-sts or
dnssec+dane), along the entire delivery path until delivery into the final
destination mailbox (so entire transport is verified-tls-protected).
2. "tls-required: no" message header, to ignore any tls and tls verification
errors even if the recipient domain has a policy that requires tls verification
(mta-sts and/or dnssec+dane), allowing delivery of non-sensitive messages in
case of misconfiguration/interoperability issues (at least useful for sending
tls reports).
we enable requiretls by default (only when tls is active), for smtp and
submission. it can be disabled through the config.
for each delivery attempt, we now store (per recipient domain, in the account
of the sender) whether the smtp server supports starttls and requiretls. this
support is shown (after having sent a first message) in the webmail when
sending a message (the previous 3 bars under the address input field are now 5
bars, the first for starttls support, the last for requiretls support). when
all recipient domains for a message are known to implement requiretls,
requiretls is automatically selected for sending (instead of "default" tls
behaviour). users can also select the "fallback to insecure" to add the
"tls-required: no" header.
new metrics are added for insight into requiretls errors and (some, not yet
all) cases where tls-required-no ignored a tls/verification error.
the admin can change the requiretls status for messages in the queue. so with
default delivery attempts, when verified tls is required by failing, an admin
could potentially change the field to "tls-required: no"-behaviour.
messages received (over smtp) with the requiretls option, get a comment added
to their Received header line, just before "id", after "with".
2023-10-24 11:06:16 +03:00
// TLS-Required: No header makes us not enforce recipient domain's TLS policy.
// Since we only deliver locally at the moment, this won't influence our behaviour.
// Once we forward, it would our delivery attempts.
// ../rfc/8689:206
// Only when requiretls smtp extension wasn't used. ../rfc/8689:246
if c . requireTLS == nil && hasTLSRequiredNo ( headers ) {
v := false
c . requireTLS = & v
}
2023-01-30 16:27:06 +03:00
// We'll be building up an Authentication-Results header.
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
authResults := message . AuthResults {
2024-03-31 16:23:53 +03:00
Hostname : mox . Conf . Static . HostnameDomain . XName ( c . msgsmtputf8 ) ,
2023-01-30 16:27:06 +03:00
}
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
commentAuthentic := func ( v bool ) string {
if v {
return "with dnssec"
}
return "without dnssec"
}
2023-01-30 16:27:06 +03:00
// Reverse IP lookup results.
// todo future: how useful is this?
// ../rfc/5321:2481
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
authResults . Methods = append ( authResults . Methods , message . AuthMethod {
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
Method : "iprev" ,
Result : string ( iprevStatus ) ,
Comment : commentAuthentic ( iprevAuthentic ) ,
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
Props : [ ] message . AuthProp {
message . MakeAuthProp ( "policy" , "iprev" , c . remoteIP . String ( ) , false , "" ) ,
2023-01-30 16:27:06 +03:00
} ,
} )
// SPF and DKIM verification in parallel.
var wg sync . WaitGroup
// DKIM
wg . Add ( 1 )
var dkimResults [ ] dkim . Result
var dkimErr error
go func ( ) {
defer func ( ) {
2023-02-16 15:22:00 +03:00
x := recover ( ) // Should not happen, but don't take program down if it does.
if x != nil {
2023-12-05 15:35:58 +03:00
c . log . Error ( "dkim verify panic" , slog . Any ( "err" , x ) )
2023-02-16 15:22:00 +03:00
debug . PrintStack ( )
2023-09-15 17:47:17 +03:00
metrics . PanicInc ( metrics . Dkimverify )
2023-02-16 15:22:00 +03:00
}
2023-01-30 16:27:06 +03:00
} ( )
defer wg . Done ( )
// We always evaluate all signatures. We want to build up reputation for each
// domain in the signature.
const ignoreTestMode = false
// todo future: longer timeout? we have to read through the entire email, which can be large, possibly multiple times.
dkimctx , dkimcancel := context . WithTimeout ( ctx , time . Minute )
defer dkimcancel ( )
// todo future: we could let user configure which dkim headers they require
2024-04-24 12:35:07 +03:00
// For localserve, fake dkim selector DNS records for hosted domains to give
// dkim-signatures a chance to pass for deliveries from queue.
resolver := c . resolver
if Localserve {
// Lookup based on message From address is an approximation.
if dc , ok := mox . Conf . Domain ( msgFrom . Domain ) ; ok && len ( dc . DKIM . Selectors ) > 0 {
txts := map [ string ] [ ] string { }
for name , sel := range dc . DKIM . Selectors {
dkimr := dkim . Record {
Version : "DKIM1" ,
Hashes : [ ] string { sel . HashEffective } ,
PublicKey : sel . Key . Public ( ) ,
}
if _ , ok := sel . Key . ( ed25519 . PrivateKey ) ; ok {
dkimr . Key = "ed25519"
} else if _ , ok := sel . Key . ( * rsa . PrivateKey ) ; ! ok {
err := fmt . Errorf ( "unrecognized private key for DKIM selector %q: %T" , name , sel . Key )
xcheckf ( err , "making dkim record" )
}
txt , err := dkimr . Record ( )
xcheckf ( err , "making DKIM DNS TXT record" )
txts [ name + "._domainkey." + msgFrom . Domain . ASCII + "." ] = [ ] string { txt }
}
resolver = dns . MockResolver { TXT : txts }
}
}
dkimResults , dkimErr = dkim . Verify ( dkimctx , c . log . Logger , resolver , c . msgsmtputf8 , dkim . DefaultPolicy , dataFile , ignoreTestMode )
2023-01-30 16:27:06 +03:00
dkimcancel ( )
} ( )
// SPF.
// ../rfc/7208:472
var receivedSPF spf . Received
var spfDomain dns . Domain
var spfExpl string
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
var spfAuthentic bool
2023-01-30 16:27:06 +03:00
var spfErr error
spfArgs := spf . Args {
RemoteIP : c . remoteIP ,
MailFromLocalpart : c . mailFrom . Localpart ,
MailFromDomain : c . mailFrom . IPDomain . Domain , // Can be empty.
HelloDomain : c . hello ,
LocalIP : c . localIP ,
LocalHostname : c . hostname ,
}
wg . Add ( 1 )
go func ( ) {
defer func ( ) {
2023-02-16 15:22:00 +03:00
x := recover ( ) // Should not happen, but don't take program down if it does.
if x != nil {
2023-12-05 15:35:58 +03:00
c . log . Error ( "spf verify panic" , slog . Any ( "err" , x ) )
2023-02-16 15:22:00 +03:00
debug . PrintStack ( )
2023-09-15 17:47:17 +03:00
metrics . PanicInc ( metrics . Spfverify )
2023-02-16 15:22:00 +03:00
}
2023-01-30 16:27:06 +03:00
} ( )
defer wg . Done ( )
spfctx , spfcancel := context . WithTimeout ( ctx , time . Minute )
defer spfcancel ( )
2024-04-24 12:35:07 +03:00
resolver := c . resolver
// For localserve, give hosted domains a chance to pass for deliveries from queue.
if Localserve && c . remoteIP . IsLoopback ( ) {
// Lookup based on message From address is an approximation.
if _ , ok := mox . Conf . Domain ( msgFrom . Domain ) ; ok {
resolver = dns . MockResolver {
TXT : map [ string ] [ ] string { msgFrom . Domain . ASCII + "." : { "v=spf1 ip4:127.0.0.1/8 ip6:::1 ~all" } } ,
}
}
}
receivedSPF , spfDomain , spfExpl , spfAuthentic , spfErr = spf . Verify ( spfctx , c . log . Logger , resolver , spfArgs )
2023-01-30 16:27:06 +03:00
spfcancel ( )
if spfErr != nil {
c . log . Infox ( "spf verify" , spfErr )
}
} ( )
// Wait for DKIM and SPF validation to finish.
wg . Wait ( )
// Give immediate response if all recipients are unknown.
nunknown := 0
for _ , r := range c . recipients {
2024-11-01 12:38:31 +03:00
if r . Account == nil && r . Alias == nil {
2023-01-30 16:27:06 +03:00
nunknown ++
}
}
if nunknown == len ( c . recipients ) {
// During RCPT TO we found that the address does not exist.
2023-12-05 15:35:58 +03:00
c . log . Info ( "deliver attempt to unknown user(s)" , slog . Any ( "recipients" , c . recipients ) )
2023-01-30 16:27:06 +03:00
// Crude attempt to slow down someone trying to guess names. Would work better
// with connection rate limiter.
2023-03-10 12:23:43 +03:00
if unknownRecipientsDelay > 0 {
mox . Sleep ( ctx , unknownRecipientsDelay )
}
2023-01-30 16:27:06 +03:00
// todo future: if remote does not look like a properly configured mail system, respond with generic 451 error? to prevent any random internet system from discovering accounts. we could give proper response if spf for ehlo or mailfrom passes.
xsmtpUserErrorf ( smtp . C550MailboxUnavail , smtp . SeAddr1UnknownDestMailbox1 , "no such user(s)" )
}
// Add DKIM results to Authentication-Results header.
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
authResAddDKIM := func ( result , comment , reason string , props [ ] message . AuthProp ) {
dm := message . AuthMethod {
2023-01-30 16:27:06 +03:00
Method : "dkim" ,
Result : result ,
Comment : comment ,
Reason : reason ,
Props : props ,
}
authResults . Methods = append ( authResults . Methods , dm )
}
if dkimErr != nil {
c . log . Errorx ( "dkim verify" , dkimErr )
authResAddDKIM ( "none" , "" , dkimErr . Error ( ) , nil )
} else if len ( dkimResults ) == 0 {
2023-12-05 15:35:58 +03:00
c . log . Info ( "no dkim-signature header" , slog . Any ( "mailfrom" , c . mailFrom ) )
2023-01-30 16:27:06 +03:00
authResAddDKIM ( "none" , "" , "no dkim signatures" , nil )
}
for i , r := range dkimResults {
var domain , selector dns . Domain
var identity * dkim . Identity
var comment string
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var props [ ] message . AuthProp
2023-01-30 16:27:06 +03:00
if r . Sig != nil {
if r . Record != nil && r . Record . PublicKey != nil {
if pubkey , ok := r . Record . PublicKey . ( * rsa . PublicKey ) ; ok {
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
comment = fmt . Sprintf ( "%d bit rsa, " , pubkey . N . BitLen ( ) )
2023-01-30 16:27:06 +03:00
}
}
sig := base64 . StdEncoding . EncodeToString ( r . Sig . Signature )
sig = sig [ : 12 ] // Must be at least 8 characters and unique among the signatures.
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
props = [ ] message . AuthProp {
2024-03-31 16:23:53 +03:00
message . MakeAuthProp ( "header" , "d" , r . Sig . Domain . XName ( c . msgsmtputf8 ) , true , r . Sig . Domain . ASCIIExtra ( c . msgsmtputf8 ) ) ,
message . MakeAuthProp ( "header" , "s" , r . Sig . Selector . XName ( c . msgsmtputf8 ) , true , r . Sig . Selector . ASCIIExtra ( c . msgsmtputf8 ) ) ,
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
message . MakeAuthProp ( "header" , "a" , r . Sig . Algorithm ( ) , false , "" ) ,
message . MakeAuthProp ( "header" , "b" , sig , false , "" ) , // ../rfc/6008:147
2023-01-30 16:27:06 +03:00
}
domain = r . Sig . Domain
selector = r . Sig . Selector
if r . Sig . Identity != nil {
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
props = append ( props , message . MakeAuthProp ( "header" , "i" , r . Sig . Identity . String ( ) , true , "" ) )
2023-01-30 16:27:06 +03:00
identity = r . Sig . Identity
}
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
if r . RecordAuthentic {
comment += "with dnssec"
} else {
comment += "without dnssec"
}
2023-01-30 16:27:06 +03:00
}
var errmsg string
if r . Err != nil {
errmsg = r . Err . Error ( )
}
authResAddDKIM ( string ( r . Status ) , comment , errmsg , props )
2023-12-05 18:06:50 +03:00
c . log . Debugx ( "dkim verification result" , r . Err ,
slog . Int ( "index" , i ) ,
slog . Any ( "mailfrom" , c . mailFrom ) ,
slog . Any ( "status" , r . Status ) ,
slog . Any ( "domain" , domain ) ,
slog . Any ( "selector" , selector ) ,
slog . Any ( "identity" , identity ) )
2023-01-30 16:27:06 +03:00
}
// Add SPF results to Authentication-Results header. ../rfc/7208:2141
var spfIdentity * dns . Domain
var mailFromValidation = store . ValidationUnknown
var ehloValidation = store . ValidationUnknown
switch receivedSPF . Identity {
case spf . ReceivedHELO :
if len ( spfArgs . HelloDomain . IP ) == 0 {
spfIdentity = & spfArgs . HelloDomain . Domain
}
ehloValidation = store . SPFValidation ( receivedSPF . Result )
case spf . ReceivedMailFrom :
spfIdentity = & spfArgs . MailFromDomain
mailFromValidation = store . SPFValidation ( receivedSPF . Result )
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var props [ ] message . AuthProp
2023-01-30 16:27:06 +03:00
if spfIdentity != nil {
2024-03-31 16:23:53 +03:00
props = [ ] message . AuthProp { message . MakeAuthProp ( "smtp" , string ( receivedSPF . Identity ) , spfIdentity . XName ( c . msgsmtputf8 ) , true , spfIdentity . ASCIIExtra ( c . msgsmtputf8 ) ) }
2023-01-30 16:27:06 +03:00
}
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
var spfComment string
if spfAuthentic {
spfComment = "with dnssec"
} else {
spfComment = "without dnssec"
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
authResults . Methods = append ( authResults . Methods , message . AuthMethod {
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
Method : "spf" ,
Result : string ( receivedSPF . Result ) ,
Comment : spfComment ,
Props : props ,
2023-01-30 16:27:06 +03:00
} )
switch receivedSPF . Result {
case spf . StatusPass :
2023-12-05 15:35:58 +03:00
c . log . Debug ( "spf pass" , slog . Any ( "ip" , spfArgs . RemoteIP ) , slog . String ( "mailfromdomain" , spfArgs . MailFromDomain . ASCII ) ) // todo: log the domain that was actually verified.
2023-01-30 16:27:06 +03:00
case spf . StatusFail :
if spfExpl != "" {
// Filter out potentially hostile text. ../rfc/7208:2529
for _ , b := range [ ] byte ( spfExpl ) {
if b < ' ' || b >= 0x7f {
spfExpl = ""
break
}
}
if spfExpl != "" {
if len ( spfExpl ) > 800 {
spfExpl = spfExpl [ : 797 ] + "..."
}
spfExpl = "remote claims: " + spfExpl
}
}
if spfExpl == "" {
spfExpl = fmt . Sprintf ( "your ip %s is not on the SPF allowlist for domain %s" , spfArgs . RemoteIP , spfDomain . ASCII )
}
2023-12-05 15:35:58 +03:00
c . log . Info ( "spf fail" , slog . String ( "explanation" , spfExpl ) ) // todo future: get this to the client. how? in smtp session in case of a reject due to dmarc fail?
2023-01-30 16:27:06 +03:00
case spf . StatusTemperror :
c . log . Infox ( "spf temperror" , spfErr )
case spf . StatusPermerror :
c . log . Infox ( "spf permerror" , spfErr )
case spf . StatusNone , spf . StatusNeutral , spf . StatusSoftfail :
default :
2023-12-05 15:35:58 +03:00
c . log . Error ( "unknown spf status, treating as None/Neutral" , slog . Any ( "status" , receivedSPF . Result ) )
2023-01-30 16:27:06 +03:00
receivedSPF . Result = spf . StatusNone
}
// DMARC
var dmarcUse bool
var dmarcResult dmarc . Result
const applyRandomPercentage = true
2023-11-01 19:55:40 +03:00
// dmarcMethod is added to authResults when delivering to recipients: accounts can
// have different policy override rules.
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
var dmarcMethod message . AuthMethod
2023-01-30 16:27:06 +03:00
var msgFromValidation = store . ValidationNone
if msgFrom . IsZero ( ) {
dmarcResult . Status = dmarc . StatusNone
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
dmarcMethod = message . AuthMethod {
2023-01-30 16:27:06 +03:00
Method : "dmarc" ,
Result : string ( dmarcResult . Status ) ,
}
} else {
2023-12-05 15:35:58 +03:00
msgFromValidation = alignment ( ctx , c . log , msgFrom . Domain , dkimResults , receivedSPF . Result , spfIdentity )
2023-01-30 16:27:06 +03:00
2023-11-01 19:55:40 +03:00
// We are doing the DMARC evaluation now. But we only store it for inclusion in an
// aggregate report when we actually use it. We use an evaluation for each
// recipient, with each a potentially different result due to mailing
// list/forwarding configuration. If we reject a message due to being spam, we
// don't want to spend any resources for the sender domain, and we don't want to
// give the sender any more information about us, so we won't record the
// evaluation.
// todo future: also not send for first-time senders? they could be spammers getting through our filter, don't want to give them insights either. though we currently would have no reasonable way to decide if they are still reputationless at the time we are composing/sending aggregate reports.
2023-01-30 16:27:06 +03:00
dmarcctx , dmarccancel := context . WithTimeout ( ctx , time . Minute )
defer dmarccancel ( )
2023-12-05 15:35:58 +03:00
dmarcUse , dmarcResult = dmarc . Verify ( dmarcctx , c . log . Logger , c . resolver , msgFrom . Domain , dkimResults , receivedSPF . Result , spfIdentity , applyRandomPercentage )
2023-01-30 16:27:06 +03:00
dmarccancel ( )
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
var comment string
if dmarcResult . RecordAuthentic {
comment = "with dnssec"
} else {
comment = "without dnssec"
}
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
dmarcMethod = message . AuthMethod {
implement dnssec-awareness throughout code, and dane for incoming/outgoing mail delivery
the vendored dns resolver code is a copy of the go stdlib dns resolver, with
awareness of the "authentic data" (i.e. dnssec secure) added, as well as support
for enhanced dns errors, and looking up tlsa records (for dane). ideally it
would be upstreamed, but the chances seem slim.
dnssec-awareness is added to all packages, e.g. spf, dkim, dmarc, iprev. their
dnssec status is added to the Received message headers for incoming email.
but the main reason to add dnssec was for implementing dane. with dane, the
verification of tls certificates can be done through certificates/public keys
published in dns (in the tlsa records). this only makes sense (is trustworthy)
if those dns records can be verified to be authentic.
mox now applies dane to delivering messages over smtp. mox already implemented
mta-sts for webpki/pkix-verification of certificates against the (large) pool
of CA's, and still enforces those policies when present. but it now also checks
for dane records, and will verify those if present. if dane and mta-sts are
both absent, the regular opportunistic tls with starttls is still done. and the
fallback to plaintext is also still done.
mox also makes it easy to setup dane for incoming deliveries, so other servers
can deliver with dane tls certificate verification. the quickstart now
generates private keys that are used when requesting certificates with acme.
the private keys are pre-generated because they must be static and known during
setup, because their public keys must be published in tlsa records in dns.
autocert would generate private keys on its own, so had to be forked to add the
option to provide the private key when requesting a new certificate. hopefully
upstream will accept the change and we can drop the fork.
with this change, using the quickstart to setup a new mox instance, the checks
at internet.nl result in a 100% score, provided the domain is dnssec-signed and
the network doesn't have any issues.
2023-10-10 13:09:35 +03:00
Method : "dmarc" ,
Result : string ( dmarcResult . Status ) ,
Comment : comment ,
add webmail
it was far down on the roadmap, but implemented earlier, because it's
interesting, and to help prepare for a jmap implementation. for jmap we need to
implement more client-like functionality than with just imap. internal data
structures need to change. jmap has lots of other requirements, so it's already
a big project. by implementing a webmail now, some of the required data
structure changes become clear and can be made now, so the later jmap
implementation can do things similarly to the webmail code. the webmail
frontend and webmail are written together, making their interface/api much
smaller and simpler than jmap.
one of the internal changes is that we now keep track of per-mailbox
total/unread/unseen/deleted message counts and mailbox sizes. keeping this
data consistent after any change to the stored messages (through the code base)
is tricky, so mox now has a consistency check that verifies the counts are
correct, which runs only during tests, each time an internal account reference
is closed. we have a few more internal "changes" that are propagated for the
webmail frontend (that imap doesn't have a way to propagate on a connection),
like changes to the special-use flags on mailboxes, and used keywords in a
mailbox. more changes that will be required have revealed themselves while
implementing the webmail, and will be implemented next.
the webmail user interface is modeled after the mail clients i use or have
used: thunderbird, macos mail, mutt; and webmails i normally only use for
testing: gmail, proton, yahoo, outlook. a somewhat technical user is assumed,
but still the goal is to make this webmail client easy to use for everyone. the
user interface looks like most other mail clients: a list of mailboxes, a
search bar, a message list view, and message details. there is a top/bottom and
a left/right layout for the list/message view, default is automatic based on
screen size. the panes can be resized by the user. buttons for actions are just
text, not icons. clicking a button briefly shows the shortcut for the action in
the bottom right, helping with learning to operate quickly. any text that is
underdotted has a title attribute that causes more information to be displayed,
e.g. what a button does or a field is about. to highlight potential phishing
attempts, any text (anywhere in the webclient) that switches unicode "blocks"
(a rough approximation to (language) scripts) within a word is underlined
orange. multiple messages can be selected with familiar ui interaction:
clicking while holding control and/or shift keys. keyboard navigation works
with arrows/page up/down and home/end keys, and also with a few basic vi-like
keys for list/message navigation. we prefer showing the text instead of
html (with inlined images only) version of a message. html messages are shown
in an iframe served from an endpoint with CSP headers to prevent dangerous
resources (scripts, external images) from being loaded. the html is also
sanitized, with javascript removed. a user can choose to load external
resources (e.g. images for tracking purposes).
the frontend is just (strict) typescript, no external frameworks. all
incoming/outgoing data is typechecked, both the api request parameters and
response types, and the data coming in over SSE. the types and checking code
are generated with sherpats, which uses the api definitions generated by
sherpadoc based on the Go code. so types from the backend are automatically
propagated to the frontend. since there is no framework to automatically
propagate properties and rerender components, changes coming in over the SSE
connection are propagated explicitly with regular function calls. the ui is
separated into "views", each with a "root" dom element that is added to the
visible document. these views have additional functions for getting changes
propagated, often resulting in the view updating its (internal) ui state (dom).
we keep the frontend compilation simple, it's just a few typescript files that
get compiled (combined and types stripped) into a single js file, no additional
runtime code needed or complicated build processes used. the webmail is served
is served from a compressed, cachable html file that includes style and the
javascript, currently just over 225kb uncompressed, under 60kb compressed (not
minified, including comments). we include the generated js files in the
repository, to keep Go's easily buildable self-contained binaries.
authentication is basic http, as with the account and admin pages. most data
comes in over one long-term SSE connection to the backend. api requests signal
which mailbox/search/messages are requested over the SSE connection. fetching
individual messages, and making changes, are done through api calls. the
operations are similar to imap, so some code has been moved from package
imapserver to package store. the future jmap implementation will benefit from
these changes too. more functionality will probably be moved to the store
package in the future.
the quickstart enables webmail on the internal listener by default (for new
installs). users can enable it on the public listener if they want to. mox
localserve enables it too. to enable webmail on existing installs, add settings
like the following to the listeners in mox.conf, similar to AccountHTTP(S):
WebmailHTTP:
Enabled: true
WebmailHTTPS:
Enabled: true
special thanks to liesbeth, gerben, andrii for early user feedback.
there is plenty still to do, see the list at the top of webmail/webmail.ts.
feedback welcome as always.
2023-08-07 22:57:03 +03:00
Props : [ ] message . AuthProp {
2023-01-30 16:27:06 +03:00
// ../rfc/7489:1489
2024-03-31 16:23:53 +03:00
message . MakeAuthProp ( "header" , "from" , msgFrom . Domain . ASCII , true , msgFrom . Domain . ASCIIExtra ( c . msgsmtputf8 ) ) ,
2023-01-30 16:27:06 +03:00
} ,
}
if dmarcResult . Status == dmarc . StatusPass && msgFromValidation == store . ValidationRelaxed {
msgFromValidation = store . ValidationDMARC
}
2023-11-01 19:55:40 +03:00
// todo future: consider enforcing an spf (soft)fail if there is no dmarc policy or the dmarc policy is none. ../rfc/7489:1507
2023-01-30 16:27:06 +03:00
}
2023-12-05 15:35:58 +03:00
c . log . Debug ( "dmarc verification" , slog . Any ( "result" , dmarcResult . Status ) , slog . Any ( "domain" , msgFrom . Domain ) )
2023-01-30 16:27:06 +03:00
// Prepare for analyzing content, calculating reputation.
2023-03-12 12:38:02 +03:00
ipmasked1 , ipmasked2 , ipmasked3 := ipmasked ( c . remoteIP )
2023-01-30 16:27:06 +03:00
var verifiedDKIMDomains [ ] string
2023-09-11 12:37:45 +03:00
dkimSeen := map [ string ] bool { }
2023-01-30 16:27:06 +03:00
for _ , r := range dkimResults {
// A message can have multiple signatures for the same identity. For example when
// signing the message multiple times with different algorithms (rsa and ed25519).
if r . Status != dkim . StatusPass {
continue
}
d := r . Sig . Domain . Name ( )
2023-09-11 12:37:45 +03:00
if ! dkimSeen [ d ] {
dkimSeen [ d ] = true
2023-01-30 16:27:06 +03:00
verifiedDKIMDomains = append ( verifiedDKIMDomains , d )
}
}
// When we deliver, we try to remove from rejects mailbox based on message-id.
// We'll parse it when we need it, but it is the same for each recipient.
var messageID string
var parsedMessageID bool
// We build up a DSN for each failed recipient. If we have recipients in dsnMsg
// after processing, we queue the DSN. Unless all recipients failed, in which case
// we may just fail the mail transaction instead (could be common for failure to
// deliver to a single recipient, e.g. for junk mail).
// ../rfc/3464:436
type deliverError struct {
rcptTo smtp . Path
code int
secode string
userError bool
errmsg string
}
var deliverErrors [ ] deliverError
2024-04-24 20:15:30 +03:00
addError := func ( rcpt recipient , code int , secode string , userError bool , errmsg string ) {
2024-11-01 12:38:31 +03:00
e := deliverError { rcpt . Addr , code , secode , userError , errmsg }
2023-12-05 18:06:50 +03:00
c . log . Info ( "deliver error" ,
slog . Any ( "rcptto" , e . rcptTo ) ,
slog . Int ( "code" , code ) ,
slog . String ( "secode" , "secode" ) ,
slog . Bool ( "usererror" , userError ) ,
slog . String ( "errmsg" , errmsg ) )
2023-01-30 16:27:06 +03:00
deliverErrors = append ( deliverErrors , e )
}
2024-04-24 20:15:30 +03:00
// Sort recipients: local accounts, aliases, unknown. For ensuring we don't deliver
// to an alias destination that was also explicitly sent to.
rcptScore := func ( r recipient ) int {
2024-11-01 12:38:31 +03:00
if r . Account != nil {
2024-04-24 20:15:30 +03:00
return 0
2024-11-01 12:38:31 +03:00
} else if r . Alias != nil {
2024-04-24 20:15:30 +03:00
return 1
}
return 2
}
sort . SliceStable ( c . recipients , func ( i , j int ) bool {
return rcptScore ( c . recipients [ i ] ) < rcptScore ( c . recipients [ j ] )
} )
2023-01-30 16:27:06 +03:00
2024-04-24 20:15:30 +03:00
// Return whether address is a regular explicit recipient in this transaction. Used
// to prevent delivering a message to an address both for alias and explicit
// addressee. Relies on c.recipients being sorted as above.
regularRecipient := func ( addr smtp . Path ) bool {
for _ , rcpt := range c . recipients {
2024-11-01 12:38:31 +03:00
if rcpt . Account == nil {
2024-04-24 20:15:30 +03:00
break
2024-11-01 12:38:31 +03:00
} else if rcpt . Addr . Equal ( addr ) {
2024-04-24 20:15:30 +03:00
return true
}
2023-01-30 16:27:06 +03:00
}
2024-04-24 20:15:30 +03:00
return false
}
2023-01-30 16:27:06 +03:00
2024-04-24 20:15:30 +03:00
// Prepare a message, analyze it against account's junk filter.
// The returned analysis has an open account that must be closed by the caller.
// We call this for all alias destinations, also when we already delivered to that
// recipient: It may be the only recipient that would allow the message.
messageAnalyze := func ( log mlog . Log , smtpRcptTo , deliverTo smtp . Path , accountName string , destination config . Destination , canonicalAddr string ) ( a * analysis , rerr error ) {
acc , err := store . OpenAccount ( log , accountName )
2023-01-30 16:27:06 +03:00
if err != nil {
2024-04-24 20:15:30 +03:00
log . Errorx ( "open account" , err , slog . Any ( "account" , accountName ) )
2023-01-30 16:27:06 +03:00
metricDelivery . WithLabelValues ( "accounterror" , "" ) . Inc ( )
2024-04-24 20:15:30 +03:00
return nil , err
2023-01-30 16:27:06 +03:00
}
defer func ( ) {
2024-04-24 20:15:30 +03:00
if a == nil {
2023-02-16 15:22:00 +03:00
err := acc . Close ( )
2024-04-24 20:15:30 +03:00
log . Check ( err , "closing account during analysis" )
2023-01-30 16:27:06 +03:00
}
} ( )
2023-11-01 23:30:13 +03:00
m := store . Message {
2023-01-30 16:27:06 +03:00
Received : time . Now ( ) ,
RemoteIP : c . remoteIP . String ( ) ,
RemoteIPMasked1 : ipmasked1 ,
RemoteIPMasked2 : ipmasked2 ,
RemoteIPMasked3 : ipmasked3 ,
EHLODomain : c . hello . Domain . Name ( ) ,
MailFrom : c . mailFrom . String ( ) ,
MailFromLocalpart : c . mailFrom . Localpart ,
MailFromDomain : c . mailFrom . IPDomain . Domain . Name ( ) ,
2024-04-24 20:15:30 +03:00
RcptToLocalpart : smtpRcptTo . Localpart ,
RcptToDomain : smtpRcptTo . IPDomain . Domain . Name ( ) ,
2023-01-30 16:27:06 +03:00
MsgFromLocalpart : msgFrom . Localpart ,
MsgFromDomain : msgFrom . Domain . Name ( ) ,
2023-12-05 15:35:58 +03:00
MsgFromOrgDomain : publicsuffix . Lookup ( ctx , log . Logger , msgFrom . Domain ) . Name ( ) ,
2023-01-30 16:27:06 +03:00
EHLOValidated : ehloValidation == store . ValidationPass ,
MailFromValidated : mailFromValidation == store . ValidationPass ,
MsgFromValidated : msgFromValidation == store . ValidationStrict || msgFromValidation == store . ValidationDMARC || msgFromValidation == store . ValidationRelaxed ,
EHLOValidation : ehloValidation ,
MailFromValidation : mailFromValidation ,
MsgFromValidation : msgFromValidation ,
DKIMDomains : verifiedDKIMDomains ,
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
DSN : isDSN ,
2023-11-01 19:55:40 +03:00
Size : msgWriter . Size ,
2023-01-30 16:27:06 +03:00
}
2023-11-01 23:30:13 +03:00
if c . tls {
tlsState := c . conn . ( * tls . Conn ) . ConnectionState ( )
m . ReceivedTLSVersion = tlsState . Version
m . ReceivedTLSCipherSuite = tlsState . CipherSuite
if c . requireTLS != nil {
m . ReceivedRequireTLS = * c . requireTLS
}
} else {
m . ReceivedTLSVersion = 1 // Signals plain text delivery.
}
2023-11-27 12:34:01 +03:00
var msgTo , msgCc [ ] message . Address
if envelope != nil {
msgTo = envelope . To
msgCc = envelope . CC
}
2024-10-04 17:01:30 +03:00
d := delivery { c . tls , & m , dataFile , smtpRcptTo , deliverTo , destination , canonicalAddr , acc , msgTo , msgCc , msgFrom , c . dnsBLs , dmarcUse , dmarcResult , dkimResults , iprevStatus , c . smtputf8 }
2024-04-24 20:15:30 +03:00
r := analyze ( ctx , log , c . resolver , d )
return & r , nil
}
// Either deliver the message, or call addError to register the recipient as failed.
// If recipient is an alias, we may be delivering to multiple address/accounts and
// we will consider a message delivered if we delivered it to at least one account
// (others may be over quota).
processRecipient := func ( rcpt recipient ) {
2024-11-01 12:38:31 +03:00
log := c . log . With ( slog . Any ( "mailfrom" , c . mailFrom ) , slog . Any ( "rcptto" , rcpt . Addr ) )
2024-04-24 20:15:30 +03:00
// If this is not a valid local user, we send back a DSN. This can only happen when
// there are also valid recipients, and only when remote is SPF-verified, so the DSN
// should not cause backscatter.
// In case of serious errors, we abort the transaction. We may have already
// delivered some messages. Perhaps it would be better to continue with other
// deliveries, and return an error at the end? Though the failure conditions will
// probably prevent any other successful deliveries too...
// We'll continue delivering to other recipients. ../rfc/5321:3275
2024-11-01 12:38:31 +03:00
if rcpt . Account == nil && rcpt . Alias == nil {
2024-04-24 20:15:30 +03:00
metricDelivery . WithLabelValues ( "unknownuser" , "" ) . Inc ( )
addError ( rcpt , smtp . C550MailboxUnavail , smtp . SeAddr1UnknownDestMailbox1 , true , "no such user" )
return
}
// la holds all analysis, and message preparation, for all accounts (multiple for
// aliases). Each has an open account that we we close on return.
var la [ ] analysis
defer func ( ) {
for _ , a := range la {
err := a . d . acc . Close ( )
log . Check ( err , "close account" )
}
} ( )
// For aliases, we prepare & analyze for each recipient. We accept the message if
// any recipient accepts it. Regular destination have just a single account to
// check. We check all alias destinations, even if we already explicitly delivered
// to them: they may be the only destination that would accept the message.
var a0 * analysis // Analysis we've used for accept/reject decision.
2024-11-01 12:38:31 +03:00
if rcpt . Alias != nil {
2024-04-24 20:15:30 +03:00
// Check if msgFrom address is acceptable. This doesn't take validation into
// consideration. If the header was forged, the message may be rejected later on.
2024-11-01 12:38:31 +03:00
if ! aliasAllowedMsgFrom ( rcpt . Alias . Alias , msgFrom ) {
2024-04-24 20:15:30 +03:00
addError ( rcpt , smtp . C550MailboxUnavail , smtp . SePol7ExpnProhibited2 , true , "not allowed to send to destination" )
return
}
2024-11-01 12:38:31 +03:00
la = make ( [ ] analysis , 0 , len ( rcpt . Alias . Alias . ParsedAddresses ) )
for _ , aa := range rcpt . Alias . Alias . ParsedAddresses {
a , err := messageAnalyze ( log , rcpt . Addr , aa . Address . Path ( ) , aa . AccountName , aa . Destination , rcpt . Alias . CanonicalAddress )
2024-04-24 20:15:30 +03:00
if err != nil {
addError ( rcpt , smtp . C451LocalErr , smtp . SeSys3Other0 , false , "error processing" )
return
}
la = append ( la , * a )
if a . accept && a0 == nil {
// Address that caused us to accept.
a0 = & la [ len ( la ) - 1 ]
}
}
if a0 == nil {
// First address, for rejecting.
a0 = & la [ 0 ]
}
} else {
2024-11-01 12:38:31 +03:00
a , err := messageAnalyze ( log , rcpt . Addr , rcpt . Addr , rcpt . Account . AccountName , rcpt . Account . Destination , rcpt . Account . CanonicalAddress )
2024-04-24 20:15:30 +03:00
if err != nil {
addError ( rcpt , smtp . C451LocalErr , smtp . SeSys3Other0 , false , "error processing" )
return
}
la = [ ] analysis { * a }
a0 = & la [ 0 ]
}
if ! a0 . accept && a0 . reason == reasonHighRate {
log . Info ( "incoming message rejected for high rate, not storing in rejects mailbox" , slog . String ( "reason" , a0 . reason ) , slog . Any ( "msgfrom" , msgFrom ) )
metricDelivery . WithLabelValues ( "reject" , a0 . reason ) . Inc ( )
c . setSlow ( true )
addError ( rcpt , a0 . code , a0 . secode , a0 . userError , a0 . errmsg )
return
}
2023-11-01 19:55:40 +03:00
// Any DMARC result override is stored in the evaluation for outgoing DMARC
// aggregate reports, and added to the Authentication-Results message header.
2023-11-02 19:54:24 +03:00
// We want to tell the sender that we have an override, e.g. for mailing lists, so
// they don't overestimate the potential damage of switching from p=none to
// p=reject.
var dmarcOverrides [ ] string
2024-04-24 20:15:30 +03:00
if a0 . dmarcOverrideReason != "" {
dmarcOverrides = [ ] string { a0 . dmarcOverrideReason }
2023-11-02 19:54:24 +03:00
}
if dmarcResult . Record != nil && ! dmarcUse {
dmarcOverrides = append ( dmarcOverrides , string ( dmarcrpt . PolicyOverrideSampledOut ) )
2023-11-01 19:55:40 +03:00
}
// Add per-recipient DMARC method to Authentication-Results. Each account can have
// their own override rules, e.g. based on configured mailing lists/forwards.
// ../rfc/7489:1486
rcptDMARCMethod := dmarcMethod
2023-11-02 19:54:24 +03:00
if len ( dmarcOverrides ) > 0 {
2023-11-01 19:55:40 +03:00
if rcptDMARCMethod . Comment != "" {
rcptDMARCMethod . Comment += ", "
}
2023-11-02 19:54:24 +03:00
rcptDMARCMethod . Comment += "override " + strings . Join ( dmarcOverrides , "," )
2023-11-01 19:55:40 +03:00
}
rcptAuthResults := authResults
rcptAuthResults . Methods = append ( [ ] message . AuthMethod { } , authResults . Methods ... )
rcptAuthResults . Methods = append ( rcptAuthResults . Methods , rcptDMARCMethod )
2024-10-04 17:01:30 +03:00
// Prepend reason as message header, for easy viewing in mail clients.
2023-11-10 21:34:00 +03:00
var xmox string
2024-04-24 20:15:30 +03:00
if a0 . reason != "" {
2024-10-04 17:01:30 +03:00
hw := & message . HeaderWriter { }
hw . Add ( " " , "X-Mox-Reason:" )
hw . Add ( " " , a0 . reason )
for i , s := range a0 . reasonText {
if i == 0 {
s = "; " + s
} else {
hw . Newline ( )
}
// Just in case any of the strings has a newline, replace it with space to not break the message.
s = strings . ReplaceAll ( s , "\n" , " " )
s = strings . ReplaceAll ( s , "\r" , " " )
s += ";"
hw . AddWrap ( [ ] byte ( s ) , true )
}
xmox = hw . String ( )
2024-04-24 20:15:30 +03:00
}
xmox += a0 . headers
for i := range la {
// ../rfc/5321:3204
// Received-SPF header goes before Received. ../rfc/7208:2038
la [ i ] . d . m . MsgPrefix = [ ] byte (
xmox +
"Delivered-To: " + la [ i ] . d . deliverTo . XString ( c . msgsmtputf8 ) + "\r\n" + // ../rfc/9228:274
"Return-Path: <" + c . mailFrom . String ( ) + ">\r\n" + // ../rfc/5321:3300
rcptAuthResults . Header ( ) +
receivedSPF . Header ( ) +
2024-11-01 12:38:31 +03:00
recvHdrFor ( rcpt . Addr . String ( ) ) ,
2024-04-24 20:15:30 +03:00
)
la [ i ] . d . m . Size += int64 ( len ( la [ i ] . d . m . MsgPrefix ) )
}
2023-11-01 19:55:40 +03:00
// Store DMARC evaluation for inclusion in an aggregate report. Only if there is at
// least one reporting address: We don't want to needlessly store a row in a
// database for each delivery attempt. If we reject a message for being junk, we
// are also not going to send it a DMARC report. The DMARC check is done early in
// the analysis, we will report on rejects because of DMARC, because it could be
// valuable feedback about forwarded or mailing list messages.
// ../rfc/7489:1492
2024-04-24 20:15:30 +03:00
if ! mox . Conf . Static . NoOutgoingDMARCReports && dmarcResult . Record != nil && len ( dmarcResult . Record . AggregateReportAddresses ) > 0 && ( a0 . accept && ! a0 . d . m . IsReject || a0 . reason == reasonDMARCPolicy ) {
2023-11-01 19:55:40 +03:00
// Disposition holds our decision on whether to accept the message. Not what the
// DMARC evaluation resulted in. We can override, e.g. because of mailing lists,
// forwarding, or local policy.
// We treat quarantine as reject, so never claim to quarantine.
// ../rfc/7489:1691
disposition := dmarcrpt . DispositionNone
2024-04-24 20:15:30 +03:00
if ! a0 . accept {
2023-11-01 19:55:40 +03:00
disposition = dmarcrpt . DispositionReject
}
// unknownDomain returns whether the sender is domain with which this account has
// not had positive interaction.
unknownDomain := func ( ) ( unknown bool ) {
2024-04-24 20:15:30 +03:00
err := a0 . d . acc . DB . Read ( ctx , func ( tx * bstore . Tx ) ( err error ) {
2023-11-01 19:55:40 +03:00
// See if we received a non-junk message from this organizational domain.
q := bstore . QueryTx [ store . Message ] ( tx )
2024-04-24 20:15:30 +03:00
q . FilterNonzero ( store . Message { MsgFromOrgDomain : a0 . d . m . MsgFromOrgDomain } )
2023-11-02 19:54:24 +03:00
q . FilterEqual ( "Notjunk" , true )
2023-11-05 01:24:47 +03:00
q . FilterEqual ( "IsReject" , false )
2023-11-01 19:55:40 +03:00
exists , err := q . Exists ( )
if err != nil {
return fmt . Errorf ( "querying for non-junk message from organizational domain: %v" , err )
}
if exists {
return nil
}
// See if we sent a message to this organizational domain.
qr := bstore . QueryTx [ store . Recipient ] ( tx )
2024-04-24 20:15:30 +03:00
qr . FilterNonzero ( store . Recipient { OrgDomain : a0 . d . m . MsgFromOrgDomain } )
2023-11-01 19:55:40 +03:00
exists , err = qr . Exists ( )
if err != nil {
return fmt . Errorf ( "querying for message sent to organizational domain: %v" , err )
}
if ! exists {
unknown = true
}
return nil
} )
if err != nil {
log . Errorx ( "checking if sender is unknown domain, for dmarc aggregate report evaluation" , err )
}
return
}
r := dmarcResult . Record
addresses := make ( [ ] string , len ( r . AggregateReportAddresses ) )
for i , a := range r . AggregateReportAddresses {
addresses [ i ] = a . String ( )
}
sp := dmarcrpt . Disposition ( r . SubdomainPolicy )
if r . SubdomainPolicy == dmarc . PolicyEmpty {
sp = dmarcrpt . Disposition ( r . Policy )
}
eval := dmarcdb . Evaluation {
// Evaluated and IntervalHours set by AddEvaluation.
PolicyDomain : dmarcResult . Domain . Name ( ) ,
// Optional evaluations don't cause a report to be sent, but will be included.
// Useful for automated inter-mailer messages, we don't want to get in a reporting
// loop. We also don't want to be used for sending reports to unsuspecting domains
// we have no relation with.
// todo: would it make sense to also mark some percentage of mailing-list-policy-overrides optional? to lower the load on mail servers of folks sending to large mailing lists.
2024-04-24 20:15:30 +03:00
Optional : a0 . d . destination . DMARCReports || a0 . d . destination . HostTLSReports || a0 . d . destination . DomainTLSReports || a0 . reason == reasonDMARCPolicy && unknownDomain ( ) ,
2023-11-01 19:55:40 +03:00
Addresses : addresses ,
PolicyPublished : dmarcrpt . PolicyPublished {
Domain : dmarcResult . Domain . Name ( ) ,
ADKIM : dmarcrpt . Alignment ( r . ADKIM ) ,
ASPF : dmarcrpt . Alignment ( r . ASPF ) ,
Policy : dmarcrpt . Disposition ( r . Policy ) ,
SubdomainPolicy : sp ,
Percentage : r . Percentage ,
// We don't save ReportingOptions, we don't do per-message failure reporting.
} ,
SourceIP : c . remoteIP . String ( ) ,
Disposition : disposition ,
AlignedDKIMPass : dmarcResult . AlignedDKIMPass ,
AlignedSPFPass : dmarcResult . AlignedSPFPass ,
2024-11-01 12:38:31 +03:00
EnvelopeTo : rcpt . Addr . IPDomain . String ( ) ,
2023-11-01 19:55:40 +03:00
EnvelopeFrom : c . mailFrom . IPDomain . String ( ) ,
HeaderFrom : msgFrom . Domain . Name ( ) ,
}
2023-11-02 19:54:24 +03:00
for _ , s := range dmarcOverrides {
reason := dmarcrpt . PolicyOverrideReason { Type : dmarcrpt . PolicyOverride ( s ) }
eval . OverrideReasons = append ( eval . OverrideReasons , reason )
2023-11-01 19:55:40 +03:00
}
// We'll include all signatures for the organizational domain, even if they weren't
// relevant due to strict alignment requirement.
for _ , dkimResult := range dkimResults {
2023-12-05 15:35:58 +03:00
if dkimResult . Sig == nil || publicsuffix . Lookup ( ctx , log . Logger , msgFrom . Domain ) != publicsuffix . Lookup ( ctx , log . Logger , dkimResult . Sig . Domain ) {
2023-11-01 19:55:40 +03:00
continue
}
r := dmarcrpt . DKIMAuthResult {
Domain : dkimResult . Sig . Domain . Name ( ) ,
Selector : dkimResult . Sig . Selector . ASCII ,
Result : dmarcrpt . DKIMResult ( dkimResult . Status ) ,
}
eval . DKIMResults = append ( eval . DKIMResults , r )
}
switch receivedSPF . Identity {
case spf . ReceivedHELO :
spfAuthResult := dmarcrpt . SPFAuthResult {
Domain : spfArgs . HelloDomain . String ( ) , // Can be unicode and also IP.
Scope : dmarcrpt . SPFDomainScopeHelo ,
Result : dmarcrpt . SPFResult ( receivedSPF . Result ) ,
}
eval . SPFResults = [ ] dmarcrpt . SPFAuthResult { spfAuthResult }
case spf . ReceivedMailFrom :
spfAuthResult := dmarcrpt . SPFAuthResult {
Domain : spfArgs . MailFromDomain . Name ( ) , // Can be unicode.
Scope : dmarcrpt . SPFDomainScopeMailFrom ,
Result : dmarcrpt . SPFResult ( receivedSPF . Result ) ,
}
eval . SPFResults = [ ] dmarcrpt . SPFAuthResult { spfAuthResult }
}
err := dmarcdb . AddEvaluation ( ctx , dmarcResult . Record . AggregateReportingInterval , & eval )
log . Check ( err , "adding dmarc evaluation to database for aggregate report" )
2023-01-30 16:27:06 +03:00
}
2023-11-01 19:55:40 +03:00
2024-04-24 20:15:30 +03:00
if ! a0 . accept {
for _ , a := range la {
// Don't add message if address was also explicitly present in a RCPT TO command.
2024-11-01 12:38:31 +03:00
if rcpt . Alias != nil && regularRecipient ( a . d . deliverTo ) {
2024-04-24 20:15:30 +03:00
continue
}
conf , _ := a . d . acc . Conf ( )
if conf . RejectsMailbox == "" {
continue
}
present , _ , messagehash , err := rejectPresent ( log , a . d . acc , conf . RejectsMailbox , a . d . m , dataFile )
2023-01-30 16:27:06 +03:00
if err != nil {
log . Errorx ( "checking whether reject is already present" , err )
2024-04-24 20:15:30 +03:00
continue
} else if present {
2023-01-30 16:27:06 +03:00
log . Info ( "reject message is already present, ignoring" )
2024-04-24 20:15:30 +03:00
continue
2023-01-30 16:27:06 +03:00
}
2024-04-24 20:15:30 +03:00
a . d . m . IsReject = true
a . d . m . Seen = true // We don't want to draw attention.
// Regular automatic junk flags configuration applies to these messages. The
// default is to treat these as neutral, so they won't cause outright rejections
// due to reputation for later delivery attempts.
a . d . m . MessageHash = messagehash
a . d . acc . WithWLock ( func ( ) {
hasSpace := true
var err error
if ! conf . KeepRejects {
hasSpace , err = a . d . acc . TidyRejectsMailbox ( c . log , conf . RejectsMailbox )
}
if err != nil {
log . Errorx ( "tidying rejects mailbox" , err )
} else if hasSpace {
if err := a . d . acc . DeliverMailbox ( log , conf . RejectsMailbox , a . d . m , dataFile ) ; err != nil {
log . Errorx ( "delivering spammy mail to rejects mailbox" , err )
} else {
log . Info ( "delivered spammy mail to rejects mailbox" )
}
} else {
log . Info ( "not storing spammy mail to full rejects mailbox" )
}
} )
2023-01-30 16:27:06 +03:00
}
2024-04-24 20:15:30 +03:00
log . Info ( "incoming message rejected" , slog . String ( "reason" , a0 . reason ) , slog . Any ( "msgfrom" , msgFrom ) )
metricDelivery . WithLabelValues ( "reject" , a0 . reason ) . Inc ( )
2023-02-08 23:45:32 +03:00
c . setSlow ( true )
2024-04-24 20:15:30 +03:00
addError ( rcpt , a0 . code , a0 . secode , a0 . userError , a0 . errmsg )
return
2023-01-30 16:27:06 +03:00
}
2023-08-09 23:31:37 +03:00
delayFirstTime := true
2024-11-01 12:38:31 +03:00
if rcpt . Account != nil && a0 . dmarcReport != nil {
2023-01-30 16:27:06 +03:00
// todo future: add rate limiting to prevent DoS attacks. ../rfc/7489:2570
2024-04-24 20:15:30 +03:00
if err := dmarcdb . AddReport ( ctx , a0 . dmarcReport , msgFrom . Domain ) ; err != nil {
2023-11-01 19:55:40 +03:00
log . Errorx ( "saving dmarc aggregate report in database" , err )
2023-01-30 16:27:06 +03:00
} else {
2023-11-01 19:55:40 +03:00
log . Info ( "dmarc aggregate report processed" )
2024-04-24 20:15:30 +03:00
a0 . d . m . Flags . Seen = true
2023-08-09 23:31:37 +03:00
delayFirstTime = false
2023-01-30 16:27:06 +03:00
}
}
2024-11-01 12:38:31 +03:00
if rcpt . Account != nil && a0 . tlsReport != nil {
2023-01-30 16:27:06 +03:00
// todo future: add rate limiting to prevent DoS attacks.
2024-04-24 20:15:30 +03:00
if err := tlsrptdb . AddReport ( ctx , c . log , msgFrom . Domain , c . mailFrom . String ( ) , a0 . d . destination . HostTLSReports , a0 . tlsReport ) ; err != nil {
2023-01-30 16:27:06 +03:00
log . Errorx ( "saving TLSRPT report in database" , err )
} else {
log . Info ( "tlsrpt report processed" )
2024-04-24 20:15:30 +03:00
a0 . d . m . Flags . Seen = true
2023-08-09 23:31:37 +03:00
delayFirstTime = false
2023-01-30 16:27:06 +03:00
}
}
2024-03-16 22:24:07 +03:00
// If this is a first-time sender and not a forwarded/mailing list message, wait
// before actually delivering. If this turns out to be a spammer, we've kept one of
// their connections busy.
2024-04-24 20:15:30 +03:00
a0conf , _ := a0 . d . acc . Conf ( )
if delayFirstTime && ! a0 . d . m . IsForward && ! a0 . d . m . IsMailingList && a0 . reason == reasonNoBadSignals && ! a0conf . NoFirstTimeSenderDelay && c . firstTimeSenderDelay > 0 {
2023-12-05 15:35:58 +03:00
log . Debug ( "delaying before delivering from sender without reputation" , slog . Duration ( "delay" , c . firstTimeSenderDelay ) )
2023-07-01 15:24:28 +03:00
mox . Sleep ( mox . Context , c . firstTimeSenderDelay )
2023-02-08 23:45:32 +03:00
}
2024-04-24 20:15:30 +03:00
if Localserve {
2024-11-01 12:38:31 +03:00
code , timeout := mox . LocalserveNeedsError ( rcpt . Addr . Localpart )
2024-04-24 20:15:30 +03:00
if timeout {
log . Info ( "timing out due to special localpart" )
mox . Sleep ( mox . Context , time . Hour )
xsmtpServerErrorf ( codes { smtp . C451LocalErr , smtp . SeOther00 } , "timing out delivery due to special localpart" )
} else if code != 0 {
log . Info ( "failure due to special localpart" , slog . Int ( "code" , code ) )
metricDelivery . WithLabelValues ( "delivererror" , "localserve" ) . Inc ( )
addError ( rcpt , code , smtp . SeOther00 , false , fmt . Sprintf ( "failure with code %d due to special localpart" , code ) )
return
}
}
2023-03-12 12:38:02 +03:00
// Gather the message-id before we deliver and the file may be consumed.
if ! parsedMessageID {
2024-04-24 20:15:30 +03:00
if p , err := message . Parse ( c . log . Logger , false , store . FileMsgReader ( a0 . d . m . MsgPrefix , dataFile ) ) ; err != nil {
2023-03-12 12:38:02 +03:00
log . Infox ( "parsing message for message-id" , err )
} else if header , err := p . Header ( ) ; err != nil {
log . Infox ( "parsing message header for message-id" , err )
} else {
messageID = header . Get ( "Message-Id" )
2023-01-30 16:27:06 +03:00
}
2024-04-24 20:15:30 +03:00
parsedMessageID = true
2023-03-12 12:38:02 +03:00
}
2023-01-30 16:27:06 +03:00
2024-04-24 20:15:30 +03:00
// Finally deliver the message to the account(s).
var nerr int // Number of non-quota errors.
var nfull int // Number of failed deliveries due to over quota.
var ndelivered int // Number delivered to account.
for _ , a := range la {
// Don't deliver to recipient that was explicitly present in SMTP transaction, or
// is sending the message to an alias they are member of.
2024-11-01 12:38:31 +03:00
if rcpt . Alias != nil && ( regularRecipient ( a . d . deliverTo ) || a . d . deliverTo . Equal ( msgFrom . Path ( ) ) ) {
2024-04-24 20:15:30 +03:00
continue
2023-01-30 16:27:06 +03:00
}
2024-04-24 20:15:30 +03:00
var delivered bool
a . d . acc . WithWLock ( func ( ) {
if err := a . d . acc . DeliverMailbox ( log , a . mailbox , a . d . m , dataFile ) ; err != nil {
log . Errorx ( "delivering" , err )
metricDelivery . WithLabelValues ( "delivererror" , a0 . reason ) . Inc ( )
if errors . Is ( err , store . ErrOverQuota ) {
nfull ++
} else {
addError ( rcpt , smtp . C451LocalErr , smtp . SeSys3Other0 , false , "error processing" )
nerr ++
}
return
2023-12-20 22:54:12 +03:00
}
2024-04-24 20:15:30 +03:00
delivered = true
ndelivered ++
metricDelivery . WithLabelValues ( "delivered" , a0 . reason ) . Inc ( )
log . Info ( "incoming message delivered" , slog . String ( "reason" , a0 . reason ) , slog . Any ( "msgfrom" , msgFrom ) )
conf , _ := a . d . acc . Conf ( )
if conf . RejectsMailbox != "" && a . d . m . MessageID != "" {
if err := a . d . acc . RejectsRemove ( log , conf . RejectsMailbox , a . d . m . MessageID ) ; err != nil {
log . Errorx ( "removing message from rejects mailbox" , err , slog . String ( "messageid" , messageID ) )
}
}
} )
2023-08-10 12:28:57 +03:00
2024-04-24 20:15:30 +03:00
// Pass delivered messages to queue for DSN processing and/or hooks.
if delivered {
mr := store . FileMsgReader ( a . d . m . MsgPrefix , dataFile )
part , err := a . d . m . LoadPart ( mr )
if err != nil {
log . Errorx ( "loading parsed part for evaluating webhook" , err )
} else {
err = queue . Incoming ( context . Background ( ) , log , a . d . acc , messageID , * a . d . m , part , a . mailbox )
log . Check ( err , "queueing webhook for incoming delivery" )
2023-08-10 12:28:57 +03:00
}
2024-04-24 20:15:30 +03:00
} else if nerr > 0 && ndelivered == 0 {
// Don't continue if we had an error and haven't delivered yet. If we only had
// quota-related errors, we keep trying for an account to deliver to.
break
2023-08-10 12:28:57 +03:00
}
2024-04-24 20:15:30 +03:00
}
if ndelivered == 0 && ( nerr > 0 || nfull > 0 ) {
if nerr == 0 {
addError ( rcpt , smtp . C452StorageFull , smtp . SeMailbox2Full2 , true , "account storage full" )
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
} else {
2024-04-24 20:15:30 +03:00
addError ( rcpt , smtp . C451LocalErr , smtp . SeSys3Other0 , false , "error processing" )
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
}
}
2024-04-24 20:15:30 +03:00
}
add a webapi and webhooks for a simple http/json-based api
for applications to compose/send messages, receive delivery feedback, and
maintain suppression lists.
this is an alternative to applications using a library to compose messages,
submitting those messages using smtp, and monitoring a mailbox with imap for
DSNs, which can be processed into the equivalent of suppression lists. but you
need to know about all these standards/protocols and find libraries. by using
the webapi & webhooks, you just need a http & json library.
unfortunately, there is no standard for these kinds of api, so mox has made up
yet another one...
matching incoming DSNs about deliveries to original outgoing messages requires
keeping history of "retired" messages (delivered from the queue, either
successfully or failed). this can be enabled per account. history is also
useful for debugging deliveries. we now also keep history of each delivery
attempt, accessible while still in the queue, and kept when a message is
retired. the queue webadmin pages now also have pagination, to show potentially
large history.
a queue of webhook calls is now managed too. failures are retried similar to
message deliveries. webhooks can also be saved to the retired list after
completing. also configurable per account.
messages can be sent with a "unique smtp mail from" address. this can only be
used if the domain is configured with a localpart catchall separator such as
"+". when enabled, a queued message gets assigned a random "fromid", which is
added after the separator when sending. when DSNs are returned, they can be
related to previously sent messages based on this fromid. in the future, we can
implement matching on the "envid" used in the smtp dsn extension, or on the
"message-id" of the message. using a fromid can be triggered by authenticating
with a login email address that is configured as enabling fromid.
suppression lists are automatically managed per account. if a delivery attempt
results in certain smtp errors, the destination address is added to the
suppression list. future messages queued for that recipient will immediately
fail without a delivery attempt. suppression lists protect your mail server
reputation.
submitted messages can carry "extra" data through the queue and webhooks for
outgoing deliveries. through webapi as a json object, through smtp submission
as message headers of the form "x-mox-extra-<key>: value".
to make it easy to test webapi/webhooks locally, the "localserve" mode actually
puts messages in the queue. when it's time to deliver, it still won't do a full
delivery attempt, but just delivers to the sender account. unless the recipient
address has a special form, simulating a failure to deliver.
admins now have more control over the queue. "hold rules" can be added to mark
newly queued messages as "on hold", pausing delivery. rules can be about
certain sender or recipient domains/addresses, or apply to all messages pausing
the entire queue. also useful for (local) testing.
new config options have been introduced. they are editable through the admin
and/or account web interfaces.
the webapi http endpoints are enabled for newly generated configs with the
quickstart, and in localserve. existing configurations must explicitly enable
the webapi in mox.conf.
gopherwatch.org was created to dogfood this code. it initially used just the
compose/smtpclient/imapclient mox packages to send messages and process
delivery feedback. it will get a config option to use the mox webapi/webhooks
instead. the gopherwatch code to use webapi/webhook is smaller and simpler, and
developing that shaped development of the mox webapi/webhooks.
for issue #31 by cuu508
2024-04-15 22:49:02 +03:00
2024-04-24 20:15:30 +03:00
// For each recipient, do final spam analysis and delivery.
for _ , rcpt := range c . recipients {
processRecipient ( rcpt )
2023-01-30 16:27:06 +03:00
}
// If all recipients failed to deliver, return an error.
if len ( c . recipients ) == len ( deliverErrors ) {
same := true
e0 := deliverErrors [ 0 ]
var serverError bool
var msgs [ ] string
major := 4
for _ , e := range deliverErrors {
serverError = serverError || ! e . userError
if e . code != e0 . code || e . secode != e0 . secode {
same = false
}
msgs = append ( msgs , e . errmsg )
if e . code >= 500 {
major = 5
}
}
if same {
xsmtpErrorf ( e0 . code , e0 . secode , ! serverError , "%s" , strings . Join ( msgs , "\n" ) )
}
// Not all failures had the same error. We'll return each error on a separate line.
lines := [ ] string { }
for _ , e := range deliverErrors {
s := fmt . Sprintf ( "%d %d.%s %s" , e . code , e . code / 100 , e . secode , e . errmsg )
lines = append ( lines , s )
}
code := smtp . C451LocalErr
secode := smtp . SeSys3Other0
if major == 5 {
code = smtp . C554TransactionFailed
}
lines = append ( lines , "multiple errors" )
2024-12-14 11:38:56 +03:00
xsmtpErrorf ( code , secode , ! serverError , "%s" , strings . Join ( lines , "\n" ) )
2023-01-30 16:27:06 +03:00
}
// Generate one DSN for all failed recipients.
if len ( deliverErrors ) > 0 {
now := time . Now ( )
dsnMsg := dsn . Message {
2024-03-31 16:23:53 +03:00
SMTPUTF8 : c . msgsmtputf8 ,
2023-07-23 18:56:39 +03:00
From : smtp . Path { Localpart : "postmaster" , IPDomain : deliverErrors [ 0 ] . rcptTo . IPDomain } ,
To : * c . mailFrom ,
Subject : "mail delivery failure" ,
2023-12-05 23:13:57 +03:00
MessageID : mox . MessageIDGen ( false ) ,
2023-07-23 18:56:39 +03:00
References : messageID ,
2023-01-30 16:27:06 +03:00
// Per-message details.
ReportingMTA : mox . Conf . Static . HostnameDomain . ASCII ,
ReceivedFromMTA : smtp . Ehlo { Name : c . hello , ConnIP : c . remoteIP } ,
ArrivalDate : now ,
}
if len ( deliverErrors ) > 1 {
dsnMsg . TextBody = "Multiple delivery failures occurred.\n\n"
}
for _ , e := range deliverErrors {
kind := "Permanent"
if e . code / 100 == 4 {
kind = "Transient"
}
dsnMsg . TextBody += fmt . Sprintf ( "%s delivery failure to:\n\n\t%s\n\nError:\n\n\t%s\n\n" , kind , e . errmsg , e . rcptTo . XString ( false ) )
rcpt := dsn . Recipient {
FinalRecipient : e . rcptTo ,
Action : dsn . Failed ,
Status : fmt . Sprintf ( "%d.%s" , e . code / 100 , e . secode ) ,
LastAttemptDate : now ,
}
dsnMsg . Recipients = append ( dsnMsg . Recipients , rcpt )
}
header , err := message . ReadHeaders ( bufio . NewReader ( & moxio . AtReader { R : dataFile } ) )
if err != nil {
c . log . Errorx ( "reading headers of incoming message for dsn, continuing dsn without headers" , err )
}
dsnMsg . Original = header
2023-03-12 12:38:02 +03:00
if Localserve {
c . log . Error ( "not queueing dsn for incoming delivery due to localserve" )
2023-12-05 23:13:57 +03:00
} else if err := queueDSN ( context . TODO ( ) , c . log , c , * c . mailFrom , dsnMsg , c . requireTLS != nil && * c . requireTLS ) ; err != nil {
2023-01-30 16:27:06 +03:00
metricServerErrors . WithLabelValues ( "queuedsn" ) . Inc ( )
c . log . Errorx ( "queuing DSN for incoming delivery, no DSN sent" , err )
}
}
c . transactionGood ++
c . transactionBad -- // Compensate for early earlier pessimistic increase.
c . rset ( )
c . writecodeline ( smtp . C250Completed , smtp . SeMailbox2Other0 , "it is done" , nil )
}
2024-04-24 20:15:30 +03:00
// Return whether msgFrom address is allowed to send a message to alias.
func aliasAllowedMsgFrom ( alias config . Alias , msgFrom smtp . Address ) bool {
for _ , aa := range alias . ParsedAddresses {
if aa . Address == msgFrom {
return true
}
}
lp , err := smtp . ParseLocalpart ( alias . LocalpartStr )
xcheckf ( err , "parsing alias localpart" )
if msgFrom == smtp . NewAddress ( lp , alias . Domain ) {
return alias . AllowMsgFrom
}
return alias . PostPublic
}
2023-01-30 16:27:06 +03:00
// ecode returns either ecode, or a more specific error based on err.
// For example, ecode can be turned from an "other system" error into a "mail
// system full" if the error indicates no disk space is available.
func errCodes ( code int , ecode string , err error ) codes {
switch {
case moxio . IsStorageSpace ( err ) :
switch ecode {
case smtp . SeMailbox2Other0 :
if code == smtp . C451LocalErr {
code = smtp . C452StorageFull
}
ecode = smtp . SeMailbox2Full2
case smtp . SeSys3Other0 :
if code == smtp . C451LocalErr {
code = smtp . C452StorageFull
}
ecode = smtp . SeSys3StorageFull1
}
}
return codes { code , ecode }
}
// ../rfc/5321:2079
func ( c * conn ) cmdRset ( p * parser ) {
// ../rfc/5321:2106
p . xend ( )
c . rset ( )
c . bwritecodeline ( smtp . C250Completed , smtp . SeOther00 , "all clear" , nil )
}
// ../rfc/5321:2108 ../rfc/5321:1222
func ( c * conn ) cmdVrfy ( p * parser ) {
// No EHLO/HELO needed.
// ../rfc/5321:2448
// ../rfc/5321:2119 ../rfc/6531:641
p . xspace ( )
p . xstring ( )
if p . space ( ) {
p . xtake ( "SMTPUTF8" )
}
p . xend ( )
// todo future: we could support vrfy and expn for submission? though would need to see if its rfc defines it.
// ../rfc/5321:4239
xsmtpUserErrorf ( smtp . C252WithoutVrfy , smtp . SePol7Other0 , "no verify but will try delivery" )
}
// ../rfc/5321:2135 ../rfc/5321:1272
func ( c * conn ) cmdExpn ( p * parser ) {
// No EHLO/HELO needed.
// ../rfc/5321:2448
// ../rfc/5321:2149 ../rfc/6531:645
p . xspace ( )
p . xstring ( )
if p . space ( ) {
p . xtake ( "SMTPUTF8" )
}
p . xend ( )
2024-04-24 20:15:30 +03:00
// todo: we could implement expn for local aliases for authenticated users, when members have permission to list. would anyone use it?
2023-01-30 16:27:06 +03:00
// ../rfc/5321:4239
xsmtpUserErrorf ( smtp . C252WithoutVrfy , smtp . SePol7Other0 , "no expand but will try delivery" )
}
// ../rfc/5321:2151
func ( c * conn ) cmdHelp ( p * parser ) {
// Let's not strictly parse the request for help. We are ignoring the text anyway.
// ../rfc/5321:2166
c . bwritecodeline ( smtp . C214Help , smtp . SeOther00 , "see rfc 5321 (smtp)" , nil )
}
// ../rfc/5321:2191
func ( c * conn ) cmdNoop ( p * parser ) {
// No idea why, but if an argument follows, it must adhere to the string ABNF production...
// ../rfc/5321:2203
if p . space ( ) {
p . xstring ( )
}
p . xend ( )
c . bwritecodeline ( smtp . C250Completed , smtp . SeOther00 , "alrighty" , nil )
}
// ../rfc/5321:2205
func ( c * conn ) cmdQuit ( p * parser ) {
// ../rfc/5321:2226
p . xend ( )
c . writecodeline ( smtp . C221Closing , smtp . SeOther00 , "okay thanks bye" , nil )
panic ( cleanClose )
}