parent
61123a6453
commit
ecd88f91a0
@ -0,0 +1,247 @@ |
||||
{ config, lib, pkgs, ... }: |
||||
|
||||
with lib; |
||||
|
||||
let |
||||
name = "maddy"; |
||||
cfg = config.services.maddy; |
||||
defaultConfig = '' |
||||
tls off |
||||
|
||||
auth.pass_table local_authdb { |
||||
table sql_table { |
||||
driver sqlite3 |
||||
dsn credentials.db |
||||
table_name passwords |
||||
} |
||||
} |
||||
|
||||
storage.imapsql local_mailboxes { |
||||
driver sqlite3 |
||||
dsn imapsql.db |
||||
} |
||||
|
||||
table.chain local_rewrites { |
||||
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3" |
||||
optional_step static { |
||||
entry postmaster postmaster@$(primary_domain) |
||||
} |
||||
optional_step file /etc/maddy/aliases |
||||
} |
||||
msgpipeline local_routing { |
||||
destination postmaster $(local_domains) { |
||||
modify { |
||||
replace_rcpt &local_rewrites |
||||
} |
||||
deliver_to &local_mailboxes |
||||
} |
||||
default_destination { |
||||
reject 550 5.1.1 "User doesn't exist" |
||||
} |
||||
} |
||||
|
||||
smtp tcp://0.0.0.0:25 { |
||||
limits { |
||||
all rate 20 1s |
||||
all concurrency 10 |
||||
} |
||||
dmarc yes |
||||
check { |
||||
require_mx_record |
||||
dkim |
||||
spf |
||||
} |
||||
source $(local_domains) { |
||||
reject 501 5.1.8 "Use Submission for outgoing SMTP" |
||||
} |
||||
default_source { |
||||
destination postmaster $(local_domains) { |
||||
deliver_to &local_routing |
||||
} |
||||
default_destination { |
||||
reject 550 5.1.1 "User doesn't exist" |
||||
} |
||||
} |
||||
} |
||||
|
||||
submission tcp://0.0.0.0:587 { |
||||
limits { |
||||
all rate 50 1s |
||||
} |
||||
auth &local_authdb |
||||
source $(local_domains) { |
||||
check { |
||||
authorize_sender { |
||||
prepare_email &local_rewrites |
||||
user_to_email identity |
||||
} |
||||
} |
||||
destination postmaster $(local_domains) { |
||||
deliver_to &local_routing |
||||
} |
||||
default_destination { |
||||
modify { |
||||
dkim $(primary_domain) $(local_domains) default |
||||
} |
||||
deliver_to &remote_queue |
||||
} |
||||
} |
||||
default_source { |
||||
reject 501 5.1.8 "Non-local sender domain" |
||||
} |
||||
} |
||||
|
||||
target.remote outbound_delivery { |
||||
limits { |
||||
destination rate 20 1s |
||||
destination concurrency 10 |
||||
} |
||||
mx_auth { |
||||
dane |
||||
mtasts { |
||||
cache fs |
||||
fs_dir mtasts_cache/ |
||||
} |
||||
local_policy { |
||||
min_tls_level encrypted |
||||
min_mx_level none |
||||
} |
||||
} |
||||
} |
||||
|
||||
target.queue remote_queue { |
||||
target &outbound_delivery |
||||
autogenerated_msg_domain $(primary_domain) |
||||
bounce { |
||||
destination postmaster $(local_domains) { |
||||
deliver_to &local_routing |
||||
} |
||||
default_destination { |
||||
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses" |
||||
} |
||||
} |
||||
} |
||||
|
||||
imap tcp://0.0.0.0:143 { |
||||
auth &local_authdb |
||||
storage &local_mailboxes |
||||
} |
||||
''; |
||||
|
||||
in { |
||||
options = { |
||||
services.maddy = { |
||||
enable = mkEnableOption "Maddy, a free an open source mail server"; |
||||
|
||||
user = mkOption { |
||||
default = "maddy"; |
||||
type = with types; uniq string; |
||||
description = '' |
||||
Name of the user under which maddy will run. If not specified, a |
||||
default user will be created. |
||||
''; |
||||
}; |
||||
group = mkOption { |
||||
default = "maddy"; |
||||
type = with types; uniq string; |
||||
description = '' |
||||
Name of the group under which maddy will run. If not specified, a |
||||
default group will be created. |
||||
''; |
||||
}; |
||||
|
||||
hostname = mkOption { |
||||
default = "localhost"; |
||||
type = with types; uniq string; |
||||
example = ''example.com''; |
||||
description = '' |
||||
Hostname to use. It should be FQDN. |
||||
''; |
||||
}; |
||||
primaryDomain = mkOption { |
||||
default = "localhost"; |
||||
type = with types; uniq string; |
||||
example = ''mail.example.com''; |
||||
description = '' |
||||
Primary MX domain to use. It should be FQDN. |
||||
''; |
||||
}; |
||||
localDomains = mkOption { |
||||
type = with types; listOf str; |
||||
default = ["$(primary_domain)"]; |
||||
example = [ |
||||
"$(primary_domain)" |
||||
"example.com" |
||||
"other.example.com" |
||||
]; |
||||
description = '' |
||||
Define list of allowed domains. |
||||
''; |
||||
}; |
||||
config = mkOption { |
||||
type = with types; nullOr lines; |
||||
default = defaultConfig; |
||||
description = '' |
||||
Server configuration. |
||||
''; |
||||
}; |
||||
|
||||
openFirewall = mkOption { |
||||
type = types.bool; |
||||
default = false; |
||||
description = '' |
||||
Open the configured incoming and outgoing mail server ports. |
||||
''; |
||||
}; |
||||
|
||||
}; |
||||
}; |
||||
|
||||
config = mkIf cfg.enable { |
||||
|
||||
systemd = { |
||||
packages = [ pkgs.maddy ]; |
||||
services.maddy = { |
||||
serviceConfig = { |
||||
User = "${cfg.user}"; |
||||
Group = "${cfg.group}"; |
||||
}; |
||||
wantedBy = [ "multi-user.target" ]; |
||||
}; |
||||
}; |
||||
|
||||
environment.etc."maddy/maddy.conf" = { |
||||
text = '' |
||||
$(hostname) = ${cfg.hostname} |
||||
$(primary_domain) = ${cfg.primaryDomain} |
||||
$(local_domains) = ${toString cfg.localDomains} |
||||
hostname ${cfg.hostname} |
||||
${cfg.config} |
||||
''; |
||||
}; |
||||
|
||||
users.users = optionalAttrs (cfg.user == "maddy") { |
||||
maddy = { |
||||
description = "Maddy service user"; |
||||
group = cfg.group; |
||||
home = "/var/lib/maddy"; |
||||
createHome = true; |
||||
isSystemUser = true; |
||||
}; |
||||
}; |
||||
|
||||
users.groups = mkIf (cfg.group == "maddy") { |
||||
maddy = pkgs.lib.mkForce { |
||||
name = cfg.group; |
||||
}; |
||||
}; |
||||
|
||||
networking.firewall = mkIf cfg.openFirewall { |
||||
allowedTCPPorts = [ 25 143 587 ]; |
||||
}; |
||||
|
||||
environment.systemPackages = [ |
||||
pkgs.maddy |
||||
]; |
||||
}; |
||||
} |
@ -0,0 +1,58 @@ |
||||
import ./make-test-python.nix ({ pkgs, ... }: { |
||||
name = "maddy"; |
||||
meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; }; |
||||
|
||||
nodes = { |
||||
server = { ... }: { |
||||
services.maddy = { |
||||
enable = true; |
||||
hostname = "server"; |
||||
primaryDomain = "server"; |
||||
openFirewall = true; |
||||
}; |
||||
}; |
||||
|
||||
client = { ... }: { |
||||
environment.systemPackages = [ |
||||
(pkgs.writers.writePython3Bin "send-testmail" { } '' |
||||
import smtplib |
||||
from email.mime.text import MIMEText |
||||
|
||||
msg = MIMEText("Hello World") |
||||
msg['Subject'] = 'Test' |
||||
msg['From'] = "postmaster@server" |
||||
msg['To'] = "postmaster@server" |
||||
with smtplib.SMTP('server', 587) as smtp: |
||||
smtp.login('postmaster@server', 'test') |
||||
smtp.sendmail('postmaster@server', 'postmaster@server', msg.as_string()) |
||||
'') |
||||
(pkgs.writers.writePython3Bin "test-imap" { } '' |
||||
import imaplib |
||||
|
||||
with imaplib.IMAP4('server') as imap: |
||||
imap.login('postmaster@server', 'test') |
||||
imap.select() |
||||
status, refs = imap.search(None, 'ALL') |
||||
assert status == 'OK' |
||||
assert len(refs) == 1 |
||||
status, msg = imap.fetch(refs[0], 'BODY[TEXT]') |
||||
assert status == 'OK' |
||||
assert msg[0][1].strip() == b"Hello World" |
||||
'') |
||||
]; |
||||
}; |
||||
}; |
||||
|
||||
testScript = '' |
||||
start_all() |
||||
server.wait_for_unit("maddy.service") |
||||
server.wait_for_open_port(143) |
||||
server.wait_for_open_port(587) |
||||
|
||||
server.succeed("echo test | maddyctl creds create postmaster@server") |
||||
server.succeed("maddyctl imap-acct create postmaster@server") |
||||
|
||||
client.succeed("send-testmail") |
||||
client.succeed("test-imap") |
||||
''; |
||||
}) |
Loading…
Reference in new issue