Skip to content
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
13 changes: 13 additions & 0 deletions docs/debian-conf.d/main/00_vexim_listmacrosdefs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ keep_environment =
add_environment = MAIN_ADD_ENVIRONMENT
.endif

# DKIM
# Requires dkim.sql migration to be run
DKIM_DOMAIN = ${lc:${domain:$h_from:}}
DKIM_SELECTOR = ${lookup mysql {SELECT d.selector FROM dkim d JOIN domains dom ON d.domain_id = dom.domain_id WHERE dom.domain = '${quote_mysql:DKIM_DOMAIN}' AND d.enabled = 1}{$value}fail}
DKIM_PRIVATE_KEY = ${lookup mysql {SELECT d.private_key FROM dkim d JOIN domains dom ON d.domain_id = dom.domain_id WHERE dom.domain = '${quote_mysql:DKIM_DOMAIN}' AND d.enabled = 1}{$value}fail}
DKIM_CANON = relaxed
DKIM_STRICT = 0
DKIM_TIMESTAMPS = 0
Comment on lines +45 to +47

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these hardcoded, especially DKIM_CANON which has its own column in the database?

Also, I don't like how you didn't define DKIM_SIGN_HEADERS on purpose, but kept the database column. IMO, a commented out macro with an explanation about why it's been commented out would fit here better.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, here's my DKIM_SIGN_HEADERS:

DKIM_SIGN_HEADERS = +Content-Type:+MIME-Version:+Date:+Message-ID:+To:+Subject:+From:+Sender:=Reply-To:+Cc:+Content-ID:+Content-Description:=Resent-Date:=Resent-From:=Resent-Sender:=Resent-To:=Resent-Cc:=Resent-Message-ID:+In-Reply-To:+References:=List-Id:=List-Help:=List-Unsubscribe:=List-Subscribe:=List-Post:=List-Owner:=List-Archive

I think it's been working nicely for me for quite a while now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to not use the default which conforms to RFC?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really remember what problem I was solving by editing these values myself. It's probably what I said here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly it's as it is to start a discussion on what is needed/wanted.

We hard code them at work because we have a lot of email accounts and reducing sql look ups is never a bad thing (you can set the canon via sql if you really wanted, hence it's there).

Same with DKIM sign headers, those are left to Exims defaults but if people want to change them per domain they can?

You have to remember there was no activity on this repo, I had no idea what direction you wanted to go with it.

The table structure doesn't seem to protect against having duplicate records for the same domain, or having none. What happens in these cases?

Why would it? How do you rotate secrets if you just delete the old one? Any mail in transit that hasn't caught up with the new key in the dns would fail?

As for having no key, it would fail. That's a personal preference, in todays world of email not having a dkim key and trying to send email is kind of pointless.

You could do something like

DKIM_SELECTOR = ${lookup mysql {SELECT d.selector FROM dkim d JOIN domains dom ON d.domain_id = dom.domain_id WHERE dom.domain = '${quote_mysql:DKIM_DOMAIN}' AND d.enabled = 1}{$value}{}}
DKIM_PRIVATE_KEY = ${lookup mysql {SELECT d.private_key FROM dkim d JOIN domains dom ON d.domain_id = dom.domain_id WHERE dom.domain = '${quote_mysql:DKIM_DOMAIN}' AND d.enabled = 1}{$value}{}}

And have in the transports have something that looks something like:

dkim_selector = ${if def:DKIM_SELECTOR {DKIM_SELECTOR}{}}
dkim_private_key = ${if def:DKIM_PRIVATE_KEY {DKIM_PRIVATE_KEY}{}}

Which wouldn't sign the email if it didn't have a key (in theory, currently untested)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you rotate the public keys on your DNS servers? Automaded/Manual?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fully automated (thousands of domains would take days!), every 3 months a new one gets generated, dns runs off a sql backend (pdns) so it's dead simple. Generate key, update the dns sql and add the new key to email database. A week later the old key gets deleted. The old key is kept so dns propagation isn't an issue.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to mention, the keys get a new selector when they are rotated so it would be jan2026 for one and the new one would be jun2026 for example.


# Disable DKIM verification for incoming mail (since we don't want to verify inbound)
DKIM_VERIFY_DOMAIN = ${if match_domain{$sender_address_domain}{+local_domains}{0}{1}}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a new macro? I don't see it being used on Bookworm.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't actually mean to leave that in there..

Was working on an acl that uses it

acl_smtp_dkim:
  # Only verify external senders
  condition = ${if eq {DKIM_VERIFY_DOMAIN}{1}}
  
  # If verification fails, set a variable
  warn
    dkim_status = fail
    set acl_m_dkim_fail = yes
    log_message = DKIM failure from $sender_address_domain: $dkim_verify_reason

  # Accept otherwise
  accept

But exim does (at least on trixie) have this on the check_rcpt acl

.ifdef DISABLE_DKIM_VERIFY

So it seemed kinda pointless



# validation of sending mailserver
#CHECK_RCPT_REVERSE_DNS = true
#CHECK_RCPT_SPF = true
Expand Down
15 changes: 15 additions & 0 deletions docs/dkim/dkim.sql

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we ship with this functionality, then this file should go to docs/setup/migrations, and if not, maybe all of this could just go to the Wiki?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in 2026 it needs some kind of dkim in the core vexim setup, DKIM has been around since the mid 00s.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CREATE TABLE `dkim` (
`dkim_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`domain_id` INT(10) UNSIGNED NOT NULL,
`selector` VARCHAR(63) NOT NULL DEFAULT 'default',
`private_key` TEXT NOT NULL,
`public_key` TEXT NOT NULL,
`canonical` ENUM('simple', 'relaxed') NOT NULL DEFAULT 'relaxed',
`sign_headers` VARCHAR(255) DEFAULT NULL, -- Optional: custom headers to sign
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`dkim_id`),
UNIQUE KEY `domain_selector` (`domain_id`, `selector`),
FOREIGN KEY (`domain_id`) REFERENCES `domains` (`domain_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;