2023-01-30 16:27:06 +03:00
package main
import (
"bytes"
"context"
2023-02-05 23:25:48 +03:00
"errors"
2023-01-30 16:27:06 +03:00
"fmt"
"log"
"net"
2023-03-05 17:40:26 +03:00
"net/url"
2023-01-30 16:27:06 +03:00
"os"
"path/filepath"
"runtime"
2023-02-05 23:25:48 +03:00
"sort"
2023-01-30 16:27:06 +03:00
"strings"
"time"
_ "embed"
"golang.org/x/crypto/bcrypt"
"github.com/mjl-/sconf"
"github.com/mjl-/mox/config"
"github.com/mjl-/mox/dns"
2023-03-05 17:40:26 +03:00
"github.com/mjl-/mox/dnsbl"
2023-01-30 16:27:06 +03:00
"github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/store"
)
//go:embed mox.service
var moxService string
func pwgen ( ) string {
rand := mox . NewRand ( )
chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*-_;:,<.>/"
s := ""
for i := 0 ; i < 12 ; i ++ {
s += string ( chars [ rand . Intn ( len ( chars ) ) ] )
}
return s
}
func cmdQuickstart ( c * cmd ) {
2023-03-05 17:40:26 +03:00
c . params = "[-existing-webserver] [-hostname host] user@domain [user | uid]"
2023-01-30 16:27:06 +03:00
c . help = ` Quickstart generates configuration files and prints instructions to quickly set up a mox instance .
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
Quickstart writes configuration files , prints initial admin and account
passwords , DNS records you should create . If you run it on Linux it writes a
systemd service file and prints commands to enable and start mox as service .
The user or uid is optional , defaults to "mox" , and is the user or uid / gid mox
will run as after initialization .
2023-03-04 02:49:02 +03:00
2023-03-05 17:40:26 +03:00
Quickstart assumes mox will run on the machine you run quickstart on and uses
its host name and public IPs . On many systems the hostname is not a fully
qualified domain name , but only the first dns "label" , e . g . "mail" in case of
"mail.example.org" . If so , quickstart does a reverse DNS lookup to find the
hostname , and as fallback uses the label plus the domain of the email address
you specified . Use flag - hostname to explicitly specify the hostname mox will
run on .
2023-03-04 02:49:02 +03:00
Mox is by far easiest to operate if you let it listen on port 443 ( HTTPS ) and
80 ( HTTP ) . TLS will be fully automatic with ACME with Let ' s Encrypt .
You can run mox along with an existing webserver , but because of MTA - STS and
autoconfig , you ' ll need to forward HTTPS traffic for two domains to mox . Run
"mox quickstart -existing-webserver ..." to generate configuration files and
instructions for configuring mox along with an existing webserver .
But please first consider configuring mox on port 443. It can itself serve
domains with HTTP / HTTPS , including with automatic TLS with ACME , is easily
configured through both configuration files and admin web interface , and can act
as a reverse proxy ( and static file server for that matter ) , so you can forward
traffic to your existing backend applications . Look for "WebHandlers:" in the
output of "mox config describe-domains" and see the output of " mox example
webhandlers " .
2023-01-30 16:27:06 +03:00
`
2023-03-04 02:49:02 +03:00
var existingWebserver bool
2023-03-05 17:40:26 +03:00
var hostname string
2023-03-04 02:49:02 +03:00
c . flag . BoolVar ( & existingWebserver , "existing-webserver" , false , "use if a webserver is already running, so mox won't listen on port 80 and 443; you'll have to provide tls certificates/keys, and configure the existing webserver as reverse proxy, forwarding requests to mox." )
2023-03-05 17:40:26 +03:00
c . flag . StringVar ( & hostname , "hostname" , "" , "hostname mox will run on, by default the hostname of the machine quickstart runs on; if specified, the IPs for the hostname are configured for the public listener" )
2023-01-30 16:27:06 +03:00
args := c . Parse ( )
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 len ( args ) != 1 && len ( args ) != 2 {
2023-01-30 16:27:06 +03:00
c . Usage ( )
}
// We take care to cleanup created files when we error out.
// We don't want to get a new user into trouble with half of the files
// after encountering an error.
// We use fatalf instead of log.Fatal* to cleanup files.
var cleanupPaths [ ] string
fatalf := func ( format string , args ... any ) {
// We remove in reverse order because dirs would have been created first and must
// be removed last, after their files have been removed.
for i := len ( cleanupPaths ) - 1 ; i >= 0 ; i -- {
p := cleanupPaths [ i ]
if err := os . Remove ( p ) ; err != nil {
log . Printf ( "cleaning up %q: %s" , p , err )
}
}
log . Fatalf ( format , args ... )
}
xwritefile := func ( path string , data [ ] byte , perm os . FileMode ) {
os . MkdirAll ( filepath . Dir ( path ) , 0770 )
f , err := os . OpenFile ( path , os . O_WRONLY | os . O_CREATE | os . O_EXCL , perm )
if err != nil {
fatalf ( "creating file %q: %s" , path , err )
}
cleanupPaths = append ( cleanupPaths , path )
_ , err = f . Write ( data )
if err == nil {
err = f . Close ( )
}
if err != nil {
fatalf ( "writing file %q: %s" , path , err )
}
}
addr , err := smtp . ParseAddress ( args [ 0 ] )
if err != nil {
fatalf ( "parsing email address: %s" , err )
}
2023-03-10 00:07:37 +03:00
accountName := addr . Localpart . String ( )
2023-01-30 16:27:06 +03:00
domain := addr . Domain
2023-03-10 00:07:37 +03:00
for _ , c := range accountName {
2023-01-30 16:27:06 +03:00
if c > 0x7f {
2023-02-05 23:25:48 +03:00
fmt . Printf ( ` NOTE : Username % q is not ASCII - only . It is recommended you also configure an
ASCII - only alias . Both for delivery of email from other systems , and for
logging in with IMAP .
2023-03-10 00:07:37 +03:00
` , accountName )
2023-01-30 16:27:06 +03:00
break
}
}
2023-03-05 17:40:26 +03:00
resolver := dns . StrictResolver { }
// We don't want to spend too much total time on the DNS lookups. Because DNS may
// not work during quickstart, and we don't want to loop doing requests and having
// to wait for a timeout each time.
resolveCtx , resolveCancel := context . WithTimeout ( context . Background ( ) , 10 * time . Second )
defer resolveCancel ( )
// We are going to find the (public) IPs to listen on and possibly the host name.
// Start with reasonable defaults. We'll replace them specific IPs, if we can find them.
publicListenerIPs := [ ] string { "0.0.0.0" , "::" }
privateListenerIPs := [ ] string { "127.0.0.1" , "::1" }
// If we find IPs based on network interfaces, {public,private}ListenerIPs are set
// based on these values.
2023-02-05 23:25:48 +03:00
var privateIPs , publicIPs [ ] string
2023-03-05 17:40:26 +03:00
var dnshostname dns . Domain
if hostname == "" {
// Gather IP addresses for public and private listeners.
// If we cannot find addresses for a category we fallback to all ips or localhost ips.
// We look at each network interface. If an interface has a private address, we
// conservatively assume all addresses on that interface are private.
ifaces , err := net . Interfaces ( )
2023-02-05 23:25:48 +03:00
if err != nil {
2023-03-05 17:40:26 +03:00
fatalf ( "listing network interfaces: %s" , err )
2023-02-05 23:25:48 +03:00
}
2023-03-05 17:40:26 +03:00
parseAddrIP := func ( s string ) net . IP {
if strings . HasPrefix ( s , "[" ) && strings . HasSuffix ( s , "]" ) {
s = s [ 1 : len ( s ) - 1 ]
}
ip , _ , _ := net . ParseCIDR ( s )
return ip
2023-02-05 23:25:48 +03:00
}
2023-03-05 17:40:26 +03:00
for _ , iface := range ifaces {
if iface . Flags & net . FlagUp == 0 {
2023-02-05 23:25:48 +03:00
continue
}
2023-03-05 17:40:26 +03:00
addrs , err := iface . Addrs ( )
if err != nil {
fatalf ( "listing address for network interface: %s" , err )
2023-02-05 23:25:48 +03:00
}
2023-03-05 17:40:26 +03:00
if len ( addrs ) == 0 {
2023-02-05 23:25:48 +03:00
continue
}
2023-03-05 17:40:26 +03:00
// todo: should we detect temporary/ephemeral ipv6 addresses and not add them?
var nonpublic bool
for _ , addr := range addrs {
ip := parseAddrIP ( addr . String ( ) )
if ip . IsInterfaceLocalMulticast ( ) || ip . IsLinkLocalMulticast ( ) || ip . IsLinkLocalUnicast ( ) || ip . IsMulticast ( ) {
continue
}
if ip . IsLoopback ( ) || ip . IsPrivate ( ) {
nonpublic = true
break
}
2023-02-05 23:25:48 +03:00
}
2023-03-05 17:40:26 +03:00
for _ , addr := range addrs {
ip := parseAddrIP ( addr . String ( ) )
if ip == nil {
continue
}
if ip . IsInterfaceLocalMulticast ( ) || ip . IsLinkLocalMulticast ( ) || ip . IsLinkLocalUnicast ( ) || ip . IsMulticast ( ) {
continue
}
if nonpublic {
privateIPs = append ( privateIPs , ip . String ( ) )
} else {
publicIPs = append ( publicIPs , ip . String ( ) )
}
2023-02-05 23:25:48 +03:00
}
}
if len ( publicIPs ) > 0 {
2023-03-05 17:40:26 +03:00
publicListenerIPs = publicIPs
2023-02-05 23:25:48 +03:00
}
2023-03-05 17:40:26 +03:00
if len ( privateIPs ) > 0 {
privateListenerIPs = privateIPs
2023-02-05 23:25:48 +03:00
}
2023-03-05 17:40:26 +03:00
hostnameStr , err := os . Hostname ( )
if err != nil {
fatalf ( "hostname: %s" , err )
}
if strings . Contains ( hostnameStr , "." ) {
dnshostname , err = dns . ParseDomain ( hostnameStr )
2023-02-05 23:25:48 +03:00
if err != nil {
2023-03-05 17:40:26 +03:00
fatalf ( "parsing hostname: %v" , err )
}
} else {
// It seems Linux machines don't have a single FQDN configured. E.g. /etc/hostname
// is just the name without domain. We'll look up the names for all IPs, and hope
// to find a single FQDN name (with at least 1 dot).
names := map [ string ] struct { } { }
if len ( publicIPs ) > 0 {
fmt . Printf ( "Trying to find hostname by reverse lookup of public IPs %s..." , strings . Join ( publicIPs , ", " ) )
}
var warned bool
warnf := func ( format string , args ... any ) {
warned = true
fmt . Printf ( "\n%s" , fmt . Sprintf ( format , args ... ) )
2023-02-05 23:25:48 +03:00
}
2023-03-05 17:40:26 +03:00
for _ , ip := range publicIPs {
revctx , revcancel := context . WithTimeout ( resolveCtx , 5 * time . Second )
defer revcancel ( )
l , err := resolver . LookupAddr ( revctx , ip )
if err != nil {
warnf ( "WARNING: looking up reverse name(s) for %s: %v" , ip , err )
}
for _ , name := range l {
if strings . Contains ( name , "." ) {
names [ name ] = struct { } { }
}
2023-02-05 23:25:48 +03:00
}
}
2023-03-05 17:40:26 +03:00
var nameList [ ] string
for k := range names {
nameList = append ( nameList , strings . TrimRight ( k , "." ) )
2023-02-05 23:25:48 +03:00
}
2023-03-05 17:40:26 +03:00
sort . Slice ( nameList , func ( i , j int ) bool {
return nameList [ i ] < nameList [ j ]
} )
if len ( nameList ) == 0 {
dnshostname , err = dns . ParseDomain ( hostnameStr + "." + domain . Name ( ) )
if err != nil {
fmt . Println ( )
fatalf ( "parsing hostname: %v" , err )
}
warnf ( ` WARNING : cannot determine hostname because the system name is not an FQDN and
no public IPs resolving to an FQDN were found . Quickstart guessed the host name
below . If it is not correct , please remove the generated config files and run
quickstart again with the - hostname flag .
2023-02-05 23:25:48 +03:00
2023-03-05 17:40:26 +03:00
% s
` , dnshostname )
} else {
if len ( nameList ) > 1 {
warnf ( ` WARNING : multiple hostnames found for the public IPs , using the first of : % s
If this is not correct , remove the generated config files and run quickstart
again with the - hostname flag .
` , strings . Join ( nameList , ", " ) )
}
dnshostname , err = dns . ParseDomain ( nameList [ 0 ] )
if err != nil {
fmt . Println ( )
fatalf ( "parsing hostname %s: %v" , nameList [ 0 ] , err )
}
2023-02-05 23:25:48 +03:00
}
2023-03-05 17:40:26 +03:00
if warned {
fmt . Printf ( "\n\n" )
} else {
fmt . Printf ( " found %s\n" , dnshostname )
2023-02-05 23:25:48 +03:00
}
}
2023-03-05 17:40:26 +03:00
} else {
// Host name was explicitly configured on command-line. We'll try to use its public
// IPs below.
var err error
dnshostname , err = dns . ParseDomain ( hostname )
if err != nil {
fatalf ( "parsing hostname: %v" , err )
2023-01-30 16:27:06 +03:00
}
}
// todo: lookup without going through /etc/hosts, because a machine typically has its name configured there, and LookupIPAddr will return it, but we care about DNS settings that the rest of the world uses to find us. perhaps we should check if the address resolves to 127.0.0.0/8?
2023-03-05 17:40:26 +03:00
fmt . Printf ( "Looking up IPs for hostname %s..." , dnshostname )
ipctx , ipcancel := context . WithTimeout ( resolveCtx , 5 * time . Second )
defer ipcancel ( )
ips , err := resolver . LookupIPAddr ( ipctx , dnshostname . ASCII + "." )
ipcancel ( )
2023-02-05 23:25:48 +03:00
var xips [ ] net . IPAddr
2023-03-05 17:40:26 +03:00
var xipstrs [ ] string
2023-02-05 23:25:48 +03:00
for _ , ip := range ips {
// During linux install, you may get an alias for you full hostname in /etc/hosts
// resolving to 127.0.1.1, which would result in a false positive about the
// hostname having a record. Filter it out. It is a bit surprising that hosts don't
// otherwise know their FQDN.
if ! ip . IP . IsLoopback ( ) {
xips = append ( xips , ip )
2023-03-05 17:40:26 +03:00
xipstrs = append ( xipstrs , ip . String ( ) )
2023-02-05 23:25:48 +03:00
}
}
if err == nil && len ( xips ) == 0 {
err = errors . New ( "hostname not in dns, probably only in /etc/hosts" )
}
ips = xips
2023-03-05 17:40:26 +03:00
if hostname != "" {
// Host name was specified, assume we will run on a machine with those IPs.
publicListenerIPs = xipstrs
publicIPs = xipstrs
}
2023-01-30 16:27:06 +03:00
if err != nil {
2023-02-05 23:25:48 +03:00
fmt . Printf ( `
WARNING : Quickstart assumed the hostname of this machine is % s and generates a
config for that host , but could not retrieve that name from DNS :
% s
This likely means one of two things :
1. You don ' t have any DNS records for this machine at all . You should add them
before continuing .
2. The hostname mentioned is not the correct host name of this machine . You will
have to replace the hostname in the suggested DNS records and generated
config / mox . conf file . Make sure your hostname resolves to your public IPs , and
your public IPs resolve back ( reverse ) to your hostname .
2023-03-05 17:40:26 +03:00
` , dnshostname , err )
2023-01-30 16:27:06 +03:00
} else {
fmt . Printf ( " OK\n" )
2023-02-03 17:54:34 +03:00
var l [ ] string
type result struct {
IP string
Addrs [ ] string
Err error
}
results := make ( chan result )
for _ , ip := range ips {
s := ip . String ( )
l = append ( l , s )
go func ( ) {
2023-03-05 17:40:26 +03:00
revctx , revcancel := context . WithTimeout ( resolveCtx , 5 * time . Second )
defer revcancel ( )
addrs , err := resolver . LookupAddr ( revctx , s )
2023-02-03 17:54:34 +03:00
results <- result { s , addrs , err }
} ( )
}
fmt . Printf ( "Looking up reverse names for IP(s) %s..." , strings . Join ( l , ", " ) )
var warned bool
warnf := func ( format string , args ... any ) {
fmt . Printf ( "\nWARNING: %s" , fmt . Sprintf ( format , args ... ) )
warned = true
}
for i := 0 ; i < len ( ips ) ; i ++ {
r := <- results
if r . Err != nil {
warnf ( "looking up reverse name for %s: %v" , r . IP , r . Err )
continue
}
if len ( r . Addrs ) != 1 {
warnf ( "expected exactly 1 name for %s, got %d (%v)" , r . IP , len ( r . Addrs ) , r . Addrs )
}
var match bool
for i , a := range r . Addrs {
a = strings . TrimRight ( a , "." )
r . Addrs [ i ] = a // For potential error message below.
d , err := dns . ParseDomain ( a )
if err != nil {
warnf ( "parsing reverse name %q for %s: %v" , a , r . IP , err )
}
2023-03-05 17:40:26 +03:00
if d == dnshostname {
2023-02-03 17:54:34 +03:00
match = true
}
}
if ! match {
2023-03-05 17:40:26 +03:00
warnf ( "reverse name(s) %s for ip %s do not match hostname %s, which will cause other mail servers to reject incoming messages from this IP" , strings . Join ( r . Addrs , "," ) , r . IP , dnshostname )
2023-02-03 17:54:34 +03:00
}
}
if warned {
2023-03-05 17:40:26 +03:00
fmt . Printf ( "\n\n" )
} else {
fmt . Printf ( " OK\n" )
}
}
zones := [ ] dns . Domain {
{ ASCII : "sbl.spamhaus.org" } ,
{ ASCII : "bl.spamcop.net" } ,
}
if len ( publicIPs ) > 0 {
fmt . Printf ( "Checking whether your public IPs are listed in popular DNS block lists..." )
var listed bool
for _ , zone := range zones {
for _ , ip := range publicIPs {
dnsblctx , dnsblcancel := context . WithTimeout ( resolveCtx , 5 * time . Second )
status , expl , err := dnsbl . Lookup ( dnsblctx , resolver , zone , net . ParseIP ( ip ) )
dnsblcancel ( )
if status == dnsbl . StatusPass {
continue
}
errstr := ""
if err != nil {
errstr = fmt . Sprintf ( " (%s)" , err )
}
fmt . Printf ( "\nWARNING: checking your public IP %s in DNS block list %s: %v %s%s" , ip , zone . Name ( ) , status , expl , errstr )
listed = true
}
}
if listed {
log . Printf ( `
Other mail servers are likely to reject email from IPs that are in a blocklist .
If all your IPs are in block lists , you will encounter problems delivering
email . Your IP may be in block lists only temporarily . To see if your IPs are
listed in more DNS block lists , visit :
` )
for _ , ip := range publicIPs {
fmt . Printf ( "- https://multirbl.valli.org/lookup/%s.html\n" , url . PathEscape ( ip ) )
}
fmt . Printf ( "\n" )
2023-02-03 17:54:34 +03:00
} else {
2023-03-05 17:40:26 +03:00
fmt . Printf ( " OK\n" )
2023-02-03 17:54:34 +03:00
}
2023-01-30 16:27:06 +03:00
}
2023-03-05 17:40:26 +03:00
fmt . Printf ( "\n" )
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
user := "mox"
if len ( args ) == 2 {
user = args [ 1 ]
}
2023-01-30 16:27:06 +03:00
dc := config . Dynamic { }
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
sc := config . Static {
2023-03-04 02:49:02 +03:00
DataDir : "../data" ,
User : user ,
LogLevel : "debug" , // Help new users, they'll bring it back to info when it all works.
2023-03-05 17:40:26 +03:00
Hostname : dnshostname . Name ( ) ,
2023-03-04 02:49:02 +03:00
AdminPasswordFile : "adminpasswd" ,
}
if ! existingWebserver {
sc . ACME = map [ string ] config . ACME {
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
"letsencrypt" : {
DirectoryURL : "https://acme-v02.api.letsencrypt.org/directory" ,
ContactEmail : args [ 0 ] , // todo: let user specify an alternative fallback address?
} ,
2023-03-04 02:49:02 +03:00
}
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
dataDir := "data" // ../data is relative to config/
os . MkdirAll ( dataDir , 0770 )
2023-01-30 16:27:06 +03:00
adminpw := pwgen ( )
adminpwhash , err := bcrypt . GenerateFromPassword ( [ ] byte ( adminpw ) , bcrypt . DefaultCost )
if err != nil {
fatalf ( "generating hash for generated admin password: %s" , err )
}
xwritefile ( filepath . Join ( "config" , sc . AdminPasswordFile ) , adminpwhash , 0660 )
fmt . Printf ( "Admin password: %s\n" , adminpw )
public := config . Listener {
IPs : publicListenerIPs ,
}
public . SMTP . Enabled = true
public . Submissions . Enabled = true
public . IMAPS . Enabled = true
2023-03-04 02:49:02 +03:00
if existingWebserver {
2023-03-05 17:40:26 +03:00
hostbase := fmt . Sprintf ( "path/to/%s" , dnshostname . Name ( ) )
2023-03-04 02:49:02 +03:00
mtastsbase := fmt . Sprintf ( "path/to/mta-sts.%s" , domain . Name ( ) )
autoconfigbase := fmt . Sprintf ( "path/to/autoconfig.%s" , domain . Name ( ) )
public . TLS = & config . TLS {
KeyCerts : [ ] config . KeyCert {
{ CertFile : hostbase + "-chain.crt.pem" , KeyFile : hostbase + ".key.pem" } ,
{ CertFile : mtastsbase + "-chain.crt.pem" , KeyFile : mtastsbase + ".key.pem" } ,
{ CertFile : autoconfigbase + "-chain.crt.pem" , KeyFile : autoconfigbase + ".key.pem" } ,
} ,
}
} else {
public . TLS = & config . TLS {
ACME : "letsencrypt" ,
}
public . AutoconfigHTTPS . Enabled = true
public . MTASTSHTTPS . Enabled = true
public . WebserverHTTP . Enabled = true
public . WebserverHTTPS . Enabled = true
}
2023-01-30 16:27:06 +03:00
// Suggest blocklists, but we'll comment them out after generating the config.
2023-03-05 17:40:26 +03:00
for _ , zone := range zones {
public . SMTP . DNSBLs = append ( public . SMTP . DNSBLs , zone . Name ( ) )
}
2023-01-30 16:27:06 +03:00
internal := config . Listener {
IPs : privateListenerIPs ,
Hostname : "localhost" ,
}
2023-02-13 15:53:47 +03:00
internal . AccountHTTP . Enabled = true
2023-01-30 16:27:06 +03:00
internal . AdminHTTP . Enabled = true
internal . MetricsHTTP . Enabled = true
2023-03-04 02:49:02 +03:00
if existingWebserver {
internal . AccountHTTP . Port = 1080
internal . AdminHTTP . Port = 1080
internal . AutoconfigHTTPS . Enabled = true
internal . AutoconfigHTTPS . Port = 81
internal . AutoconfigHTTPS . NonTLS = true
internal . MTASTSHTTPS . Enabled = true
internal . MTASTSHTTPS . Port = 81
internal . MTASTSHTTPS . NonTLS = true
internal . WebserverHTTP . Enabled = true
internal . WebserverHTTP . Port = 81
}
2023-01-30 16:27:06 +03:00
sc . Listeners = map [ string ] config . Listener {
"public" : public ,
"internal" : internal ,
}
2023-03-10 00:07:37 +03:00
sc . Postmaster . Account = accountName
2023-01-30 16:27:06 +03:00
sc . Postmaster . Mailbox = "Postmaster"
mox . ConfigStaticPath = "config/mox.conf"
mox . ConfigDynamicPath = "config/domains.conf"
mox . Conf . DynamicLastCheck = time . Now ( ) // Prevent error logging by Make calls below.
accountConf := mox . MakeAccountConfig ( addr )
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
const withMTASTS = true
2023-03-10 00:07:37 +03:00
confDomain , keyPaths , err := mox . MakeDomainConfig ( context . Background ( ) , domain , dnshostname , accountName , withMTASTS )
2023-01-30 16:27:06 +03:00
if err != nil {
fatalf ( "making domain config: %s" , err )
}
cleanupPaths = append ( cleanupPaths , keyPaths ... )
dc . Domains = map [ string ] config . Domain {
domain . Name ( ) : confDomain ,
}
dc . Accounts = map [ string ] config . Account {
2023-03-10 00:07:37 +03:00
accountName : accountConf ,
2023-01-30 16:27:06 +03:00
}
// Build config in memory, so we can easily comment out the DNSBLs config.
var sb strings . Builder
sc . CheckUpdates = true // Commented out below.
if err := sconf . WriteDocs ( & sb , & sc ) ; err != nil {
fatalf ( "generating static config: %v" , err )
}
confstr := sb . String ( )
confstr = strings . ReplaceAll ( confstr , "\nCheckUpdates: true\n" , "\n#\n# RECOMMENDED: please enable to stay up to date\n#\n#CheckUpdates: true\n" )
confstr = strings . ReplaceAll ( confstr , "DNSBLs:\n" , "#DNSBLs:\n" )
for _ , bl := range public . SMTP . DNSBLs {
confstr = strings . ReplaceAll ( confstr , "- " + bl + "\n" , "#- " + bl + "\n" )
}
xwritefile ( "config/mox.conf" , [ ] byte ( confstr ) , 0660 )
// Generate domains config, and add a commented out example for delivery to a mailing list.
var db bytes . Buffer
if err := sconf . WriteDocs ( & db , & dc ) ; err != nil {
fatalf ( "generating domains config: %v" , err )
}
// This approach is a bit horrible, but it generates a convenient
// example that includes the comments. Though it is gone by the first
// write of the file by mox.
2023-03-10 00:07:37 +03:00
odests := fmt . Sprintf ( "\t\tDestinations:\n\t\t\t%s: nil\n" , addr . String ( ) )
2023-01-30 16:27:06 +03:00
var destsExample = struct {
Destinations map [ string ] config . Destination
} {
Destinations : map [ string ] config . Destination {
2023-03-10 00:07:37 +03:00
addr . String ( ) : {
2023-01-30 16:27:06 +03:00
Rulesets : [ ] config . Ruleset {
{
VerifiedDomain : "list.example.org" ,
HeadersRegexp : map [ string ] string {
"^list-id$" : ` <name\.list\.example\.org> ` ,
} ,
ListAllowDomain : "list.example.org" ,
Mailbox : "Lists/Example" ,
} ,
} ,
} ,
} ,
}
var destBuf strings . Builder
if err := sconf . Describe ( & destBuf , destsExample ) ; err != nil {
fatalf ( "describing destination example: %v" , err )
}
ndests := odests + "#\t\t\tIf you receive email from mailing lists, you probably want to configure them like the example below.\n"
for _ , line := range strings . Split ( destBuf . String ( ) , "\n" ) [ 1 : ] {
ndests += "#\t\t" + line + "\n"
}
dconfstr := strings . ReplaceAll ( db . String ( ) , odests , ndests )
xwritefile ( "config/domains.conf" , [ ] byte ( dconfstr ) , 0660 )
// Verify config.
2023-03-04 02:49:02 +03:00
skipCheckTLSKeyCerts := existingWebserver
2023-03-06 01:56:02 +03:00
mc , errs := mox . ParseConfig ( context . Background ( ) , "config/mox.conf" , true , skipCheckTLSKeyCerts , false )
2023-01-30 16:27:06 +03:00
if len ( errs ) > 0 {
if len ( errs ) > 1 {
log . Printf ( "checking generated config, multiple errors:" )
for _ , err := range errs {
log . Println ( err )
}
fatalf ( "aborting due to multiple config errors" )
}
fatalf ( "checking generated config: %s" , errs [ 0 ] )
}
mox . SetConfig ( mc )
// NOTE: Now that we've prepared the config, we can open the account
// and set a passsword, and the public key for the DKIM private keys
// are available for generating the DKIM DNS records below.
confDomain , ok := mc . Domain ( domain )
if ! ok {
fatalf ( "cannot find domain in new config" )
}
acc , _ , err := store . OpenEmail ( args [ 0 ] )
if err != nil {
fatalf ( "open account: %s" , err )
}
2023-03-10 00:07:37 +03:00
cleanupPaths = append ( cleanupPaths , dataDir , filepath . Join ( dataDir , "accounts" ) , filepath . Join ( dataDir , "accounts" , accountName ) , filepath . Join ( dataDir , "accounts" , accountName , "index.db" ) )
2023-01-30 16:27:06 +03:00
password := pwgen ( )
if err := acc . SetPassword ( password ) ; err != nil {
fatalf ( "setting password: %s" , err )
}
if err := acc . Close ( ) ; err != nil {
fatalf ( "closing account: %s" , err )
}
fmt . Printf ( "IMAP and SMTP submission password for %s: %s\n\n" , args [ 0 ] , password )
fmt . Println ( ` When configuring your email client , use the email address as username . If
autoconfig / autodiscover does not work , use the settings below . ` )
fmt . Println ( "" )
printClientConfig ( domain )
2023-03-04 02:49:02 +03:00
if existingWebserver {
fmt . Printf ( `
Configuration files have been written to config / mox . conf and
config / domains . conf .
Create the DNS records below . The admin interface can show these same records , and
has a page to check they have been configured correctly .
You must configure your existing webserver to forward requests for :
https : //mta-sts.%s/
https : //autoconfig.%s/
To mox , at :
http : //127.0.0.1:81
If it makes it easier to get a TLS certificate for % s , you can add a
reverse proxy for that hostname too .
You must edit mox . conf and configure the paths to the TLS certificates and keys .
The paths are relative to config / directory that holds mox . conf ! To test if your
config is valid , run :
. / mox config test
2023-03-05 17:40:26 +03:00
` , domain . ASCII , domain . ASCII , dnshostname . ASCII )
2023-03-04 02:49:02 +03:00
} else {
fmt . Printf ( `
Configuration files have been written to config / mox . conf and
2023-02-01 23:42:04 +03:00
config / domains . conf . You should review them . Then create the DNS records below .
2023-01-30 16:27:06 +03:00
You can also skip creating the DNS records and start mox immediately . The admin
interface can show these same records , and has a page to check they have been
2023-03-04 02:49:02 +03:00
configured correctly .
` )
}
2023-01-30 16:27:06 +03:00
// We do not verify the records exist: If they don't exist, we would only be
// priming dns caches with negative/absent records, causing our "quick setup" to
// appear to fail or take longer than "quick".
records , err := mox . DomainRecords ( confDomain , domain )
if err != nil {
fatalf ( "making required DNS records" )
}
2023-03-04 02:49:02 +03:00
fmt . Print ( "\n\n" + strings . Join ( records , "\n" ) + "\n\n\n\n" )
2023-01-30 16:27:06 +03:00
fmt . Printf ( ` WARNING : The configuration and DNS records above assume you do not currently
have email configured for your domain . If you do already have email configured ,
or if you are sending email for your domain from other machines / services , you
should understand the consequences of the DNS records above before
continuing !
2023-03-04 02:49:02 +03:00
` )
if os . Getenv ( "MOX_DOCKER" ) == "" {
fmt . Printf ( `
You can now start mox with "./mox serve" , as root .
` )
} else {
fmt . Printf ( `
You can now start the mox container .
` )
}
fmt . Printf ( `
File ownership and permissions are automatically set correctly by mox when
starting up . On linux , you may want to enable mox as a systemd service .
2023-01-30 16:27:06 +03:00
` )
2023-02-24 16:16:51 +03:00
// For now, we only give service config instructions for linux when not running in docker.
if runtime . GOOS == "linux" && os . Getenv ( "MOX_DOCKER" ) == "" {
2023-01-30 16:27:06 +03:00
pwd , err := os . Getwd ( )
if err != nil {
log . Printf ( "current working directory: %v" , 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
pwd = "/home/mox"
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
service := strings . ReplaceAll ( moxService , "/home/mox" , pwd )
2023-01-30 16:27:06 +03:00
xwritefile ( "mox.service" , [ ] byte ( service ) , 0644 )
cleanupPaths = append ( cleanupPaths , "mox.service" )
fmt . Printf ( ` See mox . service for a systemd service file . To enable and start :
sudo chmod 644 mox . service
sudo systemctl enable $ PWD / mox . service
sudo systemctl start mox . service
sudo journalctl - f - u mox . service # See logs
` )
}
2023-03-04 02:49:02 +03:00
fmt . Printf ( ` For secure email exchange you should have a strictly validating DNSSEC
2023-01-30 16:27:06 +03:00
resolver . An easy and the recommended way is to install unbound .
2023-03-04 02:49:02 +03:00
If you run into problem , have questions / feedback or found a bug , please let us
know . Mox needs your help !
2023-02-05 23:25:48 +03:00
Enjoy !
2023-03-04 02:49:02 +03:00
` )
2023-02-05 23:25:48 +03:00
2023-03-04 02:49:02 +03:00
if ! existingWebserver {
fmt . Printf ( `
PS : If you want to run mox along side an existing webserver that uses port 443
and 80 , see "mox help quickstart" with the - existing - webserver option .
` )
}
2023-01-30 16:27:06 +03:00
cleanupPaths = nil
}