So, the default Debian configuration of Exim allow for one smarthost. So when you send mail and you have to route it through your ISP's relay in order to be able to send mail at all, that is what that is there for. But what happens when you want to have more than one smarthost, because you have more than one email address and SPF whines if you send mail as "joe.bloggs@company.com" via your ISP smarthost rather than through "company.com" mail system? The question has been asked on Debian lists more than once, and the answer there seems to have been "tough". So I took it as a personal challenge.
First, Postgresql
# su -s /bin/bash postgres $ createuser -D -R -S -P Debian-exim $ createdb -O Debian-exim exim $ ^D # su -s /bin/bash Debian-exim $ psql exim exim=> create table smarthosts (sender VARCHAR(128) not NULL, smarthost VARCHAR(128) not NULL, port VARCHAR(16) not NULL, username VARCHAR(128) not NULL, password VARCHAR(128) not NULL, primary key (sender)); exim=> insert into smarthosts (sender,smarthost,port,username,password) values ('sender@company.com','smtp.company.com','587','senderid','password'); exim=> insert into smarthosts (sender,smarthost,port,username,password) values ('default','smtp.isp.com','465','othersender','password'); exim=> ^D $ ^D
At this point, we have a database "exim", a table "smarthosts" and a default smarthost and a smarthost to use when we send as "sender@company.com". You need to edit "/etc/postgresql/15/main/pg_hba.conf" as root and make sure the database is available over the network if you are not running Exim on the same host as the database. Now we turn our attention to Exim.
Add the lines below to /etc/exim4/conf.d/main/000_local_macros
hide pgsql_servers = database-host/exim/Debian-exim/password SMARTHOST_HOST = ${lookup pgsql{SELECT smarthost FROM smarthosts WHERE sender = '${quote_pgsql:$sender_address}' OR sender = 'default' LIMIT 1}{$value}} SMARTHOST_PORT = ${lookup pgsql{SELECT port FROM smarthosts WHERE sender = '${quote_pgsql:$sender_address}' OR sender = 'default' LIMIT 1}{$value}} SMARTHOST_USER = ${lookup pgsql{SELECT username FROM smarthosts WHERE sender = '${quote_pgsql:$sender_address}' OR sender = 'default' LIMIT 1}{$value}} SMARTHOST_PASS = ${lookup pgsql{SELECT password FROM smarthosts WHERE sender = '${quote_pgsql:$sender_address}' OR sender = 'default' LIMIT 1}{$value}}
Add the following to /etc/exim4/conf.d/auth/30_exim4-config_examples
cram_md5: driver = cram_md5 public_name = CRAM-MD5 client_name = SMARTHOST_USER client_secret = SMARTHOST_PASS
and then replace the statement
PASSWDLINE=${sg{\ ${lookup{$sender_address}nwildlsearch{CONFDIR/passwd.client}{$value}fail}\ }\ {\\N[\\^]\\N}\ {^^}\ }
with
PASSWDLINE=${sg{SMARTHOST_USER:SMARTHOST_PASS}{\\N[\\^]\\N}{^^}}
Create the file /etc/exim4/conf.d/transport/30_exim4-config_remote_smtp_auto with the following content:
remote_smtp_auto: debug_print = "T: remote_smtp_auto for $local_part@$domain from $sender_address" driver = smtp multi_domain hosts_require_auth = SMARTHOST_HOST hosts_require_tls = SMARTHOST_HOST port = SMARTHOST_PORT hosts_try_auth = <; SMARTHOST_USER:SMARTHOST_PASS
Create the file /etc/exim4/conf.d/router/199_exim4-config_smarthost_auto with the following content:
smarthost_auto: debug_print = "T: smarthost_auto remote_smtp_auto for $local_part@$domain from $sender_address" self = send driver = manualroute transport = remote_smtp_auto route_data = SMARTHOST_HOST domains = ! +local_domains
Then we run "update-exim4.conf.template -r" and "update-exim4.conf --removecomments -o /etc/exim4/exim4.conf" to generate the exim configuration, and then restart exim with "systemctl restart exim4.service".
So what does all this do? Well, the macros pull data out of the database for the smarthost host, port, and username/password authentication for the smarthost. If the sender address does not exist, the COALESCE statement pulls the 'default' smarthost instead. So that means that you can add many special-case smarthosts for sender addresses and still have a default smarthost. The cram_md5 stanza works for authenticating against the smarthost, and the router and transport we put in checks if we have a smarthost and uses it. It took me some time to understand how things worked in the router/transport and that you needed to move the OR logic to the SQL statement rather than using Exim's '${if' conditional. This means that your Exim install is going to use the "auto" router and transport for everything, as it'll always fall through to the default if there isn't a special case.
The reason for using a Postgres database for this rather than files? So you can have multiple instances of Exim pointing at the same database and table, and they will behave identically. It then does not matter which Exim instance you send mail through, it works the same. Overkill for a few users perhaps, but if you need to scale it up to thousands - you will want to have a DB backend. It also removes clutter in the /etc/exim4/ directory. You can in a similar way move blacklists and whitelists into a database table and just use macros to query the database as and when needed.