Files
DECNET/decnet/services/smtp_relay.py
anti f2b3393669 chore: relicense to AGPL-3.0-or-later and add SPDX headers
Replaces LICENSE (GPLv3 -> AGPLv3) and prepends
`SPDX-License-Identifier: AGPL-3.0-or-later` to every source file
across decnet/, decnet_web/, tests/, scripts/, and tools/.

Rationale: closes the GPLv3 ASP loophole so any party operating a
modified DECNET as a network service must offer their modified
source. Personal copyright (Samuel Paschuan) + inbound=outbound
contributions make a future unilateral relicense infeasible.

- LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt)
- COPYRIGHT: project copyright notice
- tools/add_spdx_headers.py: idempotent header injector
  (shebang- and PEP 263-aware)

Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh).
No behavior change; comments only.
2026-05-22 21:04:16 -04:00

111 lines
4.0 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
import os
from pathlib import Path
from decnet.services.base import BaseService, ServiceConfigField
# Reuses the same template as the smtp service — only difference is
# SMTP_OPEN_RELAY=1 in the environment, which enables the open relay persona.
_TEMPLATES_DIR = Path(__file__).parent.parent / "templates" / "smtp"
ARTIFACTS_ROOT = os.environ.get("DECNET_ARTIFACTS_ROOT", "/var/lib/decnet/artifacts")
# See decnet/services/smtp.py — benign-looking in-container quarantine path.
_IN_CONTAINER_QUARANTINE = "/var/spool/mqueue"
class SMTPRelayService(BaseService):
"""SMTP open relay bait — accepts any RCPT TO and delivers messages."""
name = "smtp_relay"
ports = [25, 587]
default_image = "build"
config_schema = [
ServiceConfigField(
key="banner",
label="SMTP greeting banner",
type="string",
placeholder="mail.corp.local ESMTP Postfix",
help="First line returned on TCP connect (220 ...).",
),
ServiceConfigField(
key="mta",
label="MTA persona",
type="enum",
enum=["postfix", "exim", "sendmail"],
default="postfix",
help="Shapes EHLO capability list and error wording.",
),
ServiceConfigField(
key="upstream_host",
label="Upstream relay host",
type="string",
placeholder="smtp.sendgrid.net",
help="Real SMTP relay used to forward probe emails. Leave blank to disable forwarding.",
),
ServiceConfigField(
key="upstream_port",
label="Upstream relay port",
type="int",
default=25,
help="Port on the upstream relay (25 or 587).",
),
ServiceConfigField(
key="upstream_user",
label="Upstream relay username",
type="string",
help="AUTH username for the upstream relay (optional).",
),
ServiceConfigField(
key="upstream_pass",
label="Upstream relay password",
type="string",
help="AUTH password for the upstream relay (optional).",
),
ServiceConfigField(
key="upstream_sender",
label="Upstream envelope sender",
type="string",
placeholder="probe@yourdomain.com",
help="Envelope MAIL FROM used when talking to the upstream relay. Set this to an address your server is authorised to send from so SPF passes at the recipient. The attacker's From: header inside the message is untouched.",
),
ServiceConfigField(
key="probe_limit",
label="Probe forward limit",
type="int",
default=1,
help="Number of emails per source IP to actually deliver upstream. All subsequent emails are silently quarantined.",
),
]
def compose_fragment(
self,
decky_name: str,
log_target: str | None = None,
service_cfg: dict | None = None,
) -> dict:
cfg = service_cfg or {}
quarantine_host = f"{ARTIFACTS_ROOT}/{decky_name}/smtp"
fragment: dict = {
"build": {"context": str(_TEMPLATES_DIR)},
"container_name": f"{decky_name}-smtp_relay",
"restart": "unless-stopped",
"cap_add": ["NET_BIND_SERVICE"],
"environment": {
"NODE_NAME": decky_name,
"SMTP_SERVICE_NAME": "smtp_relay",
"SMTP_OPEN_RELAY": "1",
"SMTP_QUARANTINE_DIR": _IN_CONTAINER_QUARANTINE,
},
"volumes": [f"{quarantine_host}:{_IN_CONTAINER_QUARANTINE}:rw"],
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target
if "banner" in cfg:
fragment["environment"]["SMTP_BANNER"] = cfg["banner"]
if "mta" in cfg:
fragment["environment"]["SMTP_MTA"] = cfg["mta"]
return fragment
def dockerfile_context(self) -> Path:
return _TEMPLATES_DIR