Compare commits

...

6 Commits

Author SHA1 Message Date
missytake
296dcad1da echobot: print invite link with python 2025-12-06 19:08:09 +01:00
Alexandre Gauthier
22c303ed7c Fix issues with echobot invite link in cmdeploy
The entire stack is setup to support 'localhost'  as a value for
ssh_host, returning LocalExec when that is the case, but this last step
made a new explicit SSHExec connection to config.mail_domain.

This changes that to simply use the subprocess module when ssh_host is
local.

This also fixes the issue where the connection was made to
'config.mail_domain' instead of the supplied ssh_host value, ensuring
that remains consistent.

Additionally, the entire process will be skipped if --dry-run is used
with cmdeploy, allowing a dry-run to complete without error.
2025-12-06 19:08:09 +01:00
j4n
166bf68915 Remove DKIM-Signature from incoming mail after checking (#747)
The original https://github.com/chatmail/relay/pull/533 attempted to remove the header through postfix, but that is too early. Instead, remove the headers in the OpenDKIM `final.lua` script after the validation.
2025-12-04 12:23:27 +01:00
Maikel Frias Mosquea
96108bbaba fix: cmdeploy webdev now works as intended
Before: cmdeploy webdev just kept running non-stop regeneration of the
files with this it truly stop unless there's an actual change.
2025-11-25 22:26:47 +01:00
Mark Felder
8f68672e31 FreeBSD/pf example: fix small inconsistency
harmless, but better to be consistent
2025-11-21 10:02:44 +01:00
Mark Felder
9e6e3af534 Proxy example for FreeBSD/pf 2025-11-20 17:03:31 +01:00
6 changed files with 117 additions and 33 deletions

View File

@@ -11,6 +11,9 @@
- Organized cmdeploy into install, configure, and activate stages - Organized cmdeploy into install, configure, and activate stages
([#695](https://github.com/chatmail/relay/pull/695)) ([#695](https://github.com/chatmail/relay/pull/695))
- echobot: print invite-link also if it's deployed locally
([#751](https://github.com/chatmail/relay/pull/751))
- docs: move readme.md docs to sphinx documentation rendered at https://chatmail.at/doc/relay - docs: move readme.md docs to sphinx documentation rendered at https://chatmail.at/doc/relay
([#711](https://github.com/chatmail/relay/pull/711)) ([#711](https://github.com/chatmail/relay/pull/711))

View File

@@ -11,6 +11,7 @@ import pathlib
import shutil import shutil
import subprocess import subprocess
import sys import sys
import time
from pathlib import Path from pathlib import Path
import pyinfra import pyinfra
@@ -109,15 +110,22 @@ def run_cmd(args, out):
try: try:
retcode = out.check_call(cmd, env=env) retcode = out.check_call(cmd, env=env)
if retcode == 0: if retcode == 0:
if not args.disable_mail: if not args.disable_mail and not args.dry_run:
print("\nYou can try out the relay by talking to this echo bot: ") print("\nYou can try out the relay by talking to this echo bot: ")
sshexec = SSHExec(args.config.mail_domain, verbose=args.verbose) invite_path = Path("/var/lib/echobot/invite-link.txt")
print( if ssh_host in ["localhost", "@local", "@docker"]:
sshexec( while not invite_path.exists():
call=remote.rshell.shell, time.sleep(0.1)
kwargs=dict(command="cat /var/lib/echobot/invite-link.txt"), with invite_path.open() as f:
print(f.readline())
else:
echo_sshexec = get_sshexec(ssh_host, verbose=args.verbose)
print(
echo_sshexec(
call=remote.rshell.shell,
kwargs=dict(command=f"cat {invite_path}"),
)
) )
)
out.green("Deploy completed, call `cmdeploy dns` next.") out.green("Deploy completed, call `cmdeploy dns` next.")
elif not remote_data["acme_account_url"]: elif not remote_data["acme_account_url"]:
out.red("Deploy completed but letsencrypt not configured") out.red("Deploy completed but letsencrypt not configured")

View File

@@ -9,9 +9,10 @@ if nsigs == nil then
return nil return nil
end end
local valid = false
for i = 1, nsigs do for i = 1, nsigs do
sig = odkim.get_sighandle(ctx, i - 1) sig = odkim.get_sighandle(ctx, i - 1)
sigres = odkim.sig_result(sig) sigres = odkim.sig_result(sig)
-- All signatures that do not correspond to From: -- All signatures that do not correspond to From:
-- were ignored in screen.lua and return sigres -1. -- were ignored in screen.lua and return sigres -1.
@@ -19,10 +20,19 @@ for i = 1, nsigs do
-- Any valid signature that was not ignored like this -- Any valid signature that was not ignored like this
-- means the message is acceptable. -- means the message is acceptable.
if sigres == 0 then if sigres == 0 then
return nil valid = true
end end
end
if valid then
-- Strip all DKIM-Signature headers after successful validation
-- Delete in reverse order to avoid index shifting.
for i = nsigs, 1, -1 do
odkim.del_header(ctx, "DKIM-Signature", i)
end
else
odkim.set_reply(ctx, "554", "5.7.1", "No valid DKIM signature found")
odkim.set_result(ctx, SMFIS_REJECT)
end end
odkim.set_reply(ctx, "554", "5.7.1", "No valid DKIM signature found")
odkim.set_result(ctx, SMFIS_REJECT)
return nil return nil

View File

@@ -140,34 +140,34 @@ def main():
config.webdev = True config.webdev = True
assert config.mail_domain assert config.mail_domain
# start web page generation, open a browser and wait for changes
www_path, src_path, build_dir = get_paths(config) www_path, src_path, build_dir = get_paths(config)
build_dir = build_webpages(src_path, build_dir, config) build_dir = build_webpages(src_path, build_dir, config)
index_path = build_dir.joinpath("index.html") index_path = build_dir.joinpath("index.html")
webbrowser.open(str(index_path)) webbrowser.open(str(index_path))
stats = snapshot_dir_stats(src_path)
print(f"\nOpened URL: file://{index_path.resolve()}\n") print(f"\nOpened URL: file://{index_path.resolve()}\n")
print(f"watching {src_path} directory for changes") print(f"Watching {src_path} directory for changes...")
stats = snapshot_dir_stats(src_path)
changenum = 0 changenum = 0
count = 0 debounce_time = 0.5 # wait 0.5s after detecting a change
while True: while True:
time.sleep(1)
newstats = snapshot_dir_stats(src_path) newstats = snapshot_dir_stats(src_path)
if newstats == stats and count % 60 != 0:
count += 1
time.sleep(1.0)
continue
for key in newstats: if newstats != stats:
if stats[key] != newstats[key]: changed_files = [f for f in newstats if stats.get(f) != newstats[f]]
print(f"*** CHANGED: {key}") for f in changed_files:
changenum += 1 print(f"*** CHANGED: {f}")
stats = newstats stats = newstats
build_webpages(src_path, build_dir, config) changenum += 1
print(f"[{changenum}] regenerated web pages at: {index_path}") build_webpages(src_path, build_dir, config)
print(f"URL: file://{index_path.resolve()}\n\n") print(f"[{changenum}] regenerated web pages at: {index_path}")
count = 0 print(f"URL: file://{index_path.resolve()}\n\n")
time.sleep(debounce_time) # simple debounce
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -273,9 +273,11 @@ Incoming emails must have a valid DKIM signature with
Signing Domain Identifier (SDID, ``d=`` parameter in the DKIM-Signature Signing Domain Identifier (SDID, ``d=`` parameter in the DKIM-Signature
header) equal to the ``From:`` header domain. This property is checked header) equal to the ``From:`` header domain. This property is checked
by OpenDKIM screen policy script before validating the signatures. This by OpenDKIM screen policy script before validating the signatures. This
correpsonds to strict :rfc:`DMARC <7489>` alignment (``adkim=s``). corresponds to strict :rfc:`DMARC <7489>` alignment (``adkim=s``).
If there is no valid DKIM signature on the incoming email, the If there is no valid DKIM signature on the incoming email, the
sender receives a “5.7.1 No valid DKIM signature found” error. sender receives a “5.7.1 No valid DKIM signature found” error.
After validating the DKIM signature,
the `final.lua` script strips all ``OpenDKIM:`` headers to reduce message size on disc.
Note that chatmail relays Note that chatmail relays

View File

@@ -22,7 +22,12 @@ Note that your chatmail relay still needs to be able to make outgoing
connections on port 25 to send messages outside. connections on port 25 to send messages outside.
To setup a reverse proxy (or rather Destination NAT, DNAT) for your To setup a reverse proxy (or rather Destination NAT, DNAT) for your
chatmail relay, put the following configuration in chatmail relay, follow these instructions:
Linux
^^^^^
Put the following configuration in
``/etc/nftables.conf``: ``/etc/nftables.conf``:
:: ::
@@ -110,5 +115,61 @@ Uncomment in ``/etc/sysctl.conf`` the following two lines:
Then reboot the relay or do ``sysctl -p`` and Then reboot the relay or do ``sysctl -p`` and
``nft -f /etc/nftables.conf``. ``nft -f /etc/nftables.conf``.
Once proxy relay is set up, you can add its IP address to the DNS. FreeBSD / pf
^^^^^^^^^^^^
Put the following configuration in
``/etc/pf.conf``:
::
ext_if = "em0"
forward_ports = "{ 25, 80, 143, 443, 465, 587, 993 }"
chatmail_ipv4 = "AAA.BBB.CCC.DDD"
icmp_types = "{ echoreq, echorep, unreach, timex }"
chatmail_ipv6 = "XXX::1"
icmp6_types = "{ echorep, echoreq, neighbradv, neighbrsol, routeradv, routersol, unreach, toobig, timex }"
set skip on lo0
nat on $ext_if inet from any to any -> ($ext_if:0)
nat on $ext_if inet6 from any to any -> ($ext_if:0)
# Define the redirect rules
rdr on $ext_if inet proto tcp from any to ($ext_if:0) port $forward_ports -> $chatmail_ipv4
rdr on $ext_if inet6 proto tcp from any to ($ext_if:0) port $forward_ports -> $chatmail_ipv6
# Accept the incoming traffic to the specified ports we will NAT redirect
pass in quick on $ext_if inet proto tcp from any to any port $forward_ports flags S/SA modulate state
pass in quick on $ext_if inet6 proto tcp from any to any port $forward_ports flags S/SA modulate state
# Allow incoming SSH for host mgmt
pass in quick on $ext_if proto tcp from any to ($ext_if) port 22 flags S/SA modulate state
# Allow ICMP
pass in quick on $ext_if inet proto icmp all icmp-type $icmp_types keep state
pass in quick on $ext_if inet6 proto ipv6-icmp all icmp6-type $icmp6_types keep state
# Allow traffic from anyone to go through the NAT
pass on $ext_if inet proto tcp from any to $chatmail_ipv4 flags S/SA modulate state
pass on $ext_if inet6 proto tcp from any to $chatmail_ipv6 flags S/SA modulate state
# Default allow out
pass out quick on $ext_if from any to any
# Default block
block drop in log all
Insert into ``/etc/sysctl.conf.local`` the following two lines:
::
net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1
Activate the sysctls with ``service sysctl onestart``.
Enable the pf firewall with ``service pf enable``.
Apply the firewall rules with ``service pf start`` or ``pfctl -f /etc/pf.conf``.
Note, enabling the firewall may interrupt your SSH session, but you can reconnect.
Once proxy relay is set up, you can add its IP address to the DNS.