treewide: reformat python code

This commit is contained in:
Martin Weinelt 2025-05-08 01:40:37 +02:00
parent f9fcbe9430
commit a7d580b934
No known key found for this signature in database
GPG Key ID: 87C1E9888F856759
3 changed files with 185 additions and 129 deletions

View File

@ -17,9 +17,9 @@
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = 'NixOS Mailserver' project = "NixOS Mailserver"
copyright = '2022, NixOS Mailserver Contributors' copyright = "2022, NixOS Mailserver Contributors"
author = 'NixOS Mailserver Contributors' author = "NixOS Mailserver Contributors"
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
@ -27,33 +27,31 @@ author = 'NixOS Mailserver Contributors'
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = ["myst_parser"]
'myst_parser'
]
myst_enable_extensions = [ myst_enable_extensions = [
'colon_fence', "colon_fence",
'linkify', "linkify",
] ]
smartquotes = False smartquotes = False
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path. # This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
master_doc = 'index' master_doc = "index"
# -- Options for HTML output ------------------------------------------------- # -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
# #
html_theme = 'sphinx_rtd_theme' html_theme = "sphinx_rtd_theme"
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,

View File

@ -21,64 +21,71 @@ template = """
f = open(sys.argv[1]) f = open(sys.argv[1])
options = json.load(f) options = json.load(f)
groups = ["mailserver.loginAccounts", groups = [
"mailserver.certificate", "mailserver.loginAccounts",
"mailserver.dkim", "mailserver.certificate",
"mailserver.dmarcReporting", "mailserver.dkim",
"mailserver.fullTextSearch", "mailserver.dmarcReporting",
"mailserver.redis", "mailserver.fullTextSearch",
"mailserver.ldap", "mailserver.redis",
"mailserver.monitoring", "mailserver.ldap",
"mailserver.backup", "mailserver.monitoring",
"mailserver.borgbackup"] "mailserver.backup",
"mailserver.borgbackup",
]
def render_option_value(opt, attr): def render_option_value(opt, attr):
if attr not in opt: if attr not in opt:
return "" return ""
if isinstance(opt[attr], dict) and '_type' in opt[attr]: if isinstance(opt[attr], dict) and "_type" in opt[attr]:
if opt[attr]['_type'] == 'literalExpression': if opt[attr]["_type"] == "literalExpression":
if '\n' in opt[attr]['text']: if "\n" in opt[attr]["text"]:
res = '\n```nix\n' + opt[attr]['text'].rstrip('\n') + '\n```' res = "\n```nix\n" + opt[attr]["text"].rstrip("\n") + "\n```"
else: else:
res = '```{}```'.format(opt[attr]['text']) res = "```{}```".format(opt[attr]["text"])
elif opt[attr]['_type'] == 'literalMD': elif opt[attr]["_type"] == "literalMD":
res = opt[attr]['text'] res = opt[attr]["text"]
else: else:
assert RuntimeError(f"Unhandled option type {opt[attr]["_type"]}") assert RuntimeError(f"Unhandled option type {opt[attr]["_type"]}")
else: else:
s = str(opt[attr]) s = str(opt[attr])
if s == "": if s == "":
res = '`""`' res = '`""`'
elif '\n' in s: elif "\n" in s:
res = '\n```\n' + s.rstrip('\n') + '\n```' res = "\n```\n" + s.rstrip("\n") + "\n```"
else: else:
res = '```{}```'.format(s) res = "```{}```".format(s)
return "- " + attr + ": " + res # type: ignore
return '- ' + attr + ': ' + res # type: ignore
def print_option(opt): def print_option(opt):
if isinstance(opt['description'], dict) and '_type' in opt['description']: # mdDoc if isinstance(opt["description"], dict) and "_type" in opt["description"]: # mdDoc
description = opt['description']['text'] description = opt["description"]["text"]
else: else:
description = opt['description'] description = opt["description"]
print(template.format( print(
key=opt['name'], template.format(
description=description or "", key=opt["name"],
type="- type: ```{}```".format(opt['type']), description=description or "",
default=render_option_value(opt, 'default'), type="- type: ```{}```".format(opt["type"]),
example=render_option_value(opt, 'example'))) default=render_option_value(opt, "default"),
example=render_option_value(opt, "example"),
)
)
print(header) print(header)
for opt in options: for opt in options:
if any([opt['name'].startswith(c) for c in groups]): if any([opt["name"].startswith(c) for c in groups]):
continue continue
print_option(opt) print_option(opt)
for c in groups: for c in groups:
print('## `{}`'.format(c)) print("## `{}`".format(c))
print() print()
for opt in options: for opt in options:
if opt['name'].startswith(c): if opt["name"].startswith(c):
print_option(opt) print_option(opt)

View File

@ -1,33 +1,37 @@
import smtplib, sys
import argparse import argparse
import os
import uuid
import imaplib
from datetime import datetime, timedelta
import email import email
import email.utils import email.utils
import imaplib
import smtplib
import time import time
import uuid
from datetime import datetime, timedelta
from typing import cast from typing import cast
RETRY = 100 RETRY = 100
def _send_mail(smtp_host, smtp_port, smtp_username, from_addr, from_pwd, to_addr, subject, starttls):
print("Sending mail with subject '{}'".format(subject))
message = "\n".join([
"From: {from_addr}",
"To: {to_addr}",
"Subject: {subject}",
"Message-ID: {random}@mail-check.py",
"Date: {date}",
"",
"This validates our mail server can send to Gmail :/"]).format(
from_addr=from_addr,
to_addr=to_addr,
subject=subject,
random=str(uuid.uuid4()),
date=email.utils.formatdate(),
)
def _send_mail(
smtp_host, smtp_port, smtp_username, from_addr, from_pwd, to_addr, subject, starttls
):
print("Sending mail with subject '{}'".format(subject))
message = "\n".join(
[
"From: {from_addr}",
"To: {to_addr}",
"Subject: {subject}",
"Message-ID: {random}@mail-check.py",
"Date: {date}",
"",
"This validates our mail server can send to Gmail :/",
]
).format(
from_addr=from_addr,
to_addr=to_addr,
subject=subject,
random=str(uuid.uuid4()),
date=email.utils.formatdate(),
)
retry = RETRY retry = RETRY
while True: while True:
@ -44,7 +48,9 @@ def _send_mail(smtp_host, smtp_port, smtp_username, from_addr, from_pwd, to_addr
except smtplib.SMTPResponseException as e: except smtplib.SMTPResponseException as e:
if e.smtp_code == 451: # service unavailable error if e.smtp_code == 451: # service unavailable error
print(e) print(e)
elif e.smtp_code == 454: # smtplib.SMTPResponseException: (454, b'4.3.0 Try again later') elif (
e.smtp_code == 454
): # smtplib.SMTPResponseException: (454, b'4.3.0 Try again later')
print(e) print(e)
else: else:
raise raise
@ -62,15 +68,17 @@ def _send_mail(smtp_host, smtp_port, smtp_username, from_addr, from_pwd, to_addr
print("Retry attempts exhausted") print("Retry attempts exhausted")
exit(5) exit(5)
def _read_mail( def _read_mail(
imap_host, imap_host,
imap_port, imap_port,
imap_username, imap_username,
to_pwd, to_pwd,
subject, subject,
ignore_dkim_spf, ignore_dkim_spf,
show_body=False, show_body=False,
delete=True): delete=True,
):
print("Reading mail from %s" % imap_username) print("Reading mail from %s" % imap_username)
message = None message = None
@ -81,28 +89,31 @@ def _read_mail(
today = datetime.today() today = datetime.today()
cutoff = today - timedelta(days=1) cutoff = today - timedelta(days=1)
dt = cutoff.strftime('%d-%b-%Y') dt = cutoff.strftime("%d-%b-%Y")
for _ in range(0, RETRY): for _ in range(0, RETRY):
print("Retrying") print("Retrying")
obj.select() obj.select()
_, data = obj.search(None, '(SINCE %s) (SUBJECT "%s")' % (dt, subject)) _, data = obj.search(None, '(SINCE %s) (SUBJECT "%s")' % (dt, subject))
if data == [b'']: if data == [b""]:
time.sleep(1) time.sleep(1)
continue continue
uids = data[0].decode("utf-8").split(" ") uids = data[0].decode("utf-8").split(" ")
if len(uids) != 1: if len(uids) != 1:
print("Warning: %d messages have been found with subject containing %s " % (len(uids), subject)) print(
"Warning: %d messages have been found with subject containing %s "
% (len(uids), subject)
)
# FIXME: we only consider the first matching message... # FIXME: we only consider the first matching message...
uid = uids[0] uid = uids[0]
_, raw = obj.fetch(uid, '(RFC822)') _, raw = obj.fetch(uid, "(RFC822)")
if delete: if delete:
obj.store(uid, '+FLAGS', '\\Deleted') obj.store(uid, "+FLAGS", "\\Deleted")
obj.expunge() obj.expunge()
assert raw[0] and raw[0][1] assert raw[0] and raw[0][1]
message = email.message_from_bytes(cast(bytes, raw[0][1])) message = email.message_from_bytes(cast(bytes, raw[0][1]))
print("Message with subject '%s' has been found" % message['subject']) print("Message with subject '%s' has been found" % message["subject"])
if show_body: if show_body:
if message.is_multipart(): if message.is_multipart():
for part in message.walk(): for part in message.walk():
@ -118,21 +129,24 @@ def _read_mail(
break break
if message is None: if message is None:
print("Error: no message with subject '%s' has been found in INBOX of %s" % (subject, imap_username)) print(
"Error: no message with subject '%s' has been found in INBOX of %s"
% (subject, imap_username)
)
exit(1) exit(1)
if ignore_dkim_spf: if ignore_dkim_spf:
return return
# gmail set this standardized header # gmail set this standardized header
if 'ARC-Authentication-Results' in message: if "ARC-Authentication-Results" in message:
if "dkim=pass" in message['ARC-Authentication-Results']: if "dkim=pass" in message["ARC-Authentication-Results"]:
print("DKIM ok") print("DKIM ok")
else: else:
print("Error: no DKIM validation found in message:") print("Error: no DKIM validation found in message:")
print(message.as_string()) print(message.as_string())
exit(2) exit(2)
if "spf=pass" in message['ARC-Authentication-Results']: if "spf=pass" in message["ARC-Authentication-Results"]:
print("SPF ok") print("SPF ok")
else: else:
print("Error: no SPF validation found in message:") print("Error: no SPF validation found in message:")
@ -142,71 +156,108 @@ def _read_mail(
print("DKIM and SPF verification failed") print("DKIM and SPF verification failed")
exit(4) exit(4)
def send_and_read(args): def send_and_read(args):
src_pwd = None src_pwd = None
if args.src_password_file is not None: if args.src_password_file is not None:
src_pwd = args.src_password_file.readline().rstrip() src_pwd = args.src_password_file.readline().rstrip()
dst_pwd = args.dst_password_file.readline().rstrip() dst_pwd = args.dst_password_file.readline().rstrip()
if args.imap_username != '': if args.imap_username != "":
imap_username = args.imap_username imap_username = args.imap_username
else: else:
imap_username = args.to_addr imap_username = args.to_addr
subject = "{}".format(uuid.uuid4()) subject = "{}".format(uuid.uuid4())
_send_mail(smtp_host=args.smtp_host, _send_mail(
smtp_port=args.smtp_port, smtp_host=args.smtp_host,
smtp_username=args.smtp_username, smtp_port=args.smtp_port,
from_addr=args.from_addr, smtp_username=args.smtp_username,
from_pwd=src_pwd, from_addr=args.from_addr,
to_addr=args.to_addr, from_pwd=src_pwd,
subject=subject, to_addr=args.to_addr,
starttls=args.smtp_starttls) subject=subject,
starttls=args.smtp_starttls,
)
_read_mail(
imap_host=args.imap_host,
imap_port=args.imap_port,
imap_username=imap_username,
to_pwd=dst_pwd,
subject=subject,
ignore_dkim_spf=args.ignore_dkim_spf,
)
_read_mail(imap_host=args.imap_host,
imap_port=args.imap_port,
imap_username=imap_username,
to_pwd=dst_pwd,
subject=subject,
ignore_dkim_spf=args.ignore_dkim_spf)
def read(args): def read(args):
_read_mail(imap_host=args.imap_host, _read_mail(
imap_port=args.imap_port, imap_host=args.imap_host,
imap_username=args.imap_username, imap_port=args.imap_port,
to_pwd=args.imap_password, imap_username=args.imap_username,
subject=args.subject, to_pwd=args.imap_password,
ignore_dkim_spf=args.ignore_dkim_spf, subject=args.subject,
show_body=args.show_body, ignore_dkim_spf=args.ignore_dkim_spf,
delete=False) show_body=args.show_body,
delete=False,
)
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers() subparsers = parser.add_subparsers()
parser_send_and_read = subparsers.add_parser('send-and-read', description="Send a email with a subject containing a random UUID and then try to read this email from the recipient INBOX.") parser_send_and_read = subparsers.add_parser(
parser_send_and_read.add_argument('--smtp-host', type=str) "send-and-read",
parser_send_and_read.add_argument('--smtp-port', type=str, default=25) description="Send a email with a subject containing a random UUID and then try to read this email from the recipient INBOX.",
parser_send_and_read.add_argument('--smtp-starttls', action='store_true') )
parser_send_and_read.add_argument('--smtp-username', type=str, default='', help="username used for smtp login. If not specified, the from-addr value is used") parser_send_and_read.add_argument("--smtp-host", type=str)
parser_send_and_read.add_argument('--from-addr', type=str) parser_send_and_read.add_argument("--smtp-port", type=str, default=25)
parser_send_and_read.add_argument('--imap-host', required=True, type=str) parser_send_and_read.add_argument("--smtp-starttls", action="store_true")
parser_send_and_read.add_argument('--imap-port', type=str, default=993) parser_send_and_read.add_argument(
parser_send_and_read.add_argument('--to-addr', type=str, required=True) "--smtp-username",
parser_send_and_read.add_argument('--imap-username', type=str, default='', help="username used for imap login. If not specified, the to-addr value is used") type=str,
parser_send_and_read.add_argument('--src-password-file', type=argparse.FileType('r')) default="",
parser_send_and_read.add_argument('--dst-password-file', required=True, type=argparse.FileType('r')) help="username used for smtp login. If not specified, the from-addr value is used",
parser_send_and_read.add_argument('--ignore-dkim-spf', action='store_true', help="to ignore the dkim and spf verification on the read mail") )
parser_send_and_read.add_argument("--from-addr", type=str)
parser_send_and_read.add_argument("--imap-host", required=True, type=str)
parser_send_and_read.add_argument("--imap-port", type=str, default=993)
parser_send_and_read.add_argument("--to-addr", type=str, required=True)
parser_send_and_read.add_argument(
"--imap-username",
type=str,
default="",
help="username used for imap login. If not specified, the to-addr value is used",
)
parser_send_and_read.add_argument("--src-password-file", type=argparse.FileType("r"))
parser_send_and_read.add_argument(
"--dst-password-file", required=True, type=argparse.FileType("r")
)
parser_send_and_read.add_argument(
"--ignore-dkim-spf",
action="store_true",
help="to ignore the dkim and spf verification on the read mail",
)
parser_send_and_read.set_defaults(func=send_and_read) parser_send_and_read.set_defaults(func=send_and_read)
parser_read = subparsers.add_parser('read', description="Search for an email with a subject containing 'subject' in the INBOX.") parser_read = subparsers.add_parser(
parser_read.add_argument('--imap-host', type=str, default="localhost") "read",
parser_read.add_argument('--imap-port', type=str, default=993) description="Search for an email with a subject containing 'subject' in the INBOX.",
parser_read.add_argument('--imap-username', required=True, type=str) )
parser_read.add_argument('--imap-password', required=True, type=str) parser_read.add_argument("--imap-host", type=str, default="localhost")
parser_read.add_argument('--ignore-dkim-spf', action='store_true', help="to ignore the dkim and spf verification on the read mail") parser_read.add_argument("--imap-port", type=str, default=993)
parser_read.add_argument('--show-body', action='store_true', help="print mail text/plain payload") parser_read.add_argument("--imap-username", required=True, type=str)
parser_read.add_argument('subject', type=str) parser_read.add_argument("--imap-password", required=True, type=str)
parser_read.add_argument(
"--ignore-dkim-spf",
action="store_true",
help="to ignore the dkim and spf verification on the read mail",
)
parser_read.add_argument(
"--show-body", action="store_true", help="print mail text/plain payload"
)
parser_read.add_argument("subject", type=str)
parser_read.set_defaults(func=read) parser_read.set_defaults(func=read)
args = parser.parse_args() args = parser.parse_args()