initial commit

This commit is contained in:
Chang, Chu-Kuan 2020-09-12 22:54:16 +08:00
commit 59307a11f5
3 changed files with 615 additions and 0 deletions

118
README.md Normal file
View File

@ -0,0 +1,118 @@
# Email server setup script
I wrote this script during the gruelling process of installing and setting up
an email server. It perfectly reproduces my successful steps to ensure the
same setup time and time again.
Read this readme and peruse the script's comments before running it. Expect it
to fail and you have to do bug testing and you will be very happy when it
actually works perfectly.
## This script installs
- **OpenSMTPD** to send and receive mail.
- **Dovecot** to get mail to your email client (mutt, Thunderbird, etc).
- Config files that unique the two above securely with native log-ins.
- **Rspamd** to prevent spam and allow you to make custom filters.
- **opensmtpd-filter-dkimsign** to validate you so you can send to Gmail and
other big sites.
## This script does _not_
- use a SQL database or anything like that.
- set up a graphical interface for mail like Roundcube or Squirrel Mail. If you
want that, you'll have to install it yourself. I just use
[isync/msmtp/mutt-wizard](https://github.com/lukesmithxyz/mutt-wizard) to
have an offline mirror of my email setup and I recommend the same. There are
other ways of doing it though, like Thunderbird, etc.
## Requirements
1. A **OpenBSD server**. I've tested this on a
[Vultr](https://www.vultr.com/?ref=8608122) OpenBSD server and their setup
works, but I suspect other VPS hosts will have similar/possibly identical
default settings which will let you run this on them. Note that the affiliate
link there to Vultr gives you a $100 credit for the first month to play
around.
2. **A Let's Encrypt SSL certificate for your site's `mx` subdomain**.
Create a httpd(1) site at `mx.domain.tld` and get a certificate
for it with acme-client(1).
3. You need two little DNS records set on your domain registrar's site/DNS
server: (1) an **MX record** pointing to your own main domain/IP and (2) a
**CNAME record** for your `mail.` subdomain.
4. **A Reverse DNS entry for your site.** Go to your VPS settings and add an
entry for your IPV4 Reverse DNS that goes from your IP address to
`mx.domain.tld`. If you would like IPV6, you can do the same for
that. This has been tested on Vultr, and all decent VPS hosts will have
a section on their instance settings page to add a reverse DNS PTR entry.
You can use the 'Test Email Server' or ':smtp' tool on
[mxtoolbox](https://mxtoolbox.com/SuperTool.aspx) to test if you set up
a reverse DNS correctly. This step is not required for everyone, but some
big email services like gmail will stop emails coming from mail servers
with no/invalid rDNS lookups. This means your email will fail to even
make it to the receipients spam folder; it will never make it to them.
6. Some VPS providers block port 25 (used to send mail). You may need to
request that this port be opened to send mail successfully. Although I have
never had to do this on a Vultr VPS, others have had this issue so if you
cannot send, contact your VPS provider.
## Post-install requirement!
- After the script runs, you'll have to add additional DNS TXT records which
are displayed at the end when the script is complete. They will help ensure
your mail is validated and secure.
- Modify rspamd whitelists/blacklists in `/etc/rspamd/local.d` to yout need.
## Making new users/mail accounts
Let's say we want to add a user Billy and let him receive mail, run this:
```
useradd -m billy
passwd billy
```
A user's mail will appear in `~/Maildir/`. If you want to see your mail while
ssh'd in the server, you could just install mutt, add `set spoolfile="+Inbox"`
to your `~/.muttrc` and use mutt to view and reply to mail. You'll probably want
to log in remotely though:
## Logging in from Thunderbird or mutt (and others) remotely
Let's say you want to access your mail with Thunderbird or mutt or another
email program. For my domain, the server information will be as follows:
- SMTP server: `mx.domain.tld`
- SMTP port: 465
- IMAP server: `mx.domain.tld`
- IMAP port: 993
- Username `user` (I.e. *not* `user@domain.tld`)
The last point is important. Many email systems use a full email address on
login. Since we just simply use local PAM logins, only the user's name is used
(this makes a difference if you're using luke's
[mutt-wizard](https://github.com/lukesmithxyz/mutt-wizard), etc.).
## Tweaking things
You're a big boy now if you have your own mail server!
You can tweak smtpd (sending mail
## Furthur reading
- [poolp's guide](https://poolp.org/posts/2019-09-14/setting-up-a-mail-server-with-opensmtpd-dovecot-and-rspamd/)
## Troubleshooting -- Can't send mail?
- Always check `/var/log/maillog` and `/var/log/rspamd` to see the specific
problem.
- Go to [this site](https://appmaildev.com/en/dkim) to test your TXT records.
If your DKIM, SPF or DMARC tests fail you probably copied in the TXT records
incorrectly.
- If everything looks good and you *can* send mail, but it still goes to Gmail
or another big provider's spam directory, your domain (especially if it's a
new one) might be on a public spam list. Check
[this site](https://mxtoolbox.com/blacklists.aspx) to see if it is. Don't
worry if you are: sometimes especially new domains are automatically assumed
to be spam temporaily. If you are blacklisted by one of these, look into it
and it will explain why and how to remove yourself.

475
emailwiz Executable file
View File

@ -0,0 +1,475 @@
#!/bin/sh
# _ _ _
# ___ _ __ ___ __ _(_) |_ _(_)____
# / _ \ '_ ` _ \ / _` | | \ \ /\ / / |_ /
#| __/ | | | | | (_| | | |\ V V /| |/ /
# \___|_| |_| |_|\__,_|_|_| \_/\_/ |_/___|
#
#
# THE SETUP
#
# Login info:
# SMTPs server: mx.domain.tld (${subdom}.${domain})
# SMTPs Setting: port 465, SSL/TLS, PLAIN
# IMAPs server: mx.domain.tld (${subdom}.${domain})
# IMAPs Setting: port 993, SSL/TLS, PLAIN
# User: user name w/o '@domain.tld'
# Firewall setting, open following port:
# SMPT port 25
# SMTPs port 465
# IMAPs port 993
# Mail will be stored in non-retarded Maildirs because it's ${current_year}. This
# makes it easier for use with isync, which is what I care about so I can have
# an offline repo of mail.
# The mailbox names are: Inbox, Sent, Drafts, Archive, Spam, Trash
# Use the typical unix login system for mail users. Users will log into their
# email with their passnames on the server. No usage of a redundant mySQL
# database to do this.
# Rspamd whitelists and blacklists are in /etc/rspamd/.local.d. Modify them to
# your own need.
#
# DEPENDENCIES BEFORE RUNNING
#
# 1. Modify "Paramater" section to your need.
# 2. Have a OpenBSD 6.7 system with a static IP and all that. Pretty much any
# default VPS offered by a company will have all the basic stuff you need.
# 3. Have a Let's Encrypt SSL certificate for mx.domain.tld
# (${subdom}.${domain}) using httpd(1) and acme-client(1).
# 4. If you've been toying around with your server settings trying to get
# smtpd/dovecot/rspamd/etc. I recommend you to clear out /etc/mail, /etc/dovecot
# , and /etc/rspamd/local.d yourself if needbe, because this script is build on
# top of only the defaults.
#
# Parameters
#
# Modify this section to your need
readonly domain='domain.tld'
readonly subdom='mx'
# DO NOT modify this part
readonly maildomain="${subdom}.${domain}"
readonly progname="$(basename "$0")"
# Certificate for smtp
readonly certfile="/etc/ssl/${maildomain}.fullchain.pem"
readonly keyfile="/etc/ssl/private/${maildomain}.key"
# RSA key for dkim
readonly dkimkey="/etc/mail/dkim/${domain}.key"
#
# Check privilege
#
if test "$(id -u)" != '0'; then
printf '%s: Root access required.\n' "${progname}"
printf '%s: Exiting...\n' "${progname}"
exit 1
fi
#
# Check dependency
#
printf '%s: Installing programs...\n' "${progname}"
pkg_add opensmtpd-filter-dkimsign dovecot dovecot-pigeonhole rspamd opensmtpd-filter-rspamd
if test ! -f /etc/doas.conf; then
printf '%s: doas(1) not configured.\n' "${progname}"
printf '%s: Exiting...\n' "${progname}"
exit 1
fi
#
# Check certificate
#
printf '%s: Checking Certificate files...' "${progname}"
if test -f "${certfile}" && test -f "${keyfile}"; then
printf ' Done\n'
else
printf ' File not found\n'
printf '%s: Note! You must first have a HTTPS/SSL Certificate for %s.
Use acme-client(1) and httpd(1) to get that and then rerun this script.' "${progname}" "${maildomain}"
printf '%s: Exiting...\n' "${progname}"
exit 1
fi
#
# Configure alternative server name ( /etc/mail/mailname )
#
printf '%s: Configuring alternative server name (/etc/mail/mailname)...\n' "${progname}"
test -f /etc/mail/mailname && cp -f /etc/mail/mailname /etc/mail/mailname.def
printf '%s\n' "${maildomain}" > /etc/mail/mailname
#
# Configure OpenSMTPD ( /etc/mail/smtpd.conf )
#
printf '%s: Configuring OpenSMTPD (smtpd.conf)...\n' "${progname}"
cp -f /etc/mail/smtpd.conf /etc/mail/smtpd.conf.def
cat << EOF > /etc/mail/smtpd.conf
# Generated by emailwiz
# This is the smtpd server system-wide configuration file.
# See smtpd.conf(5) for more information.
pki "${maildomain}" cert "${certfile}"
pki "${maildomain}" key "${keyfile}"
filter "check_rdns" phase connect match !rdns disconnect "550 no rDNS"
filter "check_fcrdns" phase connect match !fcrdns disconnect "550 no FCrDNS"
filter "rspamd" proc-exec "filter-rspamd"
filter "spam_triad" chain { "check_rdns", "check_fcrdns", "rspamd" }
filter "dkimsign" proc-exec "filter-dkimsign -d ${domain} -s ${subdom} -k ${dkimkey}" user _dkimsign group _dkimsign
table aliases file:/etc/mail/aliases
listen on lo0
listen on egress port 25 tls pki "${maildomain}" filter "spam_triad"
listen on egress port 465 smtps pki "${maildomain}" auth filter "dkimsign"
action "local_mail" lmtp "/var/dovecot/lmtp" alias <aliases>
action "outbound" relay helo "${maildomain}"
match from local for local action "local_mail"
match from local for any action "outbound"
match from any for domain "${domain}" action "local_mail"
match from any auth for any action "outbound"
EOF
#
# Configure Dovecot ( /etc/dovecot/dovecot.conf )
#
# By default, dovecot has a bunch of configs in /etc/dovecot/conf.d/ These
# files have nice documentation if you want to read it, but it's a huge pain to
# go through them to organize. Instead, we simply rewrite dovecot.conf because
# it's easier to manage.
printf '%s: Configuring Dovecot (dovecot.conf)...\n' "${progname}"
cp -f /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.def
cat << EOF > /etc/dovecot/dovecot.conf
# Generate by emailwiz
# Note that in the dovecot.conf, you can use:
# %u for username
# %n for the name in name@domain.tld
# %d for the domain
# %h the user's home directory
# Use imap and lmtp ( deliver mail for OpenSMTPD )
protocols = imap lmtp
# Our mail for each user will be in ~/Maildir, and the inbox will be ~/Maildir/Inbox
# The LAYOUT option is also important because otherwise, the boxes will be ".Sent" instead of "Sent".
mail_location = maildir:~/Maildir:INBOX=~/Maildir/Inbox:LAYOUT=fs
namespace inbox {
inbox = yes
mailbox Drafts {
special_use = \\Drafts
auto = subscribe
}
mailbox Spam {
special_use = \\Junk
auto = subscribe
autoexpunge = 30d
}
mailbox Sent {
special_use = \\Sent
auto = subscribe
}
mailbox Trash {
special_use = \\Trash
auto = subscribe
}
mailbox Archive {
special_use = \\Archive
auto = subscribe
}
}
# Use BSDAuth to authenticate the user
passdb {
driver = bsdauth
}
# Retrieves post-login information in /etc/passwd
userdb {
driver = passwd
}
# Outlook Express and Windows Mail works only with LOGIN mechanism, not the standard PLAIN:
auth_mechanisms = plain login
# Use plain user name instead of user@domain for login
auth_username_format = %n
# Configure SSL/TLS
# NOTE: If you have only plaintext mechanisms enabled
# (e.g. auth { mechanisms = plain login } ), ssl=yes and ssl=required are
# completely equivalent because in either case the authentication will fail
# unless SSL/TLS is enabled first
ssl = required
ssl_cert = <${certfile}
ssl_key = <${keyfile}
# Disable insecure IMAP
service imap-login {
inet_listener imap {
#port = 143
port = 0
}
inet_listener imaps {
#port = 993
#ssl = yes
}
}
# Configure LMTP
service lmtp {
unix_listener lmtp {
mode = 0666
}
}
# Configure sieve
protocol lmtp {
mail_plugins = \$mail_plugins sieve
}
plugin {
# The location of the user's main script storage. The active script
# in this storage is used as the main user script executed during
# delivery. The include extension fetches the :personal scripts
# from this location. When ManageSieve is used, this is also where
# scripts are uploaded. This example uses the file system as
# storage, with all the user's scripts located in the directory
# "~/sieve" and the active script (symbolic link) located at
# "~/.dovecot.sieve".
sieve = file:~/sieve;active=~/.dovecot.sieve
# If the user has no personal active script (i.e. if the location
# indicated in sieve= does not exist or has no active script), use
# this one:
sieve_default = /usr/local/lib/dovecot/sieve/default.sieve
# The include extension fetches the :global scripts from this
# location.
sieve_global = /usr/local/lib/dovecot/sieve/global/
}
EOF
#
# Generate sieve scripts for Dovecot ( /usr/local/lib/dovecot/sieve/* )
#
printf '%s: Generating sieve scripts for Dovecot ( /usr/local/lib/dovecot/sieve/* )...\n' "${progname}"
mkdir -p /usr/local/lib/dovecot/sieve/
# Default sieve script: put Rspamd-tagged mails into mailbox "Spam"
cat << EOF > /usr/local/lib/dovecot/sieve/default.sieve
require "fileinto";
if header :contains "X-Spam" "YES" {
fileinto "Spam";
}
EOF
# Compile sieve scripts
sievec /usr/local/lib/dovecot/sieve/default.sieve
#
# Configure user authentication for Dovecot ( /etc/login.conf )
#
printf '%s: Configuring user authentication for Dovecot (/etc/login.conf)...\n' "${progname}"
if ! grep -q 'dovecot' /etc/login.conf; then
cp -f /etc/login.conf /etc/login.conf.def
cat << EOF >> /etc/login.conf
#
# Dovecot entry
#
dovecot:\\
:openfiles-cur=1024:\\
:openfiles-max=2048:\\
:tc=daemon:
EOF
fi
# reload login.conf
cap_mkdb /etc/login.conf
#
# Configure rspamd ( /etc/rspamd/local.d/action.conf )
#
printf '%s: Configuring Rspamd (action.conf)...\n' "${progname}"
mkdir -p /etc/rspamd/local.d/
cat << EOF > /etc/rspamd/local.d/action.conf
# Generate by emailwiz
actions {
#reject = 15; # Reject when reaching this score
reject = null; # Disable Rejection
add_header = 6; # Add header when reaching this score
greylist = 4; # Apply greylisting when reaching this score (will emit "soft reject action")
}
EOF
printf '%s: Configuring Rspamd black/whitelist (multimap.conf)...\n' "${progname}"
for x in 'local_bl_from.map.inc' 'local_bl_ip.map.inc' 'local_bl_rcpt.map.inc' \
'local_wl_from.map.inc' 'local_wl_ip.map.inc' 'local_wl_rcpt.map.inc'; do
touch "/etc/rspamd/local.d/${x}"
chmod 666 "/etc/rspamd/local.d/${x}"
done
cat << EOF > /etc/rspamd/local.d/multimap.conf
# Blacklists
local_bl_ip {
type = "ip";
map = "\$LOCAL_CONFDIR/local.d/local_bl_ip.map.inc";
symbol = "LOCAL_BL_IP";
description = "Local ip blacklist";
score = 5;
}
local_bl_from {
type = "from";
map = "\$LOCAL_CONFDIR/local.d/local_bl_from.map.inc";
symbol = "LOCAL_BL_FROM";
description = "Local from blacklist";
score = 5;
}
local_bl_rcpt {
type = "rcpt";
map = "\$LOCAL_CONFDIR/local.d/local_bl_rcpt.map.inc";
symbol = "LOCAL_BL_RCPT";
description = "Local rcpt blacklist";
score = 5;
}
# Whitelists
local_wl_ip {
type = "ip";
map = "\$LOCAL_CONFDIR/local.d/local_wl_ip.map.inc";
symbol = "LOCAL_WL_IP";
description = "Local ip whitelist";
score = -5;
}
local_wl_from {
type = "from";
map = "\$LOCAL_CONFDIR/local.d/local_wl_from.map.inc";
symbol = "LOCAL_WL_FROM";
description = "Local from whitelist";
score = -5;
}
local_wl_rcpt {
type = "rcpt";
map = "\$LOCAL_CONFDIR/local.d/local_wl_rcpt.map.inc";
symbol = "LOCAL_WL_RCPT";
description = "Local rcpt whitelist";
score = -5;
}
EOF
#
# Configure opensmtpd-filter-dkimsign
#
printf '%s: Generating dkim keys...\n' "${progname}"
mkdir -p /etc/mail/dkim
doas -u _dkimsign openssl genrsa -out "${dkimkey}" 2048
#
# Restart daemons
#
for x in 'smtpd' 'dovecot' 'rspamd'; do
printf '%s: Enabling %s...' "${progname}" "${x}"
rcctl enable "${x}" 2>&1 > /dev/null && printf ' Done\n' || printf ' Failed\n'
printf '%s: Restarting %s...' "${progname}" "${x}"
rcctl restart "${x}" 2>&1 > /dev/null && printf ' Done\n' || printf ' Failed\n'
done
#
# Display DNS Record & Finish up
#
readonly pval="$(openssl rsa -in "${dkimkey}" -pubout \
| sed '1s/.*/p=/;:nl;${s/-----.*//;q;};N;s/\n//g;b nl;')"
readonly dkim_entry="${subdom}._domainkey.${domain} TXT v=DKIM1; k=rsa; ${pval}"
readonly dmarc_entry="_dmarc.${domain} TXT v=DMARC1; p=none; rua=mailto:postmaster@${domain}; fo=1"
readonly spf_entry="@ TXT v=spf1 mx a:${maildomain} -all"
cat << EOF > "${HOME}/dns_emailwiz"
${dkim_entry}
${dmarc_entry}
${spf_entry}
EOF
cat << EOF
_ _
| \ | | _____ ___
| \| |/ _ \ \ /\ / (_)
| |\ | (_) \ V V / _
|_| \_|\___/ \_/\_/ (_)
1. Add these three records to your DNS TXT records on either your registrar's site
or your DNS server:
${dkim_entry}
${dmarc_entry}
${spf_entry}
NOTE: You may need to omit the ".${domain}" portion at the beginning if
inputting them in a registrar's web interface.
Also saving these to ~/dns_emailwiz in case you want them in a file.
EOF

22
mailrestart Executable file
View File

@ -0,0 +1,22 @@
#!/bin/sh
# Program:
# * restart mail server related daemons
# Dependency:
# * nil
# History:
# * 20200912 cckuan first release
readonly progname="$(basename "$0")"
if test "$(id -u)" != '0'; then
printf '%s: Root access required.\n' "${progname}"
printf '%s: Exiting...\n' "${progname}"
exit 1
fi
for x in 'smtpd' 'dovecot' 'rspamd'; do
printf '%s: Restarting %s...' "${progname}" "${x}"
rcctl restart "${x}" 2>&1 > /dev/null \
&& printf ' Done\n' \
|| printf ' Failed\n'
done