dovecot & postfix servers
Install and configure dovecot (imap) and postfix (smtp) servers, co-deployed to share authentication. spamassassin is also installed to filter junk mail.
2021-06-11 Ubuntu 20.04
Overall configuration across the servers:
- Domain user
- user@domain
- Mailbox
/var/mail/vhosts/domain/user/Maildir/
- Authentication
-
Managed by dovecot, channel for postfix at
/var/spool/postfix/private/auth
- User lookup
/var/mail/vhosts/domain/passwd
- Password lookup
/var/mail/vhosts/domain/shadow
The example being used is to add domain user fred.nerk@geddy.au. The server domain names are assumed to be set up as:
s1.geddy.au
- Host on which the servers co-reside.
smtp.geddy.au
-
smtp server, pointing at
s1.geddy.au
. It must have a DNS A record not a DNS CNAME record tos1.geddy.au
– this is part of the security paradigm between open internet smtp servers. imap.geddy.au
- imap server, pointing at
s1.geddy.au
. pop.geddy.au
- pop server, pointing at
s1.geddy.au
.
postfix
graham:~ sudo vi /etc/hostname/etc/hostname
replace all content
smtp
/etc/hosts
add/update single setting
127.0.1.1 smtp.geddy.au
- Verify fully qualified domain name is correct.
- Select Satellite site in popup configuration, though it matters not what option is selected – it is about to be completely overwritten anyway.
/etc/postfix/main.cf
replace all content
# Virtual Domain Postfix (geddy.au) # See /usr/share/postfix/main.cf.dist for a commented, more complete version # A safety net that causes Postfix to run with # backwards-compatible default settings after an upgrade to a newer Postfix # version. See http://www.postfix.org/COMPATIBILITY_README.html. # # new installs: set to 2 compatibility_level = 2 # The UNIX system account that owns the Postfix queue and most Postfix daemon # processes. Specify the name of an unprivileged user account that does not # share a user or group ID with other accounts, and that owns no other files # or processes on the system. In particular, don't specify nobody or daemon. # PLEASE USE A DEDICATED USER ID AND GROUP ID. # # default, leave un-commented #mail_owner = postfix # The group ownership of set-gid Postfix commands and of group-writable # Postfix directories. # # default, leave commented-out #setgid_group = postdrop ##:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ## MTA Identification ##:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: # The internet hostname of this mail system. # myhostname = smtp.geddy.au # The domain name that locally-posted mail appears to come from, # and that locally posted mail is delivered to. # # default, leave commented-out #myorigin = /etc/mailname # The list of domains that are delivered via the $local_transport mail # delivery transport. # mydestination = $myhostname localhost localhost.$mydomain smtp.geddy.au s1.$mydomain # ^ note that domains are listed in virtual_mailbox_domains *instead* # The list of "trusted" remote SMTP clients that have more privileges # than "strangers". In particular, "trusted" SMTP clients are allowed # to relay mail through Postfix. # mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 ##:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ## LOCAL RECEIVE ##:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ## SENDMAIL(1) ::::::::::::::::::::::::::::::::::::::::::::::::::::::: ##:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ## LOCAL DELIVERY ##:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ## LOCAL(8) - LOCAL MAIL DELIVERY :::::::::::::::::::::::::::::::::::: ## Compatability # Whether or not to use the local biff service. # biff = no ## Delivery Method # The alias databases that are used for local(8) delivery. # alias_maps = hash:/etc/aliases # The alias databases for local(8) delivery that are updated with # "newaliases" or with "sendmail -bi". This is a separate configuration # parameter because not all the tables specified with $alias_maps have to # be local files. # alias_database = hash:/etc/aliases # The set of characters that can separate a user name from its # extension (example: user+foo), or a .forward file name from its # extension (example: .forward+foo). # recipient_delimiter = + ## Resource Controls # The maximal size of any local(8) individual mailbox or maildir # file, or zero (no limit). # mailbox_size_limit = 0 # The maximal size in bytes of a message, including envelope information. # #message_size_limit = 120480000 ## TRIVIAL-REWRITE(8) :::::::::::::::::::::::::::::::::::::::::::::::: ## Address Rewriting Rules # With locally submitted mail, append the string ".$mydomain" to # addresses that have no ".domain" information. # append_dot_mydomain = no ##:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ## NETWORK RECEIVE ##:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: # The network interface addresses that this mail system receives mail on. # The parameter also controls delivery of mail to user@[ip.address]. # inet_interfaces = all # The Internet protocols Postfix will attempt to use when making or # accepting connections. # #inet_protocols = all inet_protocols = ipv4 ## SMTPD(8) - SMTP SERVER :::::::::::::::::::::::::::::::::::::::::::: ## Compatability # Enable interoperability with remote SMTP clients that implement # an obsolete version of the AUTH command (RFC 4954). # broken_sasl_auth_clients = yes # Disable the SMTP VRFY command. # disable_vrfy_command = yes # Require that addresses received in SMTP MAIL FROM and RCPT TO # commands are enclosed with <>, and that those addresses do not # contain RFC 822 style comments or phrases. # strict_rfc821_envelopes = yes ## SASL # Enable SASL authentication in the Postfix SMTP server. # smtpd_sasl_auth_enable = yes # The name of the Postfix SMTP server's local SASL authentication # realm. # smtpd_sasl_local_domain = $mydomain # Postfix SMTP server SASL security options; as of Postfix 2.3 the # list of available features depends on the SASL server implemen- # tation that is selected with smtpd_sasl_type. # smtpd_sasl_security_options = noanonymous # Implementation-specific information that the Postfix SMTP client # passes through to the SASL plug-in implementation that is # selected with smtp_sasl_type. # smtpd_sasl_path = private/auth # The SASL plug-in type that the Postfix SMTP client should use # for authentication. # smtpd_sasl_type = dovecot ## STARTTLS # The SMTP TLS security level for the Postfix SMTP server; when a # non-empty value is specified, this overrides the obsolete param- # eters smtpd_use_tls and smtpd_enforce_tls. # # select one: # STARTTLS smtpd_tls_security_level = may # SSL/TLS #smtpd_tls_security_level = encrypt # When TLS encryption is optional in the Postfix SMTP server, do not # announce or accept SASL authentication over unencrypted connections. # smtpd_tls_auth_only = no # A directory containing (PEM format) CA certificates of root CAs # trusted to sign either remote SMTP client certificates or inter- # mediate CA certificates. # # do not provide (not signing client certs) #smtpd_tls_CApath = # File with the Postfix SMTP server RSA certificate in PEM format. # smtpd_tls_cert_file = /etc/letsencrypt/live/smtp.geddy.au/fullchain.pem # File with the Postfix SMTP server RSA private key in PEM format. # smtpd_tls_key_file = /etc/letsencrypt/live/smtp.geddy.au/privkey.pem # Request that the Postfix SMTP server produces Received: message # headers that include information about the protocol and cipher # used, as well as the remote SMTP client CommonName and client # certificate issuer CommonName. # smtpd_tls_received_header = yes # Name of the file containing the optional Postfix SMTP server TLS # session cache. # smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache ##:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ## NETWORK DELIVERY ##:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ## SMTP(8) - SMTP/LMTP CLIENT :::::::::::::::::::::::::::::::::::::::: ## STARTTLS # The default SMTP TLS security level for the Postfix SMTP client; # when a non-empty value is specified, this overrides the obsolete # parameters smtp_use_tls, smtp_enforce_tls, and # smtp_tls_enforce_peername. # # select one: # STARTTLS smtp_tls_security_level = may # SSL/TLS #smtp_tls_security_level = encyrpt # Directory with PEM format Certification Authority certificates # that the Postfix SMTP client uses to verify a remote SMTP server # certificate. # smtp_tls_CApath = /etc/ssl/certs # File with the Postfix SMTP client RSA certificate in PEM format. # # do not provide #smtp_tls_cert_file = # File with the Postfix SMTP client RSA private key in PEM format. # # do not provide #smtp_tls_key_file = # The external entropy source for the in-memory tlsmgr(8) pseudo # random number generator (PRNG) pool. # #tls_random_source = dev:/dev/urandom ## Resource and Rate Controls # The time unit over which client connection rates and other rates are # calculated. # anvil_rate_time_unit = 60s # The maximal number of recipients that the Postfix SMTP server # accepts per message delivery request. # smtpd_recipient_limit = 250 # How many simultaneous connections any client is allowed to make # to this service. # smtpd_client_connection_count_limit = 5 # The maximal number of connection attempts any client is allowed # to make to this service per time unit. # smtpd_client_connection_rate_limit = 5 # The number of errors a remote SMTP client is allowed to make # without delivering mail before the Postfix SMTP server slows # down all its responses. # smtpd_soft_error_limit = 2 # The number of errors a remote SMTP client is allowed to make # without delivering mail before the Postfix SMTP server slows # down all its responses. # smtpd_hard_error_limit = 3 # the SMTP server response delay after a client has made more than # $smtpd_soft_error_limit errors, and fewer than $smtpd_hard_error_limit # errors, without delivering mail. # smtpd_error_sleep_time = 5s # Wait until the RCPT TO command before evaluating # $smtpd_client_restrictions, $smtpd_helo_restrictions and # $smtpd_sender_restrictions, or wait until the ETRN command be‐ # fore evaluating $smtpd_client_restrictions and $smtpd_helo_re‐ # strictions. # smtpd_delay_reject = yes # Require that a remote SMTP client introduces itself with the # HELO or EHLO command before sending the MAIL command or other # commands that require EHLO negotiation. # smtpd_helo_required = yes # Optional restrictions that the Postfix SMTP server applies in # the context of a client connection request. # smtpd_client_restrictions = permit_mynetworks permit_sasl_authenticated reject_unknown_client_hostname reject_unauth_pipelining # reject_rbl_client zen.spamhaus.org # ^ recommended use RBL in spamassassin not MTA # Optional restrictions that the Postfix SMTP server applies in # the context of a client HELO command. # smtpd_helo_restrictions = reject_non_fqdn_hostname reject_invalid_helo_hostname # reject_unknown_helo_hostname # ^ rejects internal domain before chance to SASL so disable # Optional restrictions that the Postfix SMTP server applies in # the context of a client MAIL FROM command. # smtpd_sender_restrictions = permit_mynetworks reject_non_fqdn_sender reject_unknown_sender_domain # Optional restrictions that the Postfix SMTP server applies in # the context of a client RCPT TO command, after smtpd_relay_re‐ # strictions. # smtpd_recipient_restrictions = permit_mynetworks reject_unauth_pipelining permit_sasl_authenticated reject_invalid_hostname reject_non_fqdn_hostname reject_non_fqdn_sender reject_non_fqdn_recipient reject_unauth_destination # reject_rbl_client zen.spamhaus.org # reject_rbl_client dul.dnsbl.sorbs.net # ^ recommended use RBL in spamassassin not MTA # Access restrictions for mail relay control that the Postfix SMTP # server applies in the context of the RCPT TO command, before # smtpd_recipient_restrictions. # smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination ## VIRTUAL(8) - VIRTUAL DOMAIN DELIVERY AGENT :::::::::::::::::::::::: ## Mailbox Delivery # Optional lookup tables that alias specific mail addresses or domains # to other local or remote address. # virtual_alias_maps = hash:/etc/postfix/virtual/geddy.au # Postfix is final destination for the specified list of domains; # mail is delivered via the $virtual_transport mail delivery # transport. # virtual_mailbox_domains = /etc/postfix/vdomain # Optional lookup tables with all valid addresses in the domains # that match $virtual_mailbox_domains. # virtual_mailbox_maps = hash:/etc/postfix/vmailbox/geddy.au # A prefix that the virtual(8) delivery agent prepends to all # pathname results from $virtual_mailbox_maps table lookups. # virtual_mailbox_base = /var/mail/vhosts # The minimum user ID value that the virtual(8) delivery agent # accepts as a result from $virtual_uid_maps table lookup. # virtual_minimum_uid = 100 # Lookup tables with the per-recipient user ID that the virtual(8) # delivery agent uses while writing to the recipient's mailbox. # virtual_uid_maps = static:5000 # Lookup tables with the per-recipient group ID for virtual(8) # mailbox delivery. # virtual_gid_maps = static:5000 # The default mail delivery transport and next-hop destination for # final delivery to domains listed with $virtual_mailbox_domains. # #virtual_transport = virtual # call Dovecot's delivery (virtual) for each recipient instead of only # one time for all recipients. # dovecot_destination_recipient_limit = 1
/etc/aliases
replace all content
# formulate delivery address for (apparent) local user # important local mail roles postmaster: root admin: root nobody: /dev/null # local account re-redirects root: root+s1@geddy.au
-
User
vmail:vmail
exists to own the virtual user mail content. Ensure this account is disabled from login.
/etc/postfix/vmailbox/geddy.au
new file
# map virtual domain user to physical mailbox (geddy.au). # note: always use trailing '/' on mailbox file to ensure maildir format fred.nerk@geddy.au geddy.au/fred.nerk/Maildir/ #OFF robot@geddy.au geddy.au/robot/Maildir
/etc/postfix/virtual/geddy.au
new file
# formulate delivery addresses for virtual domain (geddy.au) # deal with domain root email if any root@geddy.au fred.nerk@geddy.au # important domain mail roles postmaster@geddy.au root@geddy.au webmaster@geddy.au root@geddy.au admin@geddy.au root@geddy.au abuse@geddy.au root@geddy.au nobody@geddy.au nobody@localhost robot@geddy.au nobody@geddy.au # domain pseudo-account redirects no-reply@geddy.au nobody@geddy.au # domain distribution lists # domain mail catch all - disabled to discourage spammer guesses #OFF @geddy.au root@geddy.au
/etc/postfix/vdomain
new file
# list of virtual domains recognised geddy.au
- This is just a list so
postmap
not required.
/etc/postfix/main.cf
add highlighted line (previous line also shown)
smtp inet n - y - - smtpd -o syslog_name=postfix/submission
submission inet n - y - - smtpd -o syslog_name=postfix/submission
smtps inet n - y - - smtpd -o syslog_name=postfix/smtps -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
# -o smtpd_recipient_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
# -o milter_macro_daemon_name=ORIGINATING -o smtpd_sasl_type=dovecot -o smtpd_sasl_path=private/auth
dovecot unix - n n - - pipe flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${recipient} spamassassin unix - n n - - pipe user=debian-spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
-
Preferably do not open firewall to ports for unencrypted services
such as
'Postfix'
.
We cannot start postfix until dependencies upon dovecot and spamassassin are satisfied.
dovecot
graham:~ sudo apt install dovecot-core dovecot-pop3d dovecot-impad dovecot-lmtpd dovecot-sieve graham:~ sudo systemctl stop dovecot # stop dovecot for updating
The configuration of dovecot is spread across many files
under /etc/dovecot/conf.d/
.
/etc/dovecot/conf.d/10-auth.conf
change settings
disable_plaintext_auth = no
auth_mechanisms = plain login
#!include auth-system.conf.ext !include auth-passwdfile.conf.ext
/etc/dovecot/conf.d/10-mail.conf
change settings
mail_location = maildir:/var/mail/vhosts/%d/%n/Maildir
separator = /
#namespace virtual { # type = private # prefix = Virtual/ # separator = / # location = maildir:/var/mail/vhosts/%d/%n/Maildir/virtual # list = children #} namespace { type = public separator = / prefix = Public/ location = maildir:/var/mail/vhosts/%d/public subscriptions = yes list = children }
/etc/dovecot/conf.d/10-master.conf
change settings
#unix_listener auth-userdb { #mode = 0666 #user = #group = extra_groups = vmail #} # Postfix smtp-auth unix_listener /var/spool/postfix/private/auth { user = postfix group = vmail mode = 0600 } unix_listener auth-master { user = vmail group = vmail mode = 0600 }
/etc/dovecot/conf.d/10-ssl.conf
change settings
ssl = yes ssl_cert = /etc/letsencrypt/live/imap.geddy.au/fullchain.pem ssl_key = /etc/letsencrypt/live/imap.geddy.au/privkey.pem ssl_client_ca_dir = /etc/ssl/certs
/etc/dovecot/conf.d/15-lda.conf
change settongs
protocol lda { # Space separated list of plugins to load (default is global mail_plugins). #mail_plugins = $mail_plugins auth_socket_path = /var/run/dovecot/auth-master hostname = imap.geddy.au mail_plugin_dir = /usr/lib/dovecot/modules mail_plugins = sieve postmaster_address = postmaster }
/etc/dovecot/conf.d/auth-passwdfile.conf.ext
change settings
passdb { driver = passwd-file args = /var/mail/vhosts/%d/shadow } userdb { driver = passwd-file args = /var/mail/vhosts/%d/passwd # Default fields that can be overridden by passwd-file #default_fields = quota_rule=*:storage=1G # Override fields from passwd-file #override_fields = home=/home/virtual/%u }
-
Preferably do not open firewall to ports for unencrypted services
such as
'Dovecot IMAP'
and'Dovecot POP3'
.
# purge Trash and Junk older than a month - monthly # (-A option fails for sqlite backend, so workaround by iterating users) #11 5 1 * * doveadm -v expunge -A mailbox Trash savedbefore 4w 11 5 1 * * for user in $(cut -f1 -d: /var/mail/vhosts/*/passwd) ; do doveadm -Dv expunge -u "$user" mailbox Trash savedbefore 4w ; done #12 5 1 * * doveadm -v expunge -A mailbox Spam savedbefore 4w 12 5 1 * * for user in $(cut -f1 -d: /var/mail/vhosts/*/passwd) ; do doveadm -Dv expunge -u "$user" mailbox Spam savedbefore 4w ; done
spamassassin
graham:~ sudo apt install spamassassin graham:~ sudo systemctl stop spamassassin # stop spamassassin for updating graham:~ sudo vi /etc/default/spamassassin/etc/default/spamassassin
change setting
CRON=1
/etc/spamassassin/local.cf
change settings
rewrite_header Subject *****SPAM _SCORE_*****
report_safe 0
trusted_networks s0.geddy.au s1.geddy.au #trusted_networks 142.250.0.0/15 # google
# lock_method flock
# required_score 5.0
# use_bayes 1
# bayes_auto_learn 1
shortcircuit ALL_TRUSTED on
# Turning on the skip_rbl_checks setting will disable the DNSEval plugin, # which implements Real-time Block List (or: Blackhole List) (RBL) lookups. # By default, SpamAssassin will run RBL checks. Individual blocklists may # be disabled selectively by setting a score of a corresponding rule to 0. # # skip_rbl_checks 0 # Turning on the skip_uribl_checks setting will disable the URIDNSBL plugin. # By default, SpamAssassin will run URI DNSBL checks. Individual URI # blocklists may be disabled selectively by setting a score of a corresponding # rule to 0 or through the uridnsbl_skip_domain parameter. Turning on the # skip_uribl_checks setting will disable the URIDNSBL plugin. # # skip_urirbl_checks 0
/etc/spamassassin/65_debian.cf
comment out last line
#score RCVD_IN_BRBL_LASTEXT 0
- This enables barracuda blacklist, but…
-
You must register (free! it just helps stop their free service being abused
on open internet). It takes a few minutes and you must specify to them
your server's IP address.
- Browse to
https://barracudacentral.org
; - Select BRBL > Account;
- Enter your details; and
-
Provide the IP addresses of both
s0.geddy.au
ands1.geddy.au
.
- Browse to
- Once registration confirmed, move on to the test below.
-
Verify address reported is
127.0.0.2
, indicating barracuda account is working.
Start them all
graham:~ sudo systemctl start postfix dovecot spamassassin graham:~ sudo systemctl status postfix dovecot spamassassin graham:~ sudo tail -f /var/log/mail.log- Verify the services have started cleanly.
-
Verify the test mail was routed from local
root
to the virtual address designated for it in/etc/aliases
(presumably an imap account we have created).
Appendix: Client configuration
For our example virtual user:
- user
- fred.nerk@geddy.au
- password
- ****(as assigned)****
- incoming
- imap.geddy.au:993 SSL/TLS
- outgoing
- smtp.geddy.au:465 SSL/TLS
Appendix: Script to add virtual user
graham:~ sudo vi /usr/local/sbin/add-virtual-user/use/local/sbin/add-virtual-user
new file
#!/bin/bash # add-virtual-user: add user to postfix and dovecot virtual domain # (c)2021 Graham Eddy; provided under GPLv3 license # run as superuser # # originated from script on https://tech.tiq.cc/2014/02/how-to-set-up-an-\ #vemail-server-with-postfix-and-dovecot-without-mysql-on-debian-7/ abort() { [ $# -gt 0 ] && echo "$@" 1>&2 exit 2 } usage() { echo "usage: $0 virtuser password [basedir]" 1>& 2 abort $@ } # check privileges [ "$EUID" == 0 ] || abort "$0: not superuser" # process command line args case $# in 2 | 3 ) virtuser="$1" { echo "$virtuser" | grep '@' > /dev/null ; } || abort "$virtuser: no @ in virtuser" username=$(echo "$virtuser" | cut -f1 -d'@') domain=$(echo "$virtuser" | cut -f2 -d'@') [ "$username" ] || abort "$virtuser: missing username" [ "$domain" ] || abort "$virtuser: missing domain" passwd="$2" basedir="${3:-$(postconf | grep '^virtual_mailbox_base ' | cut -f3 -d' ')}" ;; * ) usage ;; esac grep "$domain" /etc/postfix/vdomain > /dev/null || abort "$domain: domain not defined" ######################################################################## # postfix # container for domains mkdir -p /etc/postfix/vmailbox || abort chown root:root /etc/postfix/vmailbox || abort chmod 755 /etc/postfix/vmailbox || abort # this domain vmailbox_file="/etc/postfix/vmailbox/$domain" [ -f "$vmailbox_file" ] || touch "$vmailbox_file" chown root:root "$vmailbox_file" || abort chmod 644 "$vmailbox_file" || abort # this user if grep "^$virtuser " "$vmailbox_file" > /dev/null ; then tty -s && echo "$virtuser: virtuser already known to Postfix, skipped add" else # force maildir format by using trailing '/' echo "$virtuser $domain/$username/Maildir/" >> "$vmailbox_file" || abort postmap "$vmailbox_file" || abort fi # no need to restart postfix ######################################################################## # dovecot dovecot_changed= # container for domains mkdir -p "$basedir" || abort chown vmail:vmail "$basedir" || abort chmod 2750 "$basedir" || abort # this domain mkdir -p "$basedir/$domain" || abort chown vmail:vmail "$basedir/$domain" || abort chmod 2755 "$basedir/$domain" || abort # user database for this domain passwd_file="$basedir/$domain/passwd" [ -f "$passwd_file" ] || touch "$passwd_file" || abort chown root:dovecot "$passwd_file" || abort chmod 644 "$passwd_file" || abort # add this user if grep "^$virtuser:" "$passwd_file" > /dev/null ; then tty -s && echo "$virtuser: virtuser already known to Dovecot/passwd, skipped add" else echo "$virtuser::5000:5000::$basedir/$domain/$username" >> "$passwd_file" || abort dovecot_changed=yes fi # password database for this domain shadow_file="$basedir/$domain/shadow" [ -f "$shadow_file" ] || touch "$shadow_file" || abort chown root:dovecot "$shadow_file" || abort chmod 640 "$shadow_file" || abort # add this user's password (uses `doveadm pw` to encrypt password) if grep "^$virtuser:" "$shadow_file" > /dev/null ; then tty -s && echo "$virtuser: virtuser already known to Dovecot/shadow, skipped add" else echo "$virtuser:$(doveadm pw -p $passwd)" >> "$shadow_file" || abort dovecot_changed=yes fi # restart dovecot if config changed [ "$dovecot_changed" ] && systemctl reload dovecot