Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to specify a custom DKIM selector #2383

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions management/dns_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ def has_rec(qname, rtype, prefix=None):

# Append the DKIM TXT record to the zone as generated by OpenDKIM.
# Skip if the user has set a DKIM record already.
opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.txt')
opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/' + env['DKIM_SELECTOR'] + '.txt')
with open(opendkim_record_file, encoding="utf-8") as orf:
m = re.match(r'(\S+)\s+IN\s+TXT\s+\( ((?:"[^"]+"\s+)+)\)', orf.read(), re.S)
val = "".join(re.findall(r'"([^"]+)"', m.group(2)))
Expand Down Expand Up @@ -752,12 +752,13 @@ def write_opendkim_tables(domains, env):
# Append a record to OpenDKIM's KeyTable and SigningTable for each domain
# that we send mail from (zones and all subdomains).

opendkim_key_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.private')
opendkim_key_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/' + env['DKIM_SELECTOR'] + '.private')

if not os.path.exists(opendkim_key_file):
# Looks like OpenDKIM is not installed.
return False

selector=env['DKIM_SELECTOR']
config = {
# The SigningTable maps email addresses to a key in the KeyTable that
# specifies signing information for matching email addresses. Here we
Expand All @@ -777,7 +778,7 @@ def write_opendkim_tables(domains, env):
# signing domain must match the sender's From: domain.
"KeyTable":
"".join(
f"{domain} {domain}:mail:{opendkim_key_file}\n"
f"{domain} {domain}:{selector}:{opendkim_key_file}\n"
for domain in domains
),
}
Expand Down
23 changes: 11 additions & 12 deletions setup/dkim.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@
#
# The DNS configuration for DKIM is done in the management daemon.

source setup/functions.sh # load our functions
source setup/functions.sh # load our functions
source /etc/mailinabox.conf # load global vars

# Install DKIM...
echo "Installing OpenDKIM/OpenDMARC..."
apt_install opendkim opendkim-tools opendmarc

# Make sure configuration directories exist.
mkdir -p /etc/opendkim;
mkdir -p /etc/opendkim
mkdir -p "$STORAGE_ROOT/mail/dkim"

# Used in InternalHosts and ExternalIgnoreList configuration directives.
# Not quite sure why.
echo "127.0.0.1" > /etc/opendkim/TrustedHosts
echo "127.0.0.1" >/etc/opendkim/TrustedHosts

# We need to at least create these files, since we reference them later.
# Otherwise, opendkim startup will fail
Expand All @@ -30,7 +30,7 @@ if grep -q "ExternalIgnoreList" /etc/opendkim.conf; then
true # already done #NODOC
else
# Add various configuration options to the end of `opendkim.conf`.
cat >> /etc/opendkim.conf << EOF;
cat >>/etc/opendkim.conf <<EOF
Canonicalization relaxed/simple
MinimumKeyBits 1024
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
Expand All @@ -52,8 +52,8 @@ fi
# A 1024-bit key is seen as a minimum standard by several providers
# such as Google. But they and others use a 2048 bit key, so we'll
# do the same. Keys beyond 2048 bits may exceed DNS record limits.
if [ ! -f "$STORAGE_ROOT/mail/dkim/mail.private" ]; then
opendkim-genkey -b 2048 -r -s mail -D "$STORAGE_ROOT/mail/dkim"
if [ ! -f "$STORAGE_ROOT/mail/dkim/$DKIM_SELECTOR.private" ]; then
opendkim-genkey -b 2048 -r -s $DKIM_SELECTOR -D $STORAGE_ROOT/mail/dkim
fi

# Ensure files are owned by the opendkim user and are private otherwise.
Expand All @@ -71,7 +71,7 @@ tools/editconf.py /etc/opendmarc.conf -s \
# used by spamassassin to evaluate the mail for spamminess.

tools/editconf.py /etc/opendmarc.conf -s \
"SPFIgnoreResults=true"
"SPFIgnoreResults=true"

# SPFSelfValidate causes the filter to perform a fallback SPF check itself
# when it can find no SPF results in the message header. If SPFIgnoreResults
Expand All @@ -80,13 +80,13 @@ tools/editconf.py /etc/opendmarc.conf -s \
# spamassassin to evaluate the mail for spamminess.

tools/editconf.py /etc/opendmarc.conf -s \
"SPFSelfValidate=true"
"SPFSelfValidate=true"

# Disables generation of failure reports for sending domains that publish a
# "none" policy.

tools/editconf.py /etc/opendmarc.conf -s \
"FailureReportsOnNone=false"
"FailureReportsOnNone=false"

# AlwaysAddARHeader Adds an "Authentication-Results:" header field even to
# unsigned messages from domains with no "signs all" policy. The reported DKIM
Expand All @@ -95,7 +95,7 @@ tools/editconf.py /etc/opendmarc.conf -s \
# is used by spamassassin to evaluate the mail for spamminess.

tools/editconf.py /etc/opendkim.conf -s \
"AlwaysAddARHeader=true"
"AlwaysAddARHeader=true"

# Add OpenDKIM and OpenDMARC as milters to postfix, which is how OpenDKIM
# intercepts outgoing mail to perform the signing (by adding a mail header)
Expand All @@ -110,7 +110,7 @@ tools/editconf.py /etc/opendkim.conf -s \
# configuring smtpd_milters there to only list the OpenDKIM milter
# (see mail-postfix.sh).
tools/editconf.py /etc/postfix/main.cf \
"smtpd_milters=inet:127.0.0.1:8891 inet:127.0.0.1:8893"\
"smtpd_milters=inet:127.0.0.1:8891 inet:127.0.0.1:8893" \
non_smtpd_milters=\$smtpd_milters \
milter_default_action=accept

Expand All @@ -121,4 +121,3 @@ hide_output systemctl enable opendmarc
restart_service opendkim
restart_service opendmarc
restart_service postfix

56 changes: 32 additions & 24 deletions setup/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ source setup/preflight.sh
# Python may not be able to read/write files. This is also
# in the management daemon startup script and the cron script.

if ! locale -a | grep en_US.utf8 > /dev/null; then
# Generate locale if not exists
hide_output locale-gen en_US.UTF-8
if ! locale -a | grep en_US.utf8 >/dev/null; then
# Generate locale if not exists
hide_output locale-gen en_US.UTF-8
fi

export LANGUAGE=en_US.UTF-8
Expand All @@ -35,16 +35,23 @@ if [ -f /etc/mailinabox.conf ]; then

# Load the old .conf file to get existing configuration options loaded
# into variables with a DEFAULT_ prefix.
cat /etc/mailinabox.conf | sed s/^/DEFAULT_/ > /tmp/mailinabox.prev.conf
cat /etc/mailinabox.conf | sed s/^/DEFAULT_/ >/tmp/mailinabox.prev.conf
source /tmp/mailinabox.prev.conf
rm -f /tmp/mailinabox.prev.conf

# Since this is a second run, attempt to read overridden settings from $STORAGE_ROOT/mailinabox.conf
if [ -f $DEFAULT_STORAGE_ROOT/mailinabox.conf ]; then
cat $DEFAULT_STORAGE_ROOT/mailinabox.conf | sed s/^/DEFAULT_/ >/tmp/mailinabox.prev.conf
source /tmp/mailinabox.prev.conf
rm -f /tmp/mailinabox.prev.conf
fi
else
FIRST_TIME_SETUP=1
fi

# Put a start script in a global location. We tell the user to run 'mailinabox'
# in the first dialog prompt, so we should do this before that starts.
cat > /usr/local/bin/mailinabox << EOF;
cat >/usr/local/bin/mailinabox <<EOF
#!/bin/bash
cd $PWD
source setup/start.sh
Expand All @@ -61,9 +68,9 @@ source setup/questions.sh
# Skip on existing installs since we don't want this to block the ability to
# upgrade, and these checks are also in the control panel status checks.
if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
if [ -z "${SKIP_NETWORK_CHECKS:-}" ]; then
source setup/network-checks.sh
fi
if [ -z "${SKIP_NETWORK_CHECKS:-}" ]; then
source setup/network-checks.sh
fi
fi

# Create the STORAGE_USER and STORAGE_ROOT directory if they don't already exist.
Expand All @@ -82,17 +89,20 @@ if [ ! -d "$STORAGE_ROOT" ]; then
mkdir -p "$STORAGE_ROOT"
fi
f=$STORAGE_ROOT
while [[ $f != / ]]; do chmod a+rx "$f"; f=$(dirname "$f"); done;
while [[ $f != / ]]; do
chmod a+rx "$f"
f=$(dirname "$f")
done
if [ ! -f "$STORAGE_ROOT/mailinabox.version" ]; then
setup/migrate.py --current > "$STORAGE_ROOT/mailinabox.version"
setup/migrate.py --current >"$STORAGE_ROOT/mailinabox.version"
chown "$STORAGE_USER:$STORAGE_USER" "$STORAGE_ROOT/mailinabox.version"
fi

# Save the global options in /etc/mailinabox.conf so that standalone
# tools know where to look for data. The default MTA_STS_MODE setting
# is blank unless set by an environment variable, but see web.sh for
# how that is interpreted.
cat > /etc/mailinabox.conf << EOF;
cat >/etc/mailinabox.conf <<EOF
STORAGE_USER=$STORAGE_USER
STORAGE_ROOT=$STORAGE_ROOT
PRIMARY_HOSTNAME=$PRIMARY_HOSTNAME
Expand All @@ -101,6 +111,7 @@ PUBLIC_IPV6=$PUBLIC_IPV6
PRIVATE_IP=$PRIVATE_IP
PRIVATE_IPV6=$PRIVATE_IPV6
MTA_STS_MODE=${DEFAULT_MTA_STS_MODE:-enforce}
DKIM_SELECTOR=${DEFAULT_DKIM_SELECTOR:-mail}
EOF

# Start service configuration.
Expand All @@ -120,8 +131,7 @@ source setup/management.sh
source setup/munin.sh

# Wait for the management daemon to start...
until nc -z -w 4 127.0.0.1 10222
do
until nc -z -w 4 127.0.0.1 10222; do
echo "Waiting for the Mail-in-a-Box management daemon to start..."
sleep 2
done
Expand All @@ -143,13 +153,13 @@ source setup/firstuser.sh
# run in the recommended curl-pipe-to-bash method there is no TTY and
# certbot will fail if it tries to ask.
if [ ! -d "$STORAGE_ROOT/ssl/lets_encrypt/accounts/acme-v02.api.letsencrypt.org/" ]; then
echo
echo "-----------------------------------------------"
echo "Mail-in-a-Box uses Let's Encrypt to provision free SSL/TLS certificates"
echo "to enable HTTPS connections to your box. We're automatically"
echo "agreeing you to their subscriber agreement. See https://letsencrypt.org."
echo
certbot register --register-unsafely-without-email --agree-tos --config-dir "$STORAGE_ROOT/ssl/lets_encrypt"
echo
echo "-----------------------------------------------"
echo "Mail-in-a-Box uses Let's Encrypt to provision free SSL/TLS certificates"
echo "to enable HTTPS connections to your box. We're automatically"
echo "agreeing you to their subscriber agreement. See https://letsencrypt.org."
echo
certbot register --register-unsafely-without-email --agree-tos --config-dir "$STORAGE_ROOT/ssl/lets_encrypt"
fi

# Done.
Expand All @@ -166,16 +176,14 @@ if management/status_checks.py --check-primary-hostname; then
echo
echo "If you have a DNS problem put the box's IP address in the URL"
echo "(https://$PUBLIC_IP/admin) but then check the TLS fingerprint:"
openssl x509 -in "$STORAGE_ROOT/ssl/ssl_certificate.pem" -noout -fingerprint -sha256\
| sed "s/SHA256 Fingerprint=//i"
openssl x509 -in "$STORAGE_ROOT/ssl/ssl_certificate.pem" -noout -fingerprint -sha256 | sed "s/SHA256 Fingerprint=//i"
else
echo "https://$PUBLIC_IP/admin"
echo
echo "You will be alerted that the website has an invalid certificate. Check that"
echo "the certificate fingerprint matches:"
echo
openssl x509 -in "$STORAGE_ROOT/ssl/ssl_certificate.pem" -noout -fingerprint -sha256\
| sed "s/SHA256 Fingerprint=//i"
openssl x509 -in "$STORAGE_ROOT/ssl/ssl_certificate.pem" -noout -fingerprint -sha256 | sed "s/SHA256 Fingerprint=//i"
echo
echo "Then you can confirm the security exception and continue."
echo
Expand Down