retain "config.mail_domain" as the domain part of @ email addresses, so for ipv4 relays "[1.2.3.4]" and introduce config.ipv4_relay and config.mail_domain_bare helpers.

This commit is contained in:
holger krekel
2026-05-08 00:08:57 +02:00
parent a69cbfcd26
commit d9e61cb8fd
16 changed files with 78 additions and 79 deletions

View File

@@ -21,9 +21,19 @@ def read_config(inipath):
class Config: class Config:
def __init__(self, inipath, params): def __init__(self, inipath, params):
self._inipath = inipath self._inipath = inipath
self.mail_domain = params["mail_domain"] raw_domain = params["mail_domain"]
self.mail_domain_hostname = format_arpa_address(params["mail_domain"]) self.mail_domain_bare = raw_domain
self.mail_domain_deliverable = format_deliverable_domain(params["mail_domain"])
if is_valid_ipv4(raw_domain):
self.ipv4_relay = raw_domain
self.mail_domain = f"[{raw_domain}]"
self.postfix_myhostname = ipaddress.IPv4Address(raw_domain).reverse_pointer
else:
DomainValidator().validate_domain_re(raw_domain)
self.ipv4_relay = None
self.mail_domain = raw_domain
self.postfix_myhostname = raw_domain
self.max_user_send_per_minute = int(params.get("max_user_send_per_minute", 60)) self.max_user_send_per_minute = int(params.get("max_user_send_per_minute", 60))
self.max_user_send_burst_size = int(params.get("max_user_send_burst_size", 10)) self.max_user_send_burst_size = int(params.get("max_user_send_burst_size", 10))
self.max_mailbox_size = params["max_mailbox_size"] self.max_mailbox_size = params["max_mailbox_size"]
@@ -57,7 +67,7 @@ class Config:
self.imap_rawlog = params.get("imap_rawlog", "false").lower() == "true" self.imap_rawlog = params.get("imap_rawlog", "false").lower() == "true"
self.imap_compress = params.get("imap_compress", "false").lower() == "true" self.imap_compress = params.get("imap_compress", "false").lower() == "true"
if "iroh_relay" not in params: if "iroh_relay" not in params:
self.iroh_relay = "https://" + params["mail_domain"] self.iroh_relay = "https://" + raw_domain
self.enable_iroh_relay = True self.enable_iroh_relay = True
else: else:
self.iroh_relay = params["iroh_relay"].strip() self.iroh_relay = params["iroh_relay"].strip()
@@ -83,19 +93,17 @@ class Config:
) )
self.tls_cert_mode = "external" self.tls_cert_mode = "external"
self.tls_cert_path, self.tls_key_path = parts self.tls_cert_path, self.tls_key_path = parts
elif self.mail_domain.startswith("_") or is_valid_ipv4(params["mail_domain"]): elif raw_domain.startswith("_") or self.ipv4_relay:
self.tls_cert_mode = "self" self.tls_cert_mode = "self"
self.tls_cert_path = "/etc/ssl/certs/mailserver.pem" self.tls_cert_path = "/etc/ssl/certs/mailserver.pem"
self.tls_key_path = "/etc/ssl/private/mailserver.key" self.tls_key_path = "/etc/ssl/private/mailserver.key"
else: else:
self.tls_cert_mode = "acme" self.tls_cert_mode = "acme"
self.tls_cert_path = f"/var/lib/acme/live/{self.mail_domain}/fullchain" self.tls_cert_path = f"/var/lib/acme/live/{raw_domain}/fullchain"
self.tls_key_path = f"/var/lib/acme/live/{self.mail_domain}/privkey" self.tls_key_path = f"/var/lib/acme/live/{raw_domain}/privkey"
# deprecated option # deprecated option
mbdir = params.get( mbdir = params.get("mailboxes_dir", f"/home/vmail/mail/{raw_domain}")
"mailboxes_dir", f"/home/vmail/mail/{self.mail_domain_deliverable}"
)
self.mailboxes_dir = Path(mbdir.strip()) self.mailboxes_dir = Path(mbdir.strip())
# old unused option (except for first migration from sqlite to maildir store) # old unused option (except for first migration from sqlite to maildir store)
@@ -192,6 +200,7 @@ def is_valid_ipv4(address: str) -> bool:
return False return False
def format_arpa_address(address: str) -> str: def format_arpa_address(address: str) -> str:
if is_valid_ipv4(address): if is_valid_ipv4(address):
return ipaddress.IPv4Address(address).reverse_pointer return ipaddress.IPv4Address(address).reverse_pointer
@@ -199,8 +208,8 @@ def format_arpa_address(address: str) -> str:
return address return address
def format_deliverable_domain(mail_domain: str) -> str: def format_mail_domain(raw_domain: str) -> str:
if is_valid_ipv4(mail_domain): if is_valid_ipv4(raw_domain):
return f"[{mail_domain}]" return f"[{raw_domain}]"
DomainValidator().validate_domain_re(mail_domain) DomainValidator().validate_domain_re(raw_domain)
return mail_domain return raw_domain

View File

@@ -108,7 +108,7 @@ class AuthDictProxy(DictProxy):
if namespace == "shared": if namespace == "shared":
if type == "userdb": if type == "userdb":
user = args[0] user = args[0]
if user.endswith(f"@{config.mail_domain_deliverable}"): if user.endswith(f"@{config.mail_domain}"):
res = self.lookup_userdb(user) res = self.lookup_userdb(user)
if res: if res:
reply_command = "O" reply_command = "O"
@@ -116,7 +116,7 @@ class AuthDictProxy(DictProxy):
reply_command = "N" reply_command = "N"
elif type == "passdb": elif type == "passdb":
user = args[1] user = args[1]
if user.endswith(f"@{config.mail_domain_deliverable}"): if user.endswith(f"@{config.mail_domain}"):
res = self.lookup_passdb(user, cleartext_password=args[0]) res = self.lookup_passdb(user, cleartext_password=args[0])
if res: if res:
reply_command = "O" reply_command = "O"

View File

@@ -22,9 +22,7 @@ def create_newemail_dict(config: Config):
secrets.choice(ALPHANUMERIC_PUNCT) secrets.choice(ALPHANUMERIC_PUNCT)
for _ in range(config.password_min_length + 3) for _ in range(config.password_min_length + 3)
) )
return dict( return dict(email=f"{user}@{config.mail_domain}", password=f"{password}")
email=f"{user}@{config.mail_domain_deliverable}", password=f"{password}"
)
def create_dclogin_url(config, email, password): def create_dclogin_url(config, email, password):
@@ -33,9 +31,9 @@ def create_dclogin_url(config, email, password):
Uses ic=3 (AcceptInvalidCertificates) so chatmail clients Uses ic=3 (AcceptInvalidCertificates) so chatmail clients
can connect to servers with self-signed TLS certificates. can connect to servers with self-signed TLS certificates.
""" """
if config.mail_domain != config.mail_domain_deliverable: if config.ipv4_relay:
imap_host = "&ih=" + config.mail_domain imap_host = "&ih=" + config.ipv4_relay
smtp_host = "&sh=" + config.mail_domain smtp_host = "&sh=" + config.ipv4_relay
else: else:
imap_host = "" imap_host = ""
smtp_host = "" smtp_host = ""

View File

@@ -4,7 +4,7 @@ import pytest
from chatmaild.config import ( from chatmaild.config import (
format_arpa_address, format_arpa_address,
format_deliverable_domain, format_mail_domain,
is_valid_ipv4, is_valid_ipv4,
parse_size_mb, parse_size_mb,
read_config, read_config,
@@ -21,12 +21,12 @@ def test_read_config_basic(example_config):
example_config = read_config(inipath) example_config = read_config(inipath)
assert example_config.max_user_send_per_minute == 37 assert example_config.max_user_send_per_minute == 37
assert example_config.mail_domain == "chat.example.org" assert example_config.mail_domain == "chat.example.org"
assert example_config.mail_domain_deliverable == "chat.example.org" assert example_config.ipv4_relay is None
def test_read_config_deliverable(ipv4_config): def test_read_config_ipv4(ipv4_config):
assert ipv4_config.mail_domain == "1.3.3.7" assert ipv4_config.ipv4_relay == "1.3.3.7"
assert ipv4_config.mail_domain_deliverable == "[1.3.3.7]" assert ipv4_config.mail_domain == "[1.3.3.7]"
def test_read_config_basic_using_defaults(tmp_path, maildomain): def test_read_config_basic_using_defaults(tmp_path, maildomain):
@@ -188,6 +188,6 @@ def test_format_arpa_address(input, result, exception):
("12394142", None, pytest.raises(ValueError)), ("12394142", None, pytest.raises(ValueError)),
], ],
) )
def test_format_deliverable_domain(input, result, exception): def test_format_mail_domain(input, result, exception):
with exception: with exception:
assert result == format_deliverable_domain(input) assert result == format_mail_domain(input)

View File

@@ -44,20 +44,12 @@ def test_invalid_username_length(example_config):
config.username_min_length = 6 config.username_min_length = 6
config.username_max_length = 10 config.username_max_length = 10
password = create_newemail_dict(config)["password"] password = create_newemail_dict(config)["password"]
assert not is_allowed_to_create(config, f"a1234@{config.mail_domain}", password)
assert is_allowed_to_create(config, f"012345@{config.mail_domain}", password)
assert is_allowed_to_create(config, f"0123456@{config.mail_domain}", password)
assert is_allowed_to_create(config, f"0123456789@{config.mail_domain}", password)
assert not is_allowed_to_create( assert not is_allowed_to_create(
config, f"a1234@{config.mail_domain_deliverable}", password config, f"0123456789x@{config.mail_domain}", password
)
assert is_allowed_to_create(
config, f"012345@{config.mail_domain_deliverable}", password
)
assert is_allowed_to_create(
config, f"0123456@{config.mail_domain_deliverable}", password
)
assert is_allowed_to_create(
config, f"0123456789@{config.mail_domain_deliverable}", password
)
assert not is_allowed_to_create(
config, f"0123456789x@{config.mail_domain_deliverable}", password
) )
@@ -132,7 +124,7 @@ def test_invalid_localpart_characters(make_config):
"""Test that is_allowed_to_create rejects localparts with invalid characters.""" """Test that is_allowed_to_create rejects localparts with invalid characters."""
config = make_config("chat.example.org", {"username_min_length": "3"}) config = make_config("chat.example.org", {"username_min_length": "3"})
password = "zequ0Aimuchoodaechik" password = "zequ0Aimuchoodaechik"
domain = config.mail_domain_deliverable domain = config.mail_domain
# valid localparts # valid localparts
assert is_allowed_to_create(config, f"abc123@{domain}", password) assert is_allowed_to_create(config, f"abc123@{domain}", password)

View File

@@ -74,7 +74,7 @@ def test_one_mail(
print(line.decode("ascii"), file=sys.stderr) print(line.decode("ascii"), file=sys.stderr)
pytest.fail("starting filtermail failed") pytest.fail("starting filtermail failed")
addr = f"user1@{config.mail_domain_deliverable}" addr = f"user1@{config.mail_domain}"
config.get_user(addr).set_password("l1k2j3l1k2j3l") config.get_user(addr).set_password("l1k2j3l1k2j3l")
# send encrypted mail # send encrypted mail

View File

@@ -56,7 +56,7 @@ def test_print_new_account(capsys, monkeypatch, maildomain, tmpdir, example_conf
assert lines[0] == "Content-Type: application/json" assert lines[0] == "Content-Type: application/json"
assert not lines[1] assert not lines[1]
dic = json.loads(lines[2]) dic = json.loads(lines[2])
assert dic["email"].endswith(f"@{example_config.mail_domain_deliverable}") assert dic["email"].endswith(f"@{example_config.mail_domain}")
assert len(dic["password"]) >= 10 assert len(dic["password"]) >= 10
# default tls_cert=acme should not include dclogin_url # default tls_cert=acme should not include dclogin_url
assert "dclogin_url" not in dic assert "dclogin_url" not in dic

View File

@@ -13,7 +13,7 @@ import sys
from pathlib import Path from pathlib import Path
import pyinfra import pyinfra
from chatmaild.config import read_config, write_initial_config, is_valid_ipv4 from chatmaild.config import read_config, write_initial_config
from packaging import version from packaging import version
from termcolor import colored from termcolor import colored
@@ -87,11 +87,11 @@ def run_cmd_options(parser):
def run_cmd(args, out): def run_cmd(args, out):
"""Deploy chatmail services on the remote server.""" """Deploy chatmail services on the remote server."""
ssh_host = args.ssh_host if args.ssh_host else args.config.mail_domain ssh_host = args.ssh_host if args.ssh_host else args.config.mail_domain_bare
sshexec = get_sshexec(ssh_host) sshexec = get_sshexec(ssh_host)
require_iroh = args.config.enable_iroh_relay require_iroh = args.config.enable_iroh_relay
strict_tls = args.config.tls_cert_mode == "acme" strict_tls = args.config.tls_cert_mode == "acme"
if is_valid_ipv4(args.config.mail_domain): if args.config.ipv4_relay:
args.dns_check_disabled = True args.dns_check_disabled = True
if not args.dns_check_disabled: if not args.dns_check_disabled:
remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain) remote_data = dns.get_initial_remote_data(sshexec, args.config.mail_domain)
@@ -121,7 +121,7 @@ def run_cmd(args, out):
elif not args.dns_check_disabled and strict_tls and not remote_data["acme_account_url"]: elif not args.dns_check_disabled and strict_tls and not remote_data["acme_account_url"]:
out.red("Deploy completed but letsencrypt not configured") out.red("Deploy completed but letsencrypt not configured")
out.red("Run 'cmdeploy run' again") out.red("Run 'cmdeploy run' again")
elif is_valid_ipv4(args.config.mail_domain): elif args.config.ipv4_relay:
out.green("Deploy completed.") out.green("Deploy completed.")
else: else:
out.green("Deploy completed, call `cmdeploy dns` next.") out.green("Deploy completed, call `cmdeploy dns` next.")
@@ -144,8 +144,9 @@ def dns_cmd_options(parser):
def dns_cmd(args, out): def dns_cmd(args, out):
"""Check DNS entries and optionally generate dns zone file.""" """Check DNS entries and optionally generate dns zone file."""
if is_valid_ipv4(args.config.mail_domain): if args.config.ipv4_relay:
print(f"[WARNING] {args.config.mail_domain} is not a domain, skipping DNS checks.") ipv4 = args.config.ipv4_relay
print(f"[WARNING] {ipv4} is not a domain, skipping DNS checks.")
return 0 return 0
ssh_host = args.ssh_host if args.ssh_host else args.config.mail_domain ssh_host = args.ssh_host if args.ssh_host else args.config.mail_domain
sshexec = get_sshexec(ssh_host, verbose=args.verbose) sshexec = get_sshexec(ssh_host, verbose=args.verbose)
@@ -184,7 +185,7 @@ def status_cmd_options(parser):
def status_cmd(args, out): def status_cmd(args, out):
"""Display status for online chatmail instance.""" """Display status for online chatmail instance."""
ssh_host = args.ssh_host if args.ssh_host else args.config.mail_domain ssh_host = args.ssh_host if args.ssh_host else args.config.mail_domain_bare
sshexec = get_sshexec(ssh_host, verbose=args.verbose) sshexec = get_sshexec(ssh_host, verbose=args.verbose)
out.green(f"chatmail domain: {args.config.mail_domain}") out.green(f"chatmail domain: {args.config.mail_domain}")

View File

@@ -468,7 +468,7 @@ class ChatmailVenvDeployer(Deployer):
def configure(self): def configure(self):
_configure_remote_venv_with_chatmaild(self.config) _configure_remote_venv_with_chatmaild(self.config)
configure_remote_units(self.config.mail_domain, self.units) configure_remote_units(self.config.mail_domain_bare, self.units)
def activate(self): def activate(self):
activate_remote_units(self.units) activate_remote_units(self.units)
@@ -482,7 +482,7 @@ class ChatmailDeployer(Deployer):
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.mail_domain_deliverable = config.mail_domain_deliverable self.mail_domain = config.mail_domain
def install(self): def install(self):
files.put( files.put(
@@ -522,7 +522,7 @@ class ChatmailDeployer(Deployer):
server.shell( server.shell(
name="Setup /etc/mailname", name="Setup /etc/mailname",
commands=[ commands=[
f"echo {self.mail_domain_deliverable} >/etc/mailname; chmod 644 /etc/mailname" f"echo {self.mail_domain} >/etc/mailname; chmod 644 /etc/mailname"
], ],
) )
@@ -584,8 +584,7 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
""" """
config = read_config(config_path) config = read_config(config_path)
check_config(config) check_config(config)
mail_domain = config.mail_domain bare_host = config.mail_domain_bare
mail_domain_deliverable = config.mail_domain_deliverable
if website_only: if website_only:
Deployment().perform_stages([WebsiteDeployer(config)]) Deployment().perform_stages([WebsiteDeployer(config)])
@@ -636,7 +635,7 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
) )
exit(1) exit(1)
tls_deployer = get_tls_deployer(config, mail_domain) tls_deployer = get_tls_deployer(config, bare_host)
all_deployers = [ all_deployers = [
ChatmailDeployer(config), ChatmailDeployer(config),
@@ -644,13 +643,13 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
FiltermailDeployer(), FiltermailDeployer(),
JournaldDeployer(), JournaldDeployer(),
UnboundDeployer(config), UnboundDeployer(config),
TurnDeployer(mail_domain), TurnDeployer(bare_host),
IrohDeployer(config.enable_iroh_relay), IrohDeployer(config.enable_iroh_relay),
tls_deployer, tls_deployer,
WebsiteDeployer(config), WebsiteDeployer(config),
ChatmailVenvDeployer(config), ChatmailVenvDeployer(config),
MtastsDeployer(), MtastsDeployer(),
OpendkimDeployer(mail_domain_deliverable), OpendkimDeployer(config.mail_domain),
# Dovecot should be started before Postfix # Dovecot should be started before Postfix
# because it creates authentication socket # because it creates authentication socket
# required by Postfix. # required by Postfix.

View File

@@ -73,7 +73,7 @@ class DovecotDeployer(Deployer):
) )
def configure(self): def configure(self):
configure_remote_units(self.config.mail_domain, self.units) configure_remote_units(self.config.mail_domain_bare, self.units)
config_restart, self.daemon_reload = _configure_dovecot(self.config) config_restart, self.daemon_reload = _configure_dovecot(self.config)
self.need_restart |= config_restart self.need_restart |= config_restart

View File

@@ -40,7 +40,7 @@ service imap {
process_limit = 50000 process_limit = 50000
} }
mail_server_admin = mailto:root@{{ config.mail_domain_deliverable }} mail_server_admin = mailto:root@{{ config.mail_domain }}
mail_server_comment = Chatmail server mail_server_comment = Chatmail server
# `zlib` enables compressing messages stored in the maildir. # `zlib` enables compressing messages stored in the maildir.

View File

@@ -12,8 +12,8 @@ from cmdeploy.basedeploy import Deployer, get_resource
class OpendkimDeployer(Deployer): class OpendkimDeployer(Deployer):
required_users = [("opendkim", None, ["opendkim"])] required_users = [("opendkim", None, ["opendkim"])]
def __init__(self, mail_domain_deliverable): def __init__(self, mail_domain):
self.mail_domain_deliverable = mail_domain_deliverable self.mail_domain = mail_domain
def install(self): def install(self):
apt.packages( apt.packages(
@@ -22,7 +22,7 @@ class OpendkimDeployer(Deployer):
) )
def configure(self): def configure(self):
domain = self.mail_domain_deliverable domain = self.mail_domain
dkim_selector = "opendkim" dkim_selector = "opendkim"
"""Configures OpenDKIM""" """Configures OpenDKIM"""
need_restart = False need_restart = False

View File

@@ -1,4 +1,4 @@
myorigin = {{ config.mail_domain_deliverable }} myorigin = {{ config.mail_domain }}
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no biff = no
@@ -54,13 +54,13 @@ smtpd_tls_exclude_ciphers = aNULL, RC4, MD5, DES
tls_preempt_cipherlist = yes tls_preempt_cipherlist = yes
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = {{ config.mail_domain_hostname }} myhostname = {{ config.postfix_myhostname }}
alias_maps = hash:/etc/aliases alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases alias_database = hash:/etc/aliases
# When postfix receives mail for $mydestination, # When postfix receives mail for $mydestination,
# it hands it over to dovecot via $local_transport. # it hands it over to dovecot via $local_transport.
mydestination = {{ config.mail_domain_deliverable }} mydestination = {{ config.mail_domain }}
local_transport = lmtp:unix:private/dovecot-lmtp local_transport = lmtp:unix:private/dovecot-lmtp
# postfix doesn't check whether local users exist or not: # postfix doesn't check whether local users exist or not:
local_recipient_maps = local_recipient_maps =

View File

@@ -81,7 +81,7 @@ filter unix - n n - - lmtp
-o syslog_name=postfix/reinject -o syslog_name=postfix/reinject
-o milter_macro_daemon_name=ORIGINATING -o milter_macro_daemon_name=ORIGINATING
-o cleanup_service_name=authclean -o cleanup_service_name=authclean
{% if config.mail_domain == config.mail_domain_deliverable %} -o smtpd_milters=unix:opendkim/opendkim.sock {% if not config.ipv4_relay %} -o smtpd_milters=unix:opendkim/opendkim.sock
{% endif %} {% endif %}
# Local SMTP server for reinjecting incoming filtered mail # Local SMTP server for reinjecting incoming filtered mail

View File

@@ -89,14 +89,14 @@ def test_concurrent_logins_same_account(
assert login_results.get() assert login_results.get()
def test_no_vrfy(cmfactory, chatmail_config): def test_no_vrfy(cmfactory, chatmail_config, maildomain):
ac = cmfactory.get_online_account() ac = cmfactory.get_online_account()
addr = ac.get_config("addr") addr = ac.get_config("addr")
s = smtplib.SMTP(chatmail_config.mail_domain) s = smtplib.SMTP(maildomain)
s.starttls() s.starttls()
s.putcmd("vrfy", f"wrongaddress@{chatmail_config.mail_domain_deliverable}") s.putcmd("vrfy", f"wrongaddress@{chatmail_config.mail_domain}")
result = s.getreply() result = s.getreply()
print(result) print(result)
s.putcmd("vrfy", addr) s.putcmd("vrfy", addr)

View File

@@ -10,7 +10,7 @@ import time
from pathlib import Path from pathlib import Path
import pytest import pytest
from chatmaild.config import read_config, format_deliverable_domain, is_valid_ipv4 from chatmaild.config import read_config, format_mail_domain, is_valid_ipv4
conftestdir = Path(__file__).parent conftestdir = Path(__file__).parent
@@ -59,12 +59,12 @@ def chatmail_config(pytestconfig):
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def maildomain(chatmail_config): def maildomain(chatmail_config):
return chatmail_config.mail_domain return chatmail_config.mail_domain_bare
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def maildomain_deliverable(maildomain): def maildomain_deliverable(maildomain):
return format_deliverable_domain(maildomain) return format_mail_domain(maildomain)
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
@@ -283,7 +283,7 @@ def gencreds(chatmail_config):
next(count) next(count)
def gen(domain=None): def gen(domain=None):
domain = domain if domain else chatmail_config.mail_domain_deliverable domain = domain if domain else chatmail_config.mail_domain
while 1: while 1:
num = next(count) num = next(count)
alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890" alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890"
@@ -322,7 +322,7 @@ class ChatmailACFactory:
def _make_transport(self, domain): def _make_transport(self, domain):
"""Build a transport config dict for the given domain.""" """Build a transport config dict for the given domain."""
domain_deliverable = format_deliverable_domain(domain) domain_deliverable = format_mail_domain(domain)
addr, password = self.gencreds(domain_deliverable) addr, password = self.gencreds(domain_deliverable)
transport = { transport = {
"addr": addr, "addr": addr,
@@ -347,7 +347,7 @@ class ChatmailACFactory:
accounts = [] accounts = []
for _ in range(num): for _ in range(num):
account = self.dc.add_account() account = self.dc.add_account()
domain_deliverable = format_deliverable_domain(domain) domain_deliverable = format_mail_domain(domain)
addr, password = self.gencreds(domain_deliverable) addr, password = self.gencreds(domain_deliverable)
if _is_ip(domain): if _is_ip(domain):
# Use DCLOGIN scheme with explicit server hosts, # Use DCLOGIN scheme with explicit server hosts,