OpenSMTPD and Dovecot with a shared PostgreSQL, Sieve and RSpamd on OpenBSD 6.6
I finally got around to setting up a new mailserver and i decided to give OpenSMTPD a try. It wasn’t a natural birth, i can tell you that. The switching of the configuration syntax makes for a lot of outdated Google Search results.
So what are we going to setup. Well the title gave it away i guess, so for the slow ones amongst you: we are building a Mailserver with OpenSMTPD, Dovecot, RSpamd and Sieve. The OpenSMTPD and the Dovecot will both be using the same authentication table and hashing scheme, making this a nifty solution.
psql -Upostgres <<EOF CREATE USER mail WITH ENCRYPTED PASSWORD 'your.mail.password'; CREATE DATABASE mail OWNER mail; EOF
psql -Umail mail <<EOF
-- this is the table for the users accounts CREATE TABLE public.accounts ( id serial, email character varying(255) DEFAULT ''::character varying NOT NULL, password character varying(255) DEFAULT ''::character varying NOT NULL, active boolean DEFAULT true NOT NULL );
-- this is the table for the virtual mappings for email -> email CREATE TABLE public.virtuals ( id serial, email character varying(255) DEFAULT ''::character varying NOT NULL, destination character varying(255) DEFAULT ''::character varying NOT NULL );
-- this view is used to determine where to deliver things CREATE VIEW public.delivery AS SELECT virtuals.email, virtuals.destination FROM public.virtuals WHERE (length((virtuals.email)::text) > 0) UNION SELECT accounts.email, 'vmail'::character varying AS destination FROM public.accounts WHERE (length((accounts.email)::text) > 0);
-- this view is used to determine which domains this server is serving CREATE VIEW public.domains AS SELECT split_part((virtuals.email)::text, '@'::text, 2) AS domain FROM public.virtuals WHERE (length((virtuals.email)::text) > 0) GROUP BY (split_part((virtuals.email)::text, '@'::text, 2)) UNION SELECT split_part((accounts.email)::text, '@'::text, 2) AS domain FROM public.accounts WHERE (length((accounts.email)::text) > 0) GROUP BY (split_part((accounts.email)::text, '@'::text, 2));
-- this view should control the email addresses users can send with CREATE VIEW public.sending AS SELECT virtuals.email, virtuals.destination AS login FROM public.virtuals WHERE (length((virtuals.email)::text) > 0) UNION SELECT accounts.email, accounts.email AS login FROM public.accounts WHERE (length((accounts.email)::text) > 0); EOF
/etc/mail/postgres.conf
Next we configure the PostgreSQL lookups for smtpd:
1 2 3 4 5
conninfo host='localhost' user='mail' password='your.mail.password' dbname='mail' query_alias SELECT "destination" FROM delivery WHERE "email"=$1; query_credentials SELECT "email", "password" FROM accounts WHERE "email"=$1; query_domain SELECT "domain" FROM domains WHERE "domain"=$1; query_mailaddrmap SELECT "email" FROM sending WHERE "login"=$1;
Also since this file contains the password to the database, only _smtp should be able to read it:
listen on all tls pki replace.with.host.name filter { check_dyndns, check_rdns, check_fcrdns, senderscore, rspamd } #listen on all port smtps smtps pki replace.with.host.name auth <auths> senders <sendermap> masquerade #listen on all port submission tls-require pki replace.with.host.name auth <auths> senders <sendermap> masquerade listen on all port smtps smtps pki replace.with.host.name auth <auths> listen on all port submission tls-require pki replace.with.host.name auth <auths>
action "receive_aliases" lmtp "/var/dovecot/lmtp" rcpt-to alias <aliases> match from local for local action "receive_aliases"
action "receive_vmail" lmtp "/var/dovecot/lmtp" rcpt-to virtual <virtuals> match from any for domain <domains> action "receive_vmail"
action "outbound" relay helo replace.with.host.name match from auth for any action "outbound"
And finally start the smtpd:
1
rcctl start smtpd
/etc/rspamd/worker-proxy.inc
In this file i actually just changed the spam_header to X-Spam-Status, but this optional.
I’ve chosen to only put in things you need to change or append, everything else should remain as is.
Why did i do this? Well since dovecot has evolved into this nice configuration-file layout, i decided that this is the most efficient way to keep this document clean and relevant.
/etc/dovecot/conf.d/10-auth.conf
Towards the beginning of the file, disable plaintext authentication:
1
disable_plaintext_auth = yes
Then at the end of the file, there are several includes. We are going to comment the auth-system.conf.ext and are going to comment in the auth-sql.conf.ext instead:
password_query = SELECT email AS user, '{CRYPT}' || password AS password FROM accounts WHERE active = true AND email = '%u' AND email != '' AND password != '' user_query = SELECT email FROM delivery WHERE email = LOWER('%u')
/etc/dovecot/dovecot.conf
Last but not least we update the protocols we are going to use:
1
protocols = imap pop3 lmtp
And finally start the dovecot:
1
rcctl start dovecot
Adding Accounts and Aliases
To generate securely hashed passwords, you can use “smtpctl encrypt” and then enter your password. The resulting hash can be used as replacement for PASSWORD:
1 2
INSERT INTO accounts (email,password) VALUES ('my@first.email.address','PASSWORD'); INSERT INTO virtuals (email,destination) VALUES ('my@second.mail.address','my@first.email.address');
That’s it
You should now be able to use this setup as expected.
If you find any errors, you can find me on Twitter and let me know!