2023-01-30 16:27:06 +03:00
// Package http provides HTTP listeners/servers, for
// autoconfiguration/autodiscovery, the account and admin web interface and
// MTA-STS policies.
package http
import (
2023-08-21 22:52:35 +03:00
"compress/gzip"
2023-03-01 00:12:27 +03:00
"context"
2023-01-30 16:27:06 +03:00
"crypto/tls"
"fmt"
2023-08-21 22:52:35 +03:00
"io"
2023-01-30 16:27:06 +03:00
golog "log"
"net"
"net/http"
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
"os"
2023-03-01 00:12:27 +03:00
"path"
"sort"
2023-01-30 16:27:06 +03:00
"strings"
"time"
_ "net/http/pprof"
2023-08-10 11:29:06 +03:00
"golang.org/x/exp/maps"
2023-03-01 00:12:27 +03:00
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
2023-01-30 16:27:06 +03:00
"github.com/prometheus/client_golang/prometheus/promhttp"
2023-03-04 02:49:02 +03:00
"github.com/mjl-/mox/autotls"
2023-01-30 16:27:06 +03:00
"github.com/mjl-/mox/config"
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
2023-03-01 00:12:27 +03:00
"github.com/mjl-/mox/ratelimit"
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
"github.com/mjl-/mox/webaccount"
"github.com/mjl-/mox/webadmin"
"github.com/mjl-/mox/webmail"
2023-01-30 16:27:06 +03:00
)
var xlog = mlog . New ( "http" )
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
var (
// metricRequest tracks performance (time to write response header) of server.
metricRequest = promauto . NewHistogramVec (
prometheus . HistogramOpts {
Name : "mox_httpserver_request_duration_seconds" ,
Help : "HTTP(s) server request with handler name, protocol, method, result codes, and duration until response status code is written, in seconds." ,
Buckets : [ ] float64 { 0.001 , 0.005 , 0.01 , 0.05 , 0.100 , 0.5 , 1 , 5 , 10 , 20 , 30 , 60 , 120 } ,
} ,
[ ] string {
"handler" , // Name from webhandler, can be empty.
2023-05-30 23:11:31 +03:00
"proto" , // "http", "https", "ws", "wss"
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
"method" , // "(unknown)" and otherwise only common verbs
"code" ,
} ,
)
// metricResponse tracks performance of entire request as experienced by users,
// which also depends on their connection speed, so not necessarily something you
// could act on.
metricResponse = promauto . NewHistogramVec (
prometheus . HistogramOpts {
Name : "mox_httpserver_response_duration_seconds" ,
Help : "HTTP(s) server response with handler name, protocol, method, result codes, and duration of entire response, in seconds." ,
Buckets : [ ] float64 { 0.001 , 0.005 , 0.01 , 0.05 , 0.100 , 0.5 , 1 , 5 , 10 , 20 , 30 , 60 , 120 } ,
} ,
[ ] string {
"handler" , // Name from webhandler, can be empty.
2023-05-30 23:11:31 +03:00
"proto" , // "http", "https", "ws", "wss"
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
"method" , // "(unknown)" and otherwise only common verbs
"code" ,
} ,
)
2023-03-01 00:12:27 +03:00
)
2023-07-05 13:54:24 +03:00
type responseWriterFlusher interface {
http . ResponseWriter
http . Flusher
}
2023-03-01 00:12:27 +03:00
// http.ResponseWriter that writes access log and tracks metrics at end of response.
type loggingWriter struct {
2023-07-05 13:54:24 +03:00
W responseWriterFlusher // Calls are forwarded.
2023-05-30 23:11:31 +03:00
Start time . Time
R * http . Request
WebsocketRequest bool // Whether request from was websocket.
2023-03-01 00:12:27 +03:00
2023-08-21 22:52:35 +03:00
// Set by router.
Handler string
Compress bool
2023-03-01 00:12:27 +03:00
// Set by handlers.
2023-05-30 23:11:31 +03:00
StatusCode int
2023-08-21 22:52:35 +03:00
Size int64 // Of data served to client, for non-websocket responses.
UncompressedSize int64 // Can be set by a handler that already serves compressed data, and we update it while compressing.
Gzip * gzip . Writer // Only set if we transparently compress within loggingWriter (static handlers handle compression themselves, with a cache).
2023-05-30 23:11:31 +03:00
Err 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
WebsocketResponse bool // If this was a successful websocket connection with backend.
SizeFromClient , SizeToClient int64 // Websocket data.
Fields [ ] mlog . Pair // Additional fields to log.
}
func ( w * loggingWriter ) AddField ( p mlog . Pair ) {
w . Fields = append ( w . Fields , p )
2023-03-01 00:12:27 +03:00
}
2023-07-05 13:54:24 +03:00
func ( w * loggingWriter ) Flush ( ) {
w . W . Flush ( )
}
2023-03-01 00:12:27 +03:00
func ( w * loggingWriter ) Header ( ) http . Header {
return w . W . Header ( )
}
2023-05-30 23:11:31 +03:00
// protocol, for logging.
func ( w * loggingWriter ) proto ( websocket bool ) string {
proto := "http"
if websocket {
proto = "ws"
}
if w . R . TLS != nil {
proto += "s"
}
return proto
}
2023-08-21 22:52:35 +03:00
func ( w * loggingWriter ) Write ( buf [ ] byte ) ( int , error ) {
if w . StatusCode == 0 {
w . WriteHeader ( http . StatusOK )
}
var n int
var err error
if w . Gzip == nil {
n , err = w . W . Write ( buf )
if n > 0 {
w . Size += int64 ( n )
}
} else {
// We flush after each write. Probably takes a few more bytes, but prevents any
// issues due to buffering.
// w.Gzip.Write updates w.Size with the compressed byte count.
n , err = w . Gzip . Write ( buf )
2023-09-12 22:22:08 +03:00
if err == nil {
2023-08-21 22:52:35 +03:00
err = w . Gzip . Flush ( )
}
if n > 0 {
w . UncompressedSize += int64 ( n )
}
}
if err != nil {
w . error ( err )
}
return n , err
}
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
func ( w * loggingWriter ) setStatusCode ( statusCode int ) {
if w . StatusCode != 0 {
return
}
w . StatusCode = statusCode
method := metricHTTPMethod ( w . R . Method )
2023-05-30 23:11:31 +03:00
metricRequest . WithLabelValues ( w . Handler , w . proto ( w . WebsocketRequest ) , method , fmt . Sprintf ( "%d" , w . StatusCode ) ) . Observe ( float64 ( time . Since ( w . Start ) ) / float64 ( time . Second ) )
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
}
2023-08-21 22:52:35 +03:00
// SetUncompressedSize is used through an interface by
// ../webmail/webmail.go:/WriteHeader, preventing an import cycle.
func ( w * loggingWriter ) SetUncompressedSize ( origSize int64 ) {
w . UncompressedSize = origSize
}
func ( w * loggingWriter ) WriteHeader ( statusCode int ) {
if w . StatusCode != 0 {
return
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
}
2023-08-21 22:52:35 +03:00
w . setStatusCode ( statusCode )
// We transparently gzip-compress responses for requests under these conditions, all must apply:
//
// - Enabled for handler (static handlers make their own decisions).
// - Not a websocket request.
// - Regular success responses (not errors, or partial content or redirects or "not modified", etc).
// - Not already compressed, or any other Content-Encoding header (including "identity").
// - Client accepts gzip encoded responses.
// - The response has a content-type that is compressible (text/*, */*+{json,xml}, and a few common files (e.g. json, xml, javascript).
if w . Compress && ! w . WebsocketRequest && statusCode == http . StatusOK && w . W . Header ( ) . Values ( "Content-Encoding" ) == nil && acceptsGzip ( w . R ) && compressibleContentType ( w . W . Header ( ) . Get ( "Content-Type" ) ) {
// todo: we should gather the first kb of data, see if it is compressible. if not, just return original. should set timer so we flush if it takes too long to gather 1kb. for smaller data we shouldn't compress at all.
// We track the gzipped output for the access log.
cw := countWriter { Writer : w . W , Size : & w . Size }
w . Gzip , _ = gzip . NewWriterLevel ( cw , gzip . BestSpeed )
w . W . Header ( ) . Set ( "Content-Encoding" , "gzip" )
w . W . Header ( ) . Del ( "Content-Length" ) // No longer valid, set again for small responses by net/http.
2023-03-01 00:12:27 +03:00
}
2023-08-21 22:52:35 +03:00
w . W . WriteHeader ( statusCode )
}
func acceptsGzip ( r * http . Request ) bool {
s := r . Header . Get ( "Accept-Encoding" )
t := strings . Split ( s , "," )
for _ , e := range t {
e = strings . TrimSpace ( e )
tt := strings . Split ( e , ";" )
if len ( tt ) > 1 && t [ 1 ] == "q=0" {
continue
}
if tt [ 0 ] == "gzip" {
return true
}
2023-03-01 00:12:27 +03:00
}
2023-08-21 22:52:35 +03:00
return false
2023-03-01 00:12:27 +03:00
}
2023-08-21 22:52:35 +03:00
var compressibleTypes = map [ string ] bool {
"application/csv" : true ,
"application/javascript" : true ,
"application/json" : true ,
"application/x-javascript" : true ,
"application/xml" : true ,
"image/vnd.microsoft.icon" : true ,
"image/x-icon" : true ,
"font/ttf" : true ,
"font/eot" : true ,
"font/otf" : true ,
"font/opentype" : true ,
}
func compressibleContentType ( ct string ) bool {
ct = strings . SplitN ( ct , ";" , 2 ) [ 0 ]
ct = strings . TrimSpace ( ct )
ct = strings . ToLower ( ct )
if compressibleTypes [ ct ] {
return true
}
t , st , _ := strings . Cut ( ct , "/" )
return t == "text" || strings . HasSuffix ( st , "+json" ) || strings . HasSuffix ( st , "+xml" )
}
func compressibleContent ( f * os . File ) bool {
// We don't want to store many small files. They take up too much disk overhead.
if fi , err := f . Stat ( ) ; err != nil || fi . Size ( ) < 1024 || fi . Size ( ) > 10 * 1024 * 1024 {
return false
}
buf := make ( [ ] byte , 512 )
n , err := f . ReadAt ( buf , 0 )
if err != nil && err != io . EOF {
return false
}
ct := http . DetectContentType ( buf [ : n ] )
return compressibleContentType ( ct )
}
type countWriter struct {
Writer io . Writer
Size * int64
}
func ( w countWriter ) Write ( buf [ ] byte ) ( int , error ) {
n , err := w . Writer . Write ( buf )
if n > 0 {
* w . Size += int64 ( n )
}
return n , err
2023-03-01 00:12:27 +03:00
}
var tlsVersions = map [ uint16 ] string {
tls . VersionTLS10 : "tls1.0" ,
tls . VersionTLS11 : "tls1.1" ,
tls . VersionTLS12 : "tls1.2" ,
tls . VersionTLS13 : "tls1.3" ,
}
func metricHTTPMethod ( method string ) string {
// https://www.iana.org/assignments/http-methods/http-methods.xhtml
method = strings . ToLower ( method )
switch method {
case "acl" , "baseline-control" , "bind" , "checkin" , "checkout" , "connect" , "copy" , "delete" , "get" , "head" , "label" , "link" , "lock" , "merge" , "mkactivity" , "mkcalendar" , "mkcol" , "mkredirectref" , "mkworkspace" , "move" , "options" , "orderpatch" , "patch" , "post" , "pri" , "propfind" , "proppatch" , "put" , "rebind" , "report" , "search" , "trace" , "unbind" , "uncheckout" , "unlink" , "unlock" , "update" , "updateredirectref" , "version-control" :
return method
}
return "(other)"
}
2023-05-30 23:11:31 +03:00
func ( w * loggingWriter ) error ( err error ) {
if w . Err == nil {
w . Err = err
}
}
2023-03-01 00:12:27 +03:00
func ( w * loggingWriter ) Done ( ) {
2023-08-21 22:52:35 +03:00
if w . Err == nil && w . Gzip != nil {
if err := w . Gzip . Close ( ) ; err != nil {
w . error ( err )
}
}
2023-03-01 00:12:27 +03:00
method := metricHTTPMethod ( w . R . Method )
2023-05-30 23:11:31 +03:00
metricResponse . WithLabelValues ( w . Handler , w . proto ( w . WebsocketResponse ) , method , fmt . Sprintf ( "%d" , w . StatusCode ) ) . Observe ( float64 ( time . Since ( w . Start ) ) / float64 ( time . Second ) )
2023-03-01 00:12:27 +03:00
tlsinfo := "plain"
if w . R . TLS != nil {
if v , ok := tlsVersions [ w . R . TLS . Version ] ; ok {
tlsinfo = v
} else {
tlsinfo = "(other)"
}
}
2023-05-30 23:11:31 +03:00
err := w . Err
2023-03-21 11:25:49 +03:00
if err == nil {
err = w . R . Context ( ) . Err ( )
}
2023-05-30 23:11:31 +03:00
fields := [ ] mlog . Pair {
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
mlog . Field ( "httpaccess" , "" ) ,
mlog . Field ( "handler" , w . Handler ) ,
mlog . Field ( "method" , method ) ,
mlog . Field ( "url" , w . R . URL ) ,
mlog . Field ( "host" , w . R . Host ) ,
mlog . Field ( "duration" , time . Since ( w . Start ) ) ,
mlog . Field ( "statuscode" , w . StatusCode ) ,
mlog . Field ( "proto" , strings . ToLower ( w . R . Proto ) ) ,
mlog . Field ( "remoteaddr" , w . R . RemoteAddr ) ,
mlog . Field ( "tlsinfo" , tlsinfo ) ,
mlog . Field ( "useragent" , w . R . Header . Get ( "User-Agent" ) ) ,
mlog . Field ( "referrr" , w . R . Header . Get ( "Referrer" ) ) ,
2023-05-30 23:11:31 +03:00
}
if w . WebsocketRequest {
fields = append ( fields ,
mlog . Field ( "websocketrequest" , true ) ,
)
}
if w . WebsocketResponse {
fields = append ( fields ,
mlog . Field ( "websocket" , true ) ,
mlog . Field ( "sizetoclient" , w . SizeToClient ) ,
mlog . Field ( "sizefromclient" , w . SizeFromClient ) ,
)
2023-08-21 22:52:35 +03:00
} else if w . UncompressedSize > 0 {
fields = append ( fields ,
mlog . Field ( "size" , w . Size ) ,
mlog . Field ( "uncompressedsize" , w . UncompressedSize ) ,
)
2023-05-30 23:11:31 +03:00
} else {
fields = append ( fields ,
mlog . Field ( "size" , w . Size ) ,
)
}
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
fields = append ( fields , w . Fields ... )
2023-05-30 23:11:31 +03:00
xlog . WithContext ( w . R . Context ( ) ) . Debugx ( "http request" , err , fields ... )
2023-03-01 00:12:27 +03:00
}
2023-01-30 16:27:06 +03:00
// Set some http headers that should prevent potential abuse. Better safe than sorry.
2023-03-12 13:52:15 +03:00
func safeHeaders ( fn http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2023-01-30 16:27:06 +03:00
h := w . Header ( )
h . Set ( "X-Frame-Options" , "deny" )
h . Set ( "X-Content-Type-Options" , "nosniff" )
h . Set ( "Content-Security-Policy" , "default-src 'self' 'unsafe-inline' data:" )
h . Set ( "Referrer-Policy" , "same-origin" )
2023-03-12 13:52:15 +03:00
fn . ServeHTTP ( w , r )
} )
2023-01-30 16:27:06 +03:00
}
2023-03-01 00:12:27 +03:00
// Built-in handlers, e.g. mta-sts and autoconfig.
type pathHandler struct {
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
Name string // For logging/metrics.
HostMatch func ( dom dns . Domain ) bool // If not nil, called to see if domain of requests matches. Only called if requested host is a valid domain.
Path string // Path to register, like on http.ServeMux.
2023-03-12 13:52:15 +03:00
Handler http . Handler
2023-03-01 00:12:27 +03:00
}
type serve struct {
2023-03-10 19:55:37 +03:00
Kinds [ ] string // Type of handler and protocol (e.g. acme-tls-alpn-01, account-http, admin-https).
2023-03-01 00:12:27 +03:00
TLSConfig * tls . Config
PathHandlers [ ] pathHandler // Sorted, longest first.
Webserver bool // Whether serving WebHandler. PathHandlers are always evaluated before WebHandlers.
}
2023-03-12 13:52:15 +03:00
// Handle registers a named handler for a path and optional host. If path ends with
// a slash, it is used as prefix match, otherwise a full path match is required. If
// hostOpt is set, only requests to those host are handled by this handler.
func ( s * serve ) Handle ( name string , hostMatch func ( dns . Domain ) bool , path string , fn http . Handler ) {
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
s . PathHandlers = append ( s . PathHandlers , pathHandler { name , hostMatch , path , fn } )
2023-03-01 00:12:27 +03:00
}
var (
limiterConnectionrate = & ratelimit . Limiter {
WindowLimits : [ ] ratelimit . WindowLimit {
{
Window : time . Minute ,
Limits : [ ... ] int64 { 1000 , 3000 , 9000 } ,
} ,
{
Window : time . Hour ,
Limits : [ ... ] int64 { 5000 , 15000 , 45000 } ,
} ,
} ,
}
)
// ServeHTTP is the starting point for serving HTTP requests. It dispatches to the
// right pathHandler or WebHandler, and it generates access logs and tracks
// metrics.
func ( s * serve ) ServeHTTP ( xw http . ResponseWriter , r * http . Request ) {
now := time . Now ( )
// Rate limiting as early as possible.
ipstr , _ , err := net . SplitHostPort ( r . RemoteAddr )
if err != nil {
xlog . Debugx ( "split host:port client remoteaddr" , err , mlog . Field ( "remoteaddr" , r . RemoteAddr ) )
} else if ip := net . ParseIP ( ipstr ) ; ip == nil {
xlog . Debug ( "parsing ip for client remoteaddr" , mlog . Field ( "remoteaddr" , r . RemoteAddr ) )
} else if ! limiterConnectionrate . Add ( ip , now , 1 ) {
method := metricHTTPMethod ( r . Method )
proto := "http"
if r . TLS != nil {
proto = "https"
}
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
metricRequest . WithLabelValues ( "(ratelimited)" , proto , method , "429" ) . Observe ( 0 )
2023-03-01 00:12:27 +03:00
// No logging, that's just noise.
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
http . Error ( xw , "429 - too many auth attempts" , http . StatusTooManyRequests )
2023-03-01 00:12:27 +03:00
return
}
ctx := context . WithValue ( r . Context ( ) , mlog . CidKey , mox . Cid ( ) )
r = r . WithContext ( ctx )
2023-07-05 13:54:24 +03:00
wf , ok := xw . ( responseWriterFlusher )
if ! ok {
http . Error ( xw , "500 - internal server error - cannot access underlying connection" + recvid ( r ) , http . StatusInternalServerError )
return
}
2023-03-01 00:12:27 +03:00
nw := & loggingWriter {
2023-07-05 13:54:24 +03:00
W : wf ,
2023-03-01 00:12:27 +03:00
Start : now ,
R : r ,
}
defer nw . Done ( )
// Cleanup path, removing ".." and ".". Keep any trailing slash.
trailingPath := strings . HasSuffix ( r . URL . Path , "/" )
if r . URL . Path == "" {
r . URL . Path = "/"
}
r . URL . Path = path . Clean ( r . URL . Path )
if r . URL . Path == "." {
r . URL . Path = "/"
}
if trailingPath && ! strings . HasSuffix ( r . URL . Path , "/" ) {
r . URL . Path += "/"
}
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
var dom dns . Domain
host := r . Host
nhost , _ , err := net . SplitHostPort ( host )
if err == nil {
host = nhost
}
// host could be an IP, some handles may match, not an error.
dom , domErr := dns . ParseDomain ( host )
2023-03-01 00:12:27 +03:00
for _ , h := range s . PathHandlers {
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
if h . HostMatch != nil && ( domErr != nil || ! h . HostMatch ( dom ) ) {
continue
}
2023-03-01 00:12:27 +03:00
if r . URL . Path == h . Path || strings . HasSuffix ( h . Path , "/" ) && strings . HasPrefix ( r . URL . Path , h . Path ) {
nw . Handler = h . Name
2023-08-21 22:52:35 +03:00
nw . Compress = true
2023-03-12 13:52:15 +03:00
h . Handler . ServeHTTP ( nw , r )
2023-03-01 00:12:27 +03:00
return
}
}
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
if s . Webserver && domErr == nil {
if WebHandle ( nw , r , dom ) {
2023-03-01 00:12:27 +03:00
return
}
}
nw . Handler = "(nomatch)"
http . NotFound ( nw , r )
}
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 binds to sockets for HTTP listeners, including those required for ACME to
// generate TLS certificates. It stores the listeners so Serve can start serving them.
func Listen ( ) {
2023-07-02 15:37:48 +03:00
redirectToTrailingSlash := func ( srv * serve , name , path string ) {
// Helpfully redirect user to version with ending slash.
if path != "/" && strings . HasSuffix ( path , "/" ) {
handler := safeHeaders ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
http . Redirect ( w , r , path , http . StatusSeeOther )
} ) )
srv . Handle ( name , nil , path [ : len ( path ) - 1 ] , handler )
}
}
2023-08-10 11:29:06 +03:00
// Initialize listeners in deterministic order for the same potential error
// messages.
names := maps . Keys ( mox . Conf . Static . Listeners )
sort . Strings ( names )
for _ , name := range names {
l := mox . Conf . Static . Listeners [ name ]
2023-03-01 00:12:27 +03:00
portServe := map [ int ] * serve { }
2023-01-30 16:27:06 +03:00
2023-03-01 00:12:27 +03:00
var ensureServe func ( https bool , port int , kind string ) * serve
ensureServe = func ( https bool , port int , kind string ) * serve {
s := portServe [ port ]
if s == nil {
s = & serve { nil , nil , nil , false }
portServe [ port ] = s
2023-01-30 16:27:06 +03:00
}
2023-03-01 00:12:27 +03:00
s . Kinds = append ( s . Kinds , kind )
2023-02-18 18:53:06 +03:00
if https && l . TLS . ACME != "" {
2023-03-01 00:12:27 +03:00
s . TLSConfig = l . TLS . ACMEConfig
2023-01-30 16:27:06 +03:00
} else if https {
2023-03-01 00:12:27 +03:00
s . TLSConfig = l . TLS . Config
2023-01-30 16:27:06 +03:00
if l . TLS . ACME != "" {
2023-03-01 00:12:27 +03:00
tlsport := config . Port ( mox . Conf . Static . ACME [ l . TLS . ACME ] . Port , 443 )
ensureServe ( true , tlsport , "acme-tls-alpn-01" )
2023-01-30 16:27:06 +03:00
}
}
return s
}
2023-02-23 01:22:42 +03:00
if l . TLS != nil && l . TLS . ACME != "" && ( l . SMTP . Enabled && ! l . SMTP . NoSTARTTLS || l . Submissions . Enabled || l . IMAPS . Enabled ) {
2023-03-01 00:12:27 +03:00
port := config . Port ( mox . Conf . Static . ACME [ l . TLS . ACME ] . Port , 443 )
2023-03-10 19:55:37 +03:00
ensureServe ( true , port , "acme-tls-alpn-01" )
2023-01-30 16:27:06 +03:00
}
2023-02-13 15:53:47 +03:00
if l . AccountHTTP . Enabled {
2023-03-01 00:12:27 +03:00
port := config . Port ( l . AccountHTTP . Port , 80 )
2023-03-12 13:52:15 +03:00
path := "/"
if l . AccountHTTP . Path != "" {
path = l . AccountHTTP . Path
}
2023-03-20 14:49:40 +03:00
srv := ensureServe ( false , port , "account-http at " + path )
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
handler := safeHeaders ( http . StripPrefix ( path [ : len ( path ) - 1 ] , http . HandlerFunc ( webaccount . Handle ) ) )
2023-03-12 13:52:15 +03:00
srv . Handle ( "account" , nil , path , handler )
2023-07-02 15:37:48 +03:00
redirectToTrailingSlash ( srv , "account" , path )
2023-02-13 15:53:47 +03:00
}
if l . AccountHTTPS . Enabled {
2023-03-01 00:12:27 +03:00
port := config . Port ( l . AccountHTTPS . Port , 443 )
2023-03-12 13:52:15 +03:00
path := "/"
if l . AccountHTTPS . Path != "" {
path = l . AccountHTTPS . Path
}
2023-03-20 14:49:40 +03:00
srv := ensureServe ( true , port , "account-https at " + path )
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
handler := safeHeaders ( http . StripPrefix ( path [ : len ( path ) - 1 ] , http . HandlerFunc ( webaccount . Handle ) ) )
2023-03-12 13:52:15 +03:00
srv . Handle ( "account" , nil , path , handler )
2023-07-02 15:37:48 +03:00
redirectToTrailingSlash ( srv , "account" , path )
2023-02-13 15:53:47 +03:00
}
2023-01-30 16:27:06 +03:00
if l . AdminHTTP . Enabled {
2023-03-01 00:12:27 +03:00
port := config . Port ( l . AdminHTTP . Port , 80 )
2023-03-12 13:52:15 +03:00
path := "/admin/"
if l . AdminHTTP . Path != "" {
path = l . AdminHTTP . Path
2023-02-13 15:53:47 +03:00
}
2023-03-20 14:49:40 +03:00
srv := ensureServe ( false , port , "admin-http at " + path )
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
handler := safeHeaders ( http . StripPrefix ( path [ : len ( path ) - 1 ] , http . HandlerFunc ( webadmin . Handle ) ) )
2023-03-12 13:52:15 +03:00
srv . Handle ( "admin" , nil , path , handler )
2023-07-02 15:37:48 +03:00
redirectToTrailingSlash ( srv , "admin" , path )
2023-01-30 16:27:06 +03:00
}
if l . AdminHTTPS . Enabled {
2023-03-01 00:12:27 +03:00
port := config . Port ( l . AdminHTTPS . Port , 443 )
2023-03-12 13:52:15 +03:00
path := "/admin/"
if l . AdminHTTPS . Path != "" {
path = l . AdminHTTPS . Path
2023-02-13 15:53:47 +03:00
}
2023-03-20 14:49:40 +03:00
srv := ensureServe ( true , port , "admin-https at " + path )
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
handler := safeHeaders ( http . StripPrefix ( path [ : len ( path ) - 1 ] , http . HandlerFunc ( webadmin . Handle ) ) )
2023-03-12 13:52:15 +03:00
srv . Handle ( "admin" , nil , path , handler )
2023-07-02 15:37:48 +03:00
redirectToTrailingSlash ( srv , "admin" , path )
2023-01-30 16:27:06 +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
maxMsgSize := l . SMTPMaxMessageSize
if maxMsgSize == 0 {
maxMsgSize = config . DefaultMaxMsgSize
}
if l . WebmailHTTP . Enabled {
port := config . Port ( l . WebmailHTTP . Port , 80 )
path := "/webmail/"
if l . WebmailHTTP . Path != "" {
path = l . WebmailHTTP . Path
}
srv := ensureServe ( false , port , "webmail-http at " + path )
srv . Handle ( "webmail" , nil , path , http . StripPrefix ( path [ : len ( path ) - 1 ] , http . HandlerFunc ( webmail . Handler ( maxMsgSize ) ) ) )
redirectToTrailingSlash ( srv , "webmail" , path )
}
if l . WebmailHTTPS . Enabled {
port := config . Port ( l . WebmailHTTPS . Port , 443 )
path := "/webmail/"
if l . WebmailHTTPS . Path != "" {
path = l . WebmailHTTPS . Path
}
srv := ensureServe ( true , port , "webmail-https at " + path )
srv . Handle ( "webmail" , nil , path , http . StripPrefix ( path [ : len ( path ) - 1 ] , http . HandlerFunc ( webmail . Handler ( maxMsgSize ) ) ) )
redirectToTrailingSlash ( srv , "webmail" , path )
}
2023-01-30 16:27:06 +03:00
if l . MetricsHTTP . Enabled {
2023-03-01 00:12:27 +03:00
port := config . Port ( l . MetricsHTTP . Port , 8010 )
srv := ensureServe ( false , port , "metrics-http" )
2023-03-12 13:52:15 +03:00
srv . Handle ( "metrics" , nil , "/metrics" , safeHeaders ( promhttp . Handler ( ) ) )
srv . Handle ( "metrics" , nil , "/" , safeHeaders ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2023-01-30 16:27:06 +03:00
if r . URL . Path != "/" {
http . NotFound ( w , r )
return
} else if r . Method != "GET" {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
return
}
w . Header ( ) . Set ( "Content-Type" , "text/html" )
2023-10-24 14:11:04 +03:00
fmt . Fprint ( w , ` <html><body>see <a href="metrics">metrics</a></body></html> ` )
2023-03-12 13:52:15 +03:00
} ) ) )
2023-01-30 16:27:06 +03:00
}
if l . AutoconfigHTTPS . Enabled {
2023-03-01 00:12:27 +03:00
port := config . Port ( l . AutoconfigHTTPS . Port , 443 )
srv := ensureServe ( ! l . AutoconfigHTTPS . NonTLS , port , "autoconfig-https" )
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
autoconfigMatch := func ( dom dns . Domain ) bool {
2023-09-23 13:05:40 +03:00
// Thunderbird requests an autodiscovery URL at the email address domain name, so
// autoconfig prefix is optional.
if strings . HasPrefix ( dom . ASCII , "autoconfig." ) {
dom . ASCII = strings . TrimPrefix ( dom . ASCII , "autoconfig." )
dom . Unicode = strings . TrimPrefix ( dom . Unicode , "autoconfig." )
}
2023-10-13 09:51:02 +03:00
// Autodiscovery uses a SRV record. It shouldn't point to a CNAME. So we directly
// use the mail server's host name.
if dom == mox . Conf . Static . HostnameDomain || dom == mox . Conf . Static . Listeners [ "public" ] . HostnameDomain {
return true
}
2023-09-23 13:05:40 +03:00
_ , ok := mox . Conf . Domain ( dom )
return ok
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
}
2023-03-12 13:52:15 +03:00
srv . Handle ( "autoconfig" , autoconfigMatch , "/mail/config-v1.1.xml" , safeHeaders ( http . HandlerFunc ( autoconfHandle ) ) )
srv . Handle ( "autodiscover" , autoconfigMatch , "/autodiscover/autodiscover.xml" , safeHeaders ( http . HandlerFunc ( autodiscoverHandle ) ) )
2023-09-23 13:05:40 +03:00
srv . Handle ( "mobileconfig" , autoconfigMatch , "/profile.mobileconfig" , safeHeaders ( http . HandlerFunc ( mobileconfigHandle ) ) )
srv . Handle ( "mobileconfigqrcodepng" , autoconfigMatch , "/profile.mobileconfig.qrcode.png" , safeHeaders ( http . HandlerFunc ( mobileconfigQRCodeHandle ) ) )
2023-01-30 16:27:06 +03:00
}
if l . MTASTSHTTPS . Enabled {
2023-03-01 00:12:27 +03:00
port := config . Port ( l . MTASTSHTTPS . Port , 443 )
2023-05-03 17:26:04 +03:00
srv := ensureServe ( ! l . MTASTSHTTPS . NonTLS , port , "mtasts-https" )
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
mtastsMatch := func ( dom dns . Domain ) bool {
// todo: may want to check this against the configured domains, could in theory be just a webserver.
return strings . HasPrefix ( dom . ASCII , "mta-sts." )
}
2023-03-12 13:52:15 +03:00
srv . Handle ( "mtasts" , mtastsMatch , "/.well-known/mta-sts.txt" , safeHeaders ( http . HandlerFunc ( mtastsPolicyHandle ) ) )
2023-01-30 16:27:06 +03:00
}
if l . PprofHTTP . Enabled {
// Importing net/http/pprof registers handlers on the default serve mux.
port := config . Port ( l . PprofHTTP . Port , 8011 )
if _ , ok := portServe [ port ] ; ok {
xlog . Fatal ( "cannot serve pprof on same endpoint as other http services" )
}
2023-03-01 00:12:27 +03:00
srv := & serve { [ ] string { "pprof-http" } , nil , nil , false }
portServe [ port ] = srv
2023-03-12 13:52:15 +03:00
srv . Handle ( "pprof" , nil , "/" , http . DefaultServeMux )
2023-03-01 00:12:27 +03:00
}
if l . WebserverHTTP . Enabled {
port := config . Port ( l . WebserverHTTP . Port , 80 )
srv := ensureServe ( false , port , "webserver-http" )
srv . Webserver = true
}
if l . WebserverHTTPS . Enabled {
port := config . Port ( l . WebserverHTTPS . Port , 443 )
srv := ensureServe ( true , port , "webserver-https" )
srv . Webserver = true
2023-01-30 16:27:06 +03:00
}
if l . TLS != nil && l . TLS . ACME != "" {
m := mox . Conf . Static . ACME [ l . TLS . ACME ] . Manager
2023-03-10 19:55:37 +03:00
// If we are listening on port 80 for plain http, also register acme http-01
// validation handler.
if srv , ok := portServe [ 80 ] ; ok && srv . TLSConfig == nil {
srv . Kinds = append ( srv . Kinds , "acme-http-01" )
2023-03-12 13:52:15 +03:00
srv . Handle ( "acme-http-01" , nil , "/.well-known/acme-challenge/" , m . Manager . HTTPHandler ( nil ) )
2023-03-10 19:55:37 +03:00
}
2023-03-04 02:49:02 +03:00
hosts := map [ dns . Domain ] struct { } {
mox . Conf . Static . HostnameDomain : { } ,
}
2023-01-30 16:27:06 +03:00
if l . HostnameDomain . ASCII != "" {
2023-03-04 02:49:02 +03:00
hosts [ l . HostnameDomain ] = struct { } { }
2023-01-30 16:27:06 +03:00
}
2023-11-14 02:26:18 +03:00
// All domains are served on all listeners. Gather autoconfig hostnames to ensure
// presence of TLS certificates for.
2023-03-04 02:49:02 +03:00
for _ , name := range mox . Conf . Domains ( ) {
2023-11-14 02:26:18 +03:00
if dom , err := dns . ParseDomain ( name ) ; err != nil {
xlog . Errorx ( "parsing domain from config" , err )
} else if d , _ := mox . Conf . Domain ( dom ) ; d . DMARC != nil && d . DMARC . Domain != "" && d . DMARC . DNSDomain != dom {
// Do not gather autoconfig name if this domain is configured to process reports
// for domains hosted elsewhere.
continue
}
autoconfdom , err := dns . ParseDomain ( "autoconfig." + name )
2023-03-04 02:49:02 +03:00
if err != nil {
xlog . Errorx ( "parsing domain from config for autoconfig" , err )
} else {
2023-11-14 02:26:18 +03:00
hosts [ autoconfdom ] = struct { } { }
2023-01-30 16:27:06 +03:00
}
2023-03-04 02:49:02 +03:00
}
2023-01-30 16:27:06 +03:00
2023-03-04 02:49:02 +03:00
ensureManagerHosts [ m ] = hosts
2023-01-30 16:27:06 +03:00
}
2023-08-10 11:29:06 +03:00
ports := maps . Keys ( portServe )
sort . Ints ( ports )
for _ , port := range ports {
srv := portServe [ port ]
improve webserver, add domain redirects (aliases), add tests and admin page ui to manage the config
- make builtin http handlers serve on specific domains, such as for mta-sts, so
e.g. /.well-known/mta-sts.txt isn't served on all domains.
- add logging of a few more fields in access logging.
- small tweaks/bug fixes in webserver request handling.
- add config option for redirecting entire domains to another (common enough).
- split httpserver metric into two: one for duration until writing header (i.e.
performance of server), another for duration until full response is sent to
client (i.e. performance as perceived by users).
- add admin ui, a new page for managing the configs. after making changes
and hitting "save", the changes take effect immediately. the page itself
doesn't look very well-designed (many input fields, makes it look messy). i
have an idea to improve it (explained in admin.html as todo) by making the
layout look just like the config file. not urgent though.
i've already changed my websites/webapps over.
the idea of adding a webserver is to take away a (the) reason for folks to want
to complicate their mox setup by running an other webserver on the same machine.
i think the current webserver implementation can already serve most common use
cases. with a few more tweaks (feedback needed!) we should be able to get to 95%
of the use cases. the reverse proxy can take care of the remaining 5%.
nevertheless, a next step is still to change the quickstart to make it easier
for folks to run with an existing webserver, with existing tls certs/keys.
that's how this relates to issue #5.
2023-03-02 20:15:54 +03:00
sort . Slice ( srv . PathHandlers , func ( i , j int ) bool {
a := srv . PathHandlers [ i ] . Path
b := srv . PathHandlers [ j ] . Path
if len ( a ) == len ( b ) {
// For consistent order.
return a < b
}
// Longest paths first.
return len ( a ) > len ( b )
} )
2023-01-30 16:27:06 +03:00
for _ , ip := range l . IPs {
2023-03-01 00:12:27 +03:00
listen1 ( ip , port , srv . TLSConfig , name , srv . Kinds , srv )
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
// functions to be launched in goroutine that will serve on a listener.
var servers [ ] func ( )
2023-03-10 19:55:37 +03:00
// We'll explicitly ensure these TLS certs exist (e.g. are created with ACME)
// immediately after startup. We only do so for our explicit listener hostnames,
// not for mta-sts DNS records, it can be requested on demand (perhaps never). We
// do request autoconfig, otherwise clients may run into their timeouts waiting for
// the certificate to be given during the first https connection.
var ensureManagerHosts = map [ * autotls . Manager ] map [ dns . Domain ] struct { } { }
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 prepares a listener, and adds it to "servers", to be launched (if not running as root) through Serve.
2023-03-01 00:12:27 +03:00
func listen1 ( ip string , port int , tlsConfig * tls . Config , name string , kinds [ ] string , handler http . Handler ) {
2023-01-30 16:27:06 +03:00
addr := net . JoinHostPort ( ip , fmt . Sprintf ( "%d" , port ) )
var protocol string
var ln net . Listener
var err error
if tlsConfig == nil {
protocol = "http"
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 {
xlog . Print ( "http listener" , mlog . Field ( "name" , name ) , mlog . Field ( "kinds" , strings . Join ( kinds , "," ) ) , mlog . Field ( "address" , addr ) )
}
ln , err = mox . Listen ( mox . Network ( ip ) , addr )
2023-01-30 16:27:06 +03:00
if err != nil {
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
xlog . Fatalx ( "http: listen" , err , mlog . Field ( "addr" , addr ) )
2023-01-30 16:27:06 +03:00
}
} else {
protocol = "https"
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 {
xlog . Print ( "https listener" , mlog . Field ( "name" , name ) , mlog . Field ( "kinds" , strings . Join ( kinds , "," ) ) , mlog . Field ( "address" , addr ) )
}
ln , err = mox . Listen ( mox . Network ( ip ) , addr )
2023-01-30 16:27:06 +03:00
if err != nil {
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
xlog . Fatalx ( "https: listen" , err , mlog . Field ( "addr" , addr ) )
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
ln = tls . NewListener ( ln , tlsConfig )
2023-01-30 16:27:06 +03:00
}
server := & http . Server {
2023-03-28 18:16:05 +03:00
Handler : handler ,
TLSConfig : tlsConfig ,
ReadHeaderTimeout : 30 * time . Second ,
IdleTimeout : 65 * time . Second , // Chrome closes connections after 60 seconds, firefox after 115 seconds.
ErrorLog : golog . New ( mlog . ErrWriter ( xlog . Fields ( mlog . Field ( "pkg" , "net/http" ) ) , mlog . LevelInfo , protocol + " error" ) , "" , 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
serve := func ( ) {
2023-01-30 16:27:06 +03:00
err := server . Serve ( ln )
xlog . Fatalx ( protocol + ": serve" , err )
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 the initialized listeners.
func Serve ( ) {
2023-08-21 22:52:35 +03:00
loadStaticGzipCache ( mox . DataDirPath ( "tmp/httpstaticcompresscache" ) , 512 * 1024 * 1024 )
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
go webadmin . ManageAuthCache ( )
go webaccount . ImportManage ( )
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
for _ , serve := range servers {
go serve ( )
}
servers = nil
2023-03-04 02:49:02 +03:00
go func ( ) {
time . Sleep ( 1 * time . Second )
i := 0
for m , hosts := range ensureManagerHosts {
for host := range hosts {
if i >= 10 {
// Just in case someone adds quite some domains to their config. We don't want to
// hit any ACME rate limits.
return
}
if i > 0 {
// Sleep just a little. We don't want to hammer our ACME provider, e.g. Let's Encrypt.
time . Sleep ( 10 * time . Second )
}
i ++
hello := & tls . ClientHelloInfo {
ServerName : host . ASCII ,
// Make us fetch an ECDSA P256 cert.
// We add TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 to get around the ecDSA check in autocert.
CipherSuites : [ ] uint16 { tls . TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 , tls . TLS_AES_128_GCM_SHA256 } ,
SupportedCurves : [ ] tls . CurveID { tls . CurveP256 } ,
SignatureSchemes : [ ] tls . SignatureScheme { tls . ECDSAWithP256AndSHA256 } ,
SupportedVersions : [ ] uint16 { tls . VersionTLS13 } ,
}
xlog . Print ( "ensuring certificate availability" , mlog . Field ( "hostname" , host ) )
if _ , err := m . Manager . GetCertificate ( hello ) ; err != nil {
xlog . Errorx ( "requesting automatic certificate" , err , mlog . Field ( "hostname" , host ) )
}
}
}
} ( )
2023-01-30 16:27:06 +03:00
}