Skip to content
Closed
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
70 changes: 62 additions & 8 deletions cmping.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import argparse
import ipaddress
import os
import queue
import random
Expand All @@ -23,13 +24,13 @@ def main():
parser.add_argument(
"relay1",
action="store",
help="chatmail relay domain",
help="chatmail relay domain or IP address",
)
parser.add_argument(
"relay2",
action="store",
nargs="?",
help="chatmail relay domain (defaults to relay1 if not specified)",
help="chatmail relay domain or IP address (defaults to relay1 if not specified)",
)
parser.add_argument(
"-c",
Expand Down Expand Up @@ -64,6 +65,15 @@ def main():
raise SystemExit(0 if pinger.received == expected_total else 1)


def _is_ip_address(host):
"""Check if host is an IP address."""
try:
ipaddress.ip_address(host)
return True
except ValueError:
return False


class AccountMaker:
def __init__(self, dc):
self.dc = dc
Expand All @@ -79,16 +89,60 @@ def _add_online(self, account):
account.start_io()
self.online.append(account)

def get_relay_account(self, domain):
def _generate_credentials(self):
"""Generate credentials for IP-based accounts.

Returns:
tuple: (username, password) where username is 12 chars (a-z, 0-9)
and password is 30 chars (A-Z, a-z, 0-9)
"""
username_chars = string.ascii_lowercase + string.digits
password_chars = string.ascii_letters + string.digits
username = "".join(random.choices(username_chars, k=12))
password = "".join(random.choices(password_chars, k=30))
return username, password

def _find_existing_account(self, domain_or_ip):
"""Find existing account for the given domain or IP address."""
for account in self.dc.get_all_accounts():
addr = account.get_config("configured_addr")
if addr is not None and addr.split("@")[1] == domain:
if account not in self.online:
break
if addr is not None:
host_part = addr.split("@")[1] if "@" in addr else None
if host_part == domain_or_ip:
return account
return None

def get_relay_account(self, domain_or_ip):
# Check if we already have an account for this domain or IP
account = self._find_existing_account(domain_or_ip)

if account is not None:
if account not in self.online:
self._add_online(account)
return account

# No existing account found, create a new one
is_ip = _is_ip_address(domain_or_ip)

if is_ip:
# Create new account with dclogin scheme for IP addresses
username, password = self._generate_credentials()
print(f"# creating account on {domain_or_ip} with username {username}")
account = self.dc.add_account()

# Build dclogin URL with IP address
# Format: dclogin:username@ip/?p=password&v=1&ip=993&sp=465&ic=3&ss=default
IMAP_PORT = "993"
SMTP_PORT = "465"
CERT_CHECKS = "3"
SMTP_SECURITY = "default"
qr_url = f"dclogin:{username}@{domain_or_ip}/?p={password}&v=1&ip={IMAP_PORT}&sp={SMTP_PORT}&ic={CERT_CHECKS}&ss={SMTP_SECURITY}"
account.set_config_from_qr(qr_url)
else:
print(f"# creating account on {domain}")
# Create new account with dcaccount scheme for domains
print(f"# creating account on {domain_or_ip}")
account = self.dc.add_account()
account.set_config_from_qr(f"dcaccount:{domain}")
account.set_config_from_qr(f"dcaccount:{domain_or_ip}")

self._add_online(account)
return account
Expand Down