Compare commits

...

7 Commits

Author SHA1 Message Date
267ba1e25d add Dockerfile
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2025-10-28 21:26:28 +01:00
715008e3da add gitsigners
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2025-10-28 21:25:48 +01:00
1cb073b7cd fix devenv on darwin
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2025-10-28 21:22:41 +01:00
99579a27e7 update flake.lock
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2025-10-28 21:21:34 +01:00
1470e69a4b update devenv
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2025-10-28 21:19:33 +01:00
e5460e1629 version 0.3.1
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2025-07-16 09:15:40 +02:00
e2ad2fc15d update certificate for test
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2025-07-16 09:10:50 +02:00
11 changed files with 144 additions and 118 deletions

1
.gitsigners Normal file
View File

@@ -0,0 +1 @@
florian.brandes@posteo.de ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICGTgYytxH0XAlMlxc7A+OYUcQhmG7AXRui+Gxhsy0oq

41
Dockerfile Normal file
View File

@@ -0,0 +1,41 @@
# syntax=docker/dockerfile:1
FROM python:3.13.8-slim as base
# Prevents Python from writing pyc files.
ENV PYTHONDONTWRITEBYTECODE=1
# Keeps Python from buffering stdout and stderr to avoid situations where
# the application crashes without emitting any logs due to buffering.
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
RUN apt-get update
RUN apt-get install -y build-essential libssl-dev python3-dev swig
# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds.
# Leverage a bind mount to requirements.txt to avoid having to copy them into
# into this layer.
RUN --mount=type=cache,target=/root/.cache/pip \
--mount=type=bind,source=requirements.txt,target=requirements.txt \
python -m pip install -r requirements.txt
# Switch to the non-privileged user to run the application.
USER appuser
ADD smtprd_ng /app/smtprd_ng
EXPOSE 8025
CMD python smtprd_ng/smtprd.py --config /app/config.ini

View File

@@ -11,6 +11,8 @@ This can also for example use `msmtp`. This way scripts can use `sendmail` which
Additionally, we can sign and encrypt the emails with S/MIME certificates, which adds a layer of authentification and security for automated information delivery (think security notifications, logs, etc.) Additionally, we can sign and encrypt the emails with S/MIME certificates, which adds a layer of authentification and security for automated information delivery (think security notifications, logs, etc.)
Please note: This will only forward emails to email addresses specified in `config.ini`, so it is not useful as a general SMTP-relay (like `msmtp`) but only for a predefined email set. This is by design.
For now, this is a proof-of-concept. For now, this is a proof-of-concept.
@@ -49,6 +51,17 @@ Note: `build` must be installed. You should use a `venv` for this.
Clone this repo and run `devenv shell` Clone this repo and run `devenv shell`
### docker
1. Clone this repo
2. `cp config.example config.ini`
3. edit `config.ini` as you need it. I'd suggest using `/app/pw` as password file and bild-mounting it.
4. `docker build -t smtprd-ng:latest .`
5. `docker run -p 8025:8025 -v ./config.ini:/app/config.ini -v ./pw:/app/pw --rm -it smtprd-ng:latest`
## Contributing ## Contributing
Contributions are always welcome! Contributions are always welcome!

View File

@@ -5,7 +5,7 @@
pkgs.python3Packages.buildPythonPackage rec { pkgs.python3Packages.buildPythonPackage rec {
pname = "smtprd-ng"; pname = "smtprd-ng";
version = "0.3.0"; version = "0.3.1";
pyproject = true; pyproject = true;
src = lib.cleanSource ./.; src = lib.cleanSource ./.;

View File

@@ -3,11 +3,10 @@
"devenv": { "devenv": {
"locked": { "locked": {
"dir": "src/modules", "dir": "src/modules",
"lastModified": 1719993933, "lastModified": 1761596764,
"owner": "cachix", "owner": "cachix",
"repo": "devenv", "repo": "devenv",
"rev": "83b295ec9febbc662040ffa8539d23f294af275d", "rev": "17560d064ba5e4fc946c0ea0ee7b31ec291e706f",
"treeHash": "decc8709d4ea38f8c3eff4a2a2a4fd396564609f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -20,11 +19,10 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1696426674, "lastModified": 1761588595,
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"treeHash": "2addb7b71a20a25ea74feeaf5c2f6a6b30898ecb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -33,10 +31,31 @@
"type": "github" "type": "github"
} }
}, },
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1760663237,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": { "gitignore": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
"pre-commit-hooks", "git-hooks",
"nixpkgs" "nixpkgs"
] ]
}, },
@@ -45,7 +64,6 @@
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "gitignore.nix", "repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394", "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"treeHash": "ca14199cabdfe1a06a7b1654c76ed49100a689f9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -56,11 +74,10 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1716977621, "lastModified": 1761313199,
"owner": "cachix", "owner": "cachix",
"repo": "devenv-nixpkgs", "repo": "devenv-nixpkgs",
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6", "rev": "d1c30452ebecfc55185ae6d1c983c09da0c274ff",
"treeHash": "6d9f1f7ca0faf1bc2eeb397c78a49623260d3412",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -70,50 +87,14 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-stable": {
"locked": {
"lastModified": 1719837636,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "28f8f3531ebdbea069995c20bd946a295699f275",
"treeHash": "0bd224f9c40a54835c5afeedd325da7056a6f14c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1719259945,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
"treeHash": "1a76ff89a9d4017b48abbb1bad8837b35d604ffc",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"devenv": "devenv", "devenv": "devenv",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks" "pre-commit-hooks": [
"git-hooks"
]
} }
} }
}, },

View File

@@ -20,10 +20,19 @@ in
pkgs.openssl pkgs.openssl
]; ];
env = {
LDFLAGS = "-L${pkgs.openssl.dev}/lib";
CFLAGS = "-I${pkgs.openssl.dev}/include";
SWIG_FEATURES = "-I${pkgs.openssl.dev}/include";
};
# https://devenv.sh/languages/ # https://devenv.sh/languages/
languages.nix.enable = true; languages.nix.enable = true;
languages.python = { languages.python = {
enable = true; enable = true;
libraries = [
pkgs.openssl.dev
];
venv = { venv = {
enable = true; enable = true;
requirements = builtins.readFile req; requirements = builtins.readFile req;
@@ -31,9 +40,9 @@ in
}; };
# https://devenv.sh/pre-commit-hooks/ # https://devenv.sh/pre-commit-hooks/
pre-commit.hooks = { git-hooks.hooks = {
nixfmt.enable = true; nixfmt.enable = true;
nixfmt.package = pkgs.nixfmt-rfc-style; # nixfmt.package = pkgs.nixfmt-rfc-style;
# remove unused imports # remove unused imports
autoflake.enable = true; autoflake.enable = true;
# formatter # formatter

49
flake.lock generated
View File

@@ -3,11 +3,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1696426674, "lastModified": 1747046372,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -21,11 +21,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1710146030, "lastModified": 1731533236,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -57,11 +57,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1720087678, "lastModified": 1761594641,
"narHash": "sha256-uOhYJU3ldDKXYV+mFaXcPtyjq/UIMh/6SCuoVNU9rxM=", "narHash": "sha256-sImk6SJQASDLQo8l+0zWWaBgg7TueLS6lTvdH5pBZpo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1afc5440469f94e7ed26e8648820971b102afdc3", "rev": "1666250dbe4141e4ca8aaf89b40a3a51c2e36144",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -70,29 +70,13 @@
"type": "indirect" "type": "indirect"
} }
}, },
"nixpkgs-stable": {
"locked": {
"lastModified": 1718811006,
"narHash": "sha256-0Y8IrGhRmBmT7HHXlxxepg2t8j1X90++qRN3lukGaIk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "03d771e513ce90147b65fe922d87d3a0356fc125",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1719082008, "lastModified": 1759070547,
"narHash": "sha256-jHJSUH619zBQ6WdC21fFAlDxHErKVDJ5fpN0Hgx4sjs=", "narHash": "sha256-JVZl8NaVRYb0+381nl7LvPE+A774/dRpif01FKLrYFQ=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "9693852a2070b398ee123a329e68f0dab5526681", "rev": "647e5c14cbd5067f44ac86b74f014962df460840",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -106,15 +90,14 @@
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"gitignore": "gitignore", "gitignore": "gitignore",
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_2"
"nixpkgs-stable": "nixpkgs-stable"
}, },
"locked": { "locked": {
"lastModified": 1719259945, "lastModified": 1760663237,
"narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=", "narHash": "sha256-BflA6U4AM1bzuRMR8QqzPXqh8sWVCNDzOdsxXEguJIc=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07", "rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "smtprd-ng" name = "smtprd-ng"
version = "0.3.0" version = "0.3.1"
description = "SMTP forwarding relay daemon with signing and encryption" description = "SMTP forwarding relay daemon with signing and encryption"
authors = [{ name = "Florian Brandes", email = "dev@mail.flo-the.dev" }] authors = [{ name = "Florian Brandes", email = "dev@mail.flo-the.dev" }]
classifiers = [ classifiers = [

View File

@@ -1,16 +1,16 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALb6E084QJJVnRGg MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKZQOkKyDARSm0nn
Tij6Mc+ZzO3bV8Izt+A2RkpV4aYTjClVc2pLHh8KyjTA/bqnUYXfQCEDTHx2RsE5 UCcNS0jmWa70RUH4AyZGBNGPN87z9CkjuiNWYb75aMomoAOdfzFNbPvqTiBkmrx6
DqmyaYxWejcptw3Ht1YkZO2RW7zLSNQjlwFfcgf92XH1uzUxido/Lbw8LkHnJqCO GJHrpA77GSkctEXWeSstxFdZWr14eR0WIKq4Cg7bZqqYliVaxsPUozIGZihMcx8e
4jemU9wP9b82JBgou91VEzqTfHPXAgMBAAECgYAc601d+fAKsMlQXdu8kj6JJy/C uyepTqOD5cePFKsNL0ZbxcJRD79dAgMBAAECgYBwLoF953cvm0Df8sUf8lmrzKUF
cCZgpTfskeduHEC7tN80MTM6m4C5O0VWLSJs+8DgvbYvAYx3J2Jra48rtu0DYrd/ 3AKzYsVNFcG+UOqGEAvLjCvMFkJMfRNv9+nLRns7GEzBB+S2+OVe6MSV+OppkgxR
gWqijODMDr6l8jgEeA8DN0OwcWhTB6unowHzR7RadijzP0nOIiGyArPtguGk4XKw AszmV9S/A3BS4DaCYj3xGJAguZJhqWLGqXc23ukuCsnpIBtldSIT7Dj4i4gC8Auq
NcRoU36bxb/smBCaIQJBAOFMt0xTx6celUUB+b7HKNLe9kZb2shSZXzKIuzqAPJd gvNxNQgZC2yMm0sGSQJBANLJrr8zJmKG7kTq6PM4psmSkfbd1s3Tugqkeg86xnRu
8TafC2CSt6iFTfhwr+kARz300r0y/JrGG30a6B8kRb8CQQDP6PvDoY6atLU35h+l 8BI5n15VB2mUvWbO3U+BtTFKI+69mlnq6Av496E+PVMCQQDJ/HZxq14tc3du28fV
WjuvkOKaR6ny/uarNP3nHapxgC9cNUkySFBDrSIJhNorYSGSMxqhEGUPWHzhH0PG R2cd5uIW5zqRsfOwYkoJ/SXnQf1FHfoM1w7ibmZEu8S2kfmjWuo6O3PXMunnBMqM
H0fpAkEAkelpXNl1mFpKOiMJZ/D8E3Wq8e5TRyF18NfIvr7eVhlZOxLN/4GFyHJt eMqPAkAnuy4+CeUlUyucP+8S0U3W1tK+hogTmeIKWYBMWJbJoZOMy+G3RS21f/zH
CNWSV8iCWzHPuhDnYCWlb+SZKHIJaQJACs4i94HX9XZazLLrBh7wZylyfW4oCPby YRyj/N3rYX8uY/yxEC8W+qGqefHTAkAu+3Zhgkbps434T07wISevIOE+CpLpCMdy
agdxAqfqCcgNrg8e5LwZX8sJr9D1vbdolT6Orbw6ZFfG9bQ4Q32wsQJAZzbkDv+l I6qtICEM5aCPzyU2j19688r9+d8LyHCUurT4zpNnXwRhJBApFH+pAkEAt2EbsszX
UBCR9qNqEpOtyOhBV8b8NzKgI/SBjgNbfzOki3r88qkwbJSMOZPZv3kkwmkjLTfz S1aLA1vof1u6RHFYCylng5wDsreGg2ON3tHpt020n0PwrFPVfJH+KaAkRJC3EYgg
x7eQLB/6eQFmFw== gsqdDSdEUyHIsQ==
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

@@ -1,17 +1,15 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIICsDCCAhmgAwIBAgIUF0xm6FBeRujH+4/G1506jAty04UwDQYJKoZIhvcNAQEL MIICZjCCAc+gAwIBAgIUW4OUtBdWs+bYoFmwkWdi9Zx813AwDQYJKoZIhvcNAQEL
BQAwajELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEjMCEGCSqGSIb3DQEJARYUc2VuZGVy GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNTA3MTYwNjUxMjRaFw0yNjA3
QGxvY2FsaG9zdC5jb20wHhcNMjQwNzA2MTEyMjU2WhcNMjUwNzA2MTEyMjU2WjBq MTYwNjUxMjRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEB
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMSMwIQYJKoZIhvcNAQkBFhRzZW5kZXJAbG9j BQADgY0AMIGJAoGBAKZQOkKyDARSm0nnUCcNS0jmWa70RUH4AyZGBNGPN87z9Ckj
YWxob3N0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtvoTTzhAklWd uiNWYb75aMomoAOdfzFNbPvqTiBkmrx6GJHrpA77GSkctEXWeSstxFdZWr14eR0W
EaBOKPoxz5nM7dtXwjO34DZGSlXhphOMKVVzakseHwrKNMD9uqdRhd9AIQNMfHZG IKq4Cg7bZqqYliVaxsPUozIGZihMcx8euyepTqOD5cePFKsNL0ZbxcJRD79dAgMB
wTkOqbJpjFZ6Nym3Dce3ViRk7ZFbvMtI1COXAV9yB/3ZcfW7NTGJ2j8tvDwuQecm AAGjUzBRMB0GA1UdDgQWBBRirbLkQUSJy7TsedWuFgEGwGZ2MjAfBgNVHSMEGDAW
oI7iN6ZT3A/1vzYkGCi73VUTOpN8c9cCAwEAAaNTMFEwHQYDVR0OBBYEFDFFLpTd gBRirbLkQUSJy7TsedWuFgEGwGZ2MjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
y1co5C6OVCLJRgIicstMMB8GA1UdIwQYMBaAFDFFLpTdy1co5C6OVCLJRgIicstM DQEBCwUAA4GBAGCzB1nq3PMBwSXetoXXO50ZI5+SNM+u6KVI3z0t6Tsg3FmvtP0c
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEATdAHRrnrl2aQ8SI3 WlJ8V9CthyQaf02+bbfKlJ85LHa62w9H2b/WozzpF0GqUujrGA9j4NFDGq19KtI5
kQgUsTp5hHc3M5b2+ZbNYCsz7SYQBtHniGId9vBh941gUs8R8X16Cdp0pjVAayeU mX4WTiCkcLhh1xV7CLAdrDWtOPLTb+O53/t9xkOYIzXHpc2ha+EhiU0G
CCW1Zs47tA9IIT1hslOORibTcSQKr7TI+RprURyky8m2T9PbOSLgmnjlbydbxN7L
hxx8dg1pWRfEKBSvO1gOkcTo7SQ=
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@@ -127,7 +127,7 @@ def test_client_encrypt():
# new line is important to seperate header from body # new line is important to seperate header from body
assert lines[7] == "" assert lines[7] == ""
assert ( assert (
lines[8] == "MIIBdgYJKoZIhvcNAQcDoIIBZzCCAWMCAQAxggEeMIIBGgIBADCBgjBqMQswCQYD" lines[8] == "MIIBTgYJKoZIhvcNAQcDoIIBPzCCATsCAQAxgfcwgfQCAQAwXTBFMQswCQYDVQQG"
) )
# test decryption # test decryption