mirror of
https://gitlab.com/simple-nixos-mailserver/nixos-mailserver.git
synced 2025-05-17 16:10:49 +05:00
Merge branch 'pre-commit' into 'master'
Pre-Commit Hook See merge request simple-nixos-mailserver/nixos-mailserver!385
This commit is contained in:
commit
433520257a
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
result
|
result
|
||||||
|
.direnv
|
||||||
|
.pre-commit-config.yaml
|
||||||
|
126
README.md
126
README.md
@ -1,82 +1,82 @@
|
|||||||
# ![Simple Nixos MailServer][logo]
|
# ![Simple Nixos MailServer][logo]
|
||||||
|
|
||||||

|

|
||||||
[](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/commits/master)
|
[](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/commits/master)
|
||||||
|
|
||||||
|
|
||||||
## Release branches
|
## Release branches
|
||||||
|
|
||||||
For each NixOS release, we publish a branch. You then have to use the
|
For each NixOS release, we publish a branch. You then have to use the
|
||||||
SNM branch corresponding to your NixOS version.
|
SNM branch corresponding to your NixOS version.
|
||||||
|
|
||||||
* For NixOS 24.11
|
* For NixOS 24.11
|
||||||
- Use the [SNM branch `nixos-24.11`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-24.11)
|
* Use the [SNM branch `nixos-24.11`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-24.11)
|
||||||
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-24.11/)
|
* [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-24.11/)
|
||||||
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-24.11/release-notes.html#nixos-24-11)
|
* [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-24.11/release-notes.html#nixos-24-11)
|
||||||
* For NixOS 24.05
|
* For NixOS 24.05
|
||||||
- Use the [SNM branch `nixos-24.05`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-24.05)
|
* Use the [SNM branch `nixos-24.05`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-24.05)
|
||||||
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-24.05/)
|
* [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-24.05/)
|
||||||
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-24.05/release-notes.html#nixos-24-05)
|
* [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-24.05/release-notes.html#nixos-24-05)
|
||||||
* For NixOS unstable
|
* For NixOS unstable
|
||||||
- Use the [SNM branch `master`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/master)
|
* Use the [SNM branch `master`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/master)
|
||||||
- [Documentation](https://nixos-mailserver.readthedocs.io/en/latest/)
|
* [Documentation](https://nixos-mailserver.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* [x] Continous Integration Testing
|
* [x] Continous Integration Testing
|
||||||
* [x] Multiple Domains
|
* [x] Multiple Domains
|
||||||
* Postfix
|
* Postfix
|
||||||
- [x] SMTP on port 25
|
* [x] SMTP on port 25
|
||||||
- [x] Submission TLS on port 465
|
* [x] Submission TLS on port 465
|
||||||
- [x] Submission StartTLS on port 587
|
* [x] Submission StartTLS on port 587
|
||||||
- [x] LMTP with Dovecot
|
* [x] LMTP with Dovecot
|
||||||
* Dovecot
|
* Dovecot
|
||||||
- [x] Maildir folders
|
* [x] Maildir folders
|
||||||
- [x] IMAP with TLS on port 993
|
* [x] IMAP with TLS on port 993
|
||||||
- [x] POP3 with TLS on port 995
|
* [x] POP3 with TLS on port 995
|
||||||
- [x] IMAP with StartTLS on port 143
|
* [x] IMAP with StartTLS on port 143
|
||||||
- [x] POP3 with StartTLS on port 110
|
* [x] POP3 with StartTLS on port 110
|
||||||
* Certificates
|
* Certificates
|
||||||
- [x] ACME
|
* [x] ACME
|
||||||
- [x] Custom certificates
|
* [x] Custom certificates
|
||||||
* Spam Filtering
|
* Spam Filtering
|
||||||
- [x] Via Rspamd
|
* [x] Via Rspamd
|
||||||
* Virus Scanning
|
* Virus Scanning
|
||||||
- [x] Via ClamAV
|
* [x] Via ClamAV
|
||||||
* DKIM Signing
|
* DKIM Signing
|
||||||
- [x] Via Rspamd
|
* [x] Via Rspamd
|
||||||
* User Management
|
* User Management
|
||||||
- [x] Declarative user management
|
* [x] Declarative user management
|
||||||
- [x] Declarative password management
|
* [x] Declarative password management
|
||||||
- [x] LDAP users
|
* [x] LDAP users
|
||||||
* Sieve
|
* Sieve
|
||||||
- [x] Allow user defined sieve scripts
|
* [x] Allow user defined sieve scripts
|
||||||
- [x] Moving mails from/to junk trains the Bayes filter
|
* [x] Moving mails from/to junk trains the Bayes filter
|
||||||
- [x] ManageSieve support
|
* [x] ManageSieve support
|
||||||
* User Aliases
|
* User Aliases
|
||||||
- [x] Regular aliases
|
* [x] Regular aliases
|
||||||
- [x] Catch all aliases
|
* [x] Catch all aliases
|
||||||
|
|
||||||
### In the future
|
### In the future
|
||||||
|
|
||||||
* Automatic client configuration
|
* Automatic client configuration
|
||||||
- [ ] [Autoconfig](https://web.archive.org/web/20210624004729/https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration)
|
* [ ] [Autoconfig](https://web.archive.org/web/20210624004729/https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration)
|
||||||
- [ ] [Autodiscovery](https://learn.microsoft.com/en-us/exchange/architecture/client-access/autodiscover?view=exchserver-2019)
|
* [ ] [Autodiscovery](https://learn.microsoft.com/en-us/exchange/architecture/client-access/autodiscover?view=exchserver-2019)
|
||||||
- [ ] [Mobileconfig](https://support.apple.com/guide/profile-manager/distribute-profiles-manually-pmdbd71ebc9/mac)
|
* [ ] [Mobileconfig](https://support.apple.com/guide/profile-manager/distribute-profiles-manually-pmdbd71ebc9/mac)
|
||||||
* DKIM Signing
|
* DKIM Signing
|
||||||
- [ ] Allow per domain selectors
|
* [ ] Allow per domain selectors
|
||||||
- [ ] Allow passing DKIM signing keys
|
* [ ] Allow passing DKIM signing keys
|
||||||
* Improve the Forwarding Experience
|
* Improve the Forwarding Experience
|
||||||
- [ ] Support [ARC](https://en.wikipedia.org/wiki/Authenticated_Received_Chain) signing with [Rspamd](https://rspamd.com/doc/modules/arc.html)
|
* [ ] Support [ARC](https://en.wikipedia.org/wiki/Authenticated_Received_Chain) signing with [Rspamd](https://rspamd.com/doc/modules/arc.html)
|
||||||
- [ ] Support [SRS](https://en.wikipedia.org/wiki/Sender_Rewriting_Scheme) with [postsrsd](https://github.com/roehling/postsrsd)
|
* [ ] Support [SRS](https://en.wikipedia.org/wiki/Sender_Rewriting_Scheme) with [postsrsd](https://github.com/roehling/postsrsd)
|
||||||
* User management
|
* User management
|
||||||
- [ ] Allow local and LDAP user to coexist
|
* [ ] Allow local and LDAP user to coexist
|
||||||
* OpenID Connect
|
* OpenID Connect
|
||||||
- Depends on relevant clients adding support, e.g. [Thunderbird](https://bugzilla.mozilla.org/show_bug.cgi?id=1602166)
|
* Depends on relevant clients adding support, e.g. [Thunderbird](https://bugzilla.mozilla.org/show_bug.cgi?id=1602166)
|
||||||
|
|
||||||
### Get in touch
|
### Get in touch
|
||||||
|
|
||||||
- Matrix: [#nixos-mailserver:nixos.org](https://matrix.to/#/#nixos-mailserver:nixos.org)
|
* Matrix: [#nixos-mailserver:nixos.org](https://matrix.to/#/#nixos-mailserver:nixos.org)
|
||||||
- IRC: `#nixos-mailserver` on [Libera Chat](https://libera.chat/guides/connect)
|
* IRC: `#nixos-mailserver` on [Libera Chat](https://libera.chat/guides/connect)
|
||||||
|
|
||||||
## How to Set Up a 10/10 Mail Server Guide
|
## How to Set Up a 10/10 Mail Server Guide
|
||||||
|
|
||||||
@ -89,16 +89,18 @@ For a complete list of options, [see in readthedocs](https://nixos-mailserver.re
|
|||||||
See the [How to Develop SNM](https://nixos-mailserver.readthedocs.io/en/latest/howto-develop.html) documentation page.
|
See the [How to Develop SNM](https://nixos-mailserver.readthedocs.io/en/latest/howto-develop.html) documentation page.
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
See the [contributor tab](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/graphs/master)
|
See the [contributor tab](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/graphs/master)
|
||||||
|
|
||||||
### Alternative Implementations
|
### Alternative Implementations
|
||||||
* [NixCloud Webservices](https://github.com/nixcloud/nixcloud-webservices)
|
|
||||||
|
* [NixCloud Webservices](https://github.com/nixcloud/nixcloud-webservices)
|
||||||
|
|
||||||
### Credits
|
### Credits
|
||||||
* send mail graphic by [tnp_dreamingmao](https://thenounproject.com/dreamingmao)
|
|
||||||
|
* send mail graphic by [tnp_dreamingmao](https://thenounproject.com/dreamingmao)
|
||||||
from [TheNounProject](https://thenounproject.com/) is licensed under
|
from [TheNounProject](https://thenounproject.com/) is licensed under
|
||||||
[CC BY 3.0](http://creativecommons.org/~/3.0/)
|
[CC BY 3.0](http://creativecommons.org/~/3.0/)
|
||||||
* Logo made with [Logomakr.com](https://logomakr.com)
|
* Logo made with [Logomakr.com](https://logomakr.com)
|
||||||
|
|
||||||
|
|
||||||
[logo]: docs/logo.png
|
[logo]: docs/logo.png
|
||||||
|
22
docs/conf.py
22
docs/conf.py
@ -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,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Nix Flakes
|
Nix Flakes
|
||||||
==========
|
==========
|
||||||
|
|
||||||
If you're using `flakes <https://nixos.wiki/wiki/Flakes>`__, you can use
|
If you're using `flakes <https://wiki.nixos.org/wiki/Flakes>`__, you can use
|
||||||
the following minimal ``flake.nix`` as an example:
|
the following minimal ``flake.nix`` as an example:
|
||||||
|
|
||||||
.. code:: nix
|
.. code:: nix
|
||||||
|
@ -4,13 +4,33 @@ Contribute or troubleshoot
|
|||||||
To report an issue, please go to
|
To report an issue, please go to
|
||||||
`<https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/issues>`_.
|
`<https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/issues>`_.
|
||||||
|
|
||||||
You can also chat with us on the Libera IRC channel ``#nixos-mailserver``.
|
If you have questions, feel free to reach out:
|
||||||
|
|
||||||
|
* Matrix: `#nixos-mailserver:nixos.org <https://matrix.to/#/#nixos-mailserver:nixos.org>`__
|
||||||
|
* IRC: `#nixos-mailserver <ircs://irc.libera.chat/nixos-mailserver>`__ on `Libera Chat <https://libera.chat/guides/connect>`__
|
||||||
|
|
||||||
|
All our workflows rely on Nix being configured with `Flakes <https://wiki.nixos.org/wiki/Flakes#Installing_flakes>`__.
|
||||||
|
|
||||||
|
Development Shell
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
We provide a `flake.nix` devshell that automatically sets up pre-commit hooks,
|
||||||
|
which allows for fast feedback cycles when making changes to the repository.
|
||||||
|
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
$ nix develop
|
||||||
|
|
||||||
|
|
||||||
|
We recommend setting up `direnv <https://direnv.net/>`__ to automatically
|
||||||
|
attach to the development environment when entering the project directories.
|
||||||
|
|
||||||
Run NixOS tests
|
Run NixOS tests
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
To run the test suite, you need to enable `Nix Flakes
|
To run the test suite, you need to enable `Nix Flakes
|
||||||
<https://nixos.wiki/wiki/Flakes#Installing_flakes>`_.
|
<https://wiki.nixos.org/wiki/Flakes#Installing_flakes>`__.
|
||||||
|
|
||||||
You can then run the testsuite via
|
You can then run the testsuite via
|
||||||
|
|
||||||
@ -37,7 +57,7 @@ For the syntax, see the `RST/Sphinx primer
|
|||||||
<https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html>`_.
|
<https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html>`_.
|
||||||
|
|
||||||
To build the documentation, you need to enable `Nix Flakes
|
To build the documentation, you need to enable `Nix Flakes
|
||||||
<https://nixos.wiki/wiki/Flakes#Installing_flakes>`_.
|
<https://wiki.nixos.org/wiki/Flakes#Installing_flakes>`__.
|
||||||
|
|
||||||
|
|
||||||
::
|
::
|
||||||
|
46
flake.lock
generated
46
flake.lock
generated
@ -32,6 +32,51 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"git-hooks": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": [
|
||||||
|
"flake-compat"
|
||||||
|
],
|
||||||
|
"gitignore": "gitignore",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1742649964,
|
||||||
|
"narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "git-hooks.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitignore": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"git-hooks",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1709087332,
|
||||||
|
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1747179050,
|
"lastModified": 1747179050,
|
||||||
@ -68,6 +113,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"blobs": "blobs",
|
"blobs": "blobs",
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
|
"git-hooks": "git-hooks",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixpkgs-24_11": "nixpkgs-24_11"
|
"nixpkgs-24_11": "nixpkgs-24_11"
|
||||||
}
|
}
|
||||||
|
63
flake.nix
63
flake.nix
@ -3,9 +3,15 @@
|
|||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
flake-compat = {
|
flake-compat = {
|
||||||
|
# for shell.nix compat
|
||||||
url = "github:edolstra/flake-compat";
|
url = "github:edolstra/flake-compat";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
|
git-hooks = {
|
||||||
|
url = "github:cachix/git-hooks.nix";
|
||||||
|
inputs.flake-compat.follows = "flake-compat";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
nixpkgs-24_11.url = "github:NixOS/nixpkgs/nixos-24.11";
|
nixpkgs-24_11.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||||
blobs = {
|
blobs = {
|
||||||
@ -14,7 +20,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, blobs, nixpkgs, nixpkgs-24_11, ... }: let
|
outputs = { self, blobs, git-hooks, nixpkgs, nixpkgs-24_11, ... }: let
|
||||||
lib = nixpkgs.lib;
|
lib = nixpkgs.lib;
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
@ -90,6 +96,7 @@
|
|||||||
in pkgs.runCommand "options.md" { buildInputs = [pkgs.python3Minimal]; } ''
|
in pkgs.runCommand "options.md" { buildInputs = [pkgs.python3Minimal]; } ''
|
||||||
echo "Generating options.md from ${options}"
|
echo "Generating options.md from ${options}"
|
||||||
python ${./scripts/generate-options.py} ${options} > $out
|
python ${./scripts/generate-options.py} ${options} > $out
|
||||||
|
echo $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
documentation = pkgs.stdenv.mkDerivation {
|
documentation = pkgs.stdenv.mkDerivation {
|
||||||
@ -122,16 +129,62 @@
|
|||||||
nixosModule = self.nixosModules.default; # compatibility
|
nixosModule = self.nixosModules.default; # compatibility
|
||||||
hydraJobs.${system} = allTests // {
|
hydraJobs.${system} = allTests // {
|
||||||
inherit documentation;
|
inherit documentation;
|
||||||
|
inherit (self.checks.${system}) pre-commit;
|
||||||
|
};
|
||||||
|
checks.${system} = allTests // {
|
||||||
|
pre-commit = git-hooks.lib.${system}.run {
|
||||||
|
src = ./.;
|
||||||
|
hooks = {
|
||||||
|
# docs
|
||||||
|
markdownlint = {
|
||||||
|
enable = true;
|
||||||
|
settings.configuration = {
|
||||||
|
# Max line length, doesn't seem to correclty account for lines containing links
|
||||||
|
# https://github.com/DavidAnson/markdownlint/blob/main/doc/md013.md
|
||||||
|
MD013 = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
rstcheck = {
|
||||||
|
enable = true;
|
||||||
|
entry = lib.getExe pkgs.rstcheckWithSphinx;
|
||||||
|
files = "\\.rst$";
|
||||||
|
};
|
||||||
|
|
||||||
|
# nix
|
||||||
|
deadnix.enable = true;
|
||||||
|
|
||||||
|
# python
|
||||||
|
pyright.enable = true;
|
||||||
|
ruff = {
|
||||||
|
enable = true;
|
||||||
|
args = [
|
||||||
|
"--extend-select"
|
||||||
|
"I"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
ruff-format.enable = true;
|
||||||
|
|
||||||
|
# scripts
|
||||||
|
shellcheck.enable = true;
|
||||||
|
|
||||||
|
# sieve
|
||||||
|
check-sieve = {
|
||||||
|
enable = true;
|
||||||
|
entry = lib.getExe pkgs.check-sieve;
|
||||||
|
files = "\\.sieve$";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
checks.${system} = allTests;
|
|
||||||
packages.${system} = {
|
packages.${system} = {
|
||||||
inherit optionsDoc documentation;
|
inherit optionsDoc documentation;
|
||||||
};
|
};
|
||||||
devShells.${system}.default = pkgs.mkShell {
|
devShells.${system}.default = pkgs.mkShellNoCC {
|
||||||
inputsFrom = [ documentation ];
|
inputsFrom = [ documentation ];
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
clamav
|
glab
|
||||||
];
|
] ++ self.checks.${system}.pre-commit.enabledPackages;
|
||||||
|
shellHook = self.checks.${system}.pre-commit.shellHook;
|
||||||
};
|
};
|
||||||
devShell.${system} = self.devShells.${system}.default; # compatibility
|
devShell.${system} = self.devShells.${system}.default; # compatibility
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, options, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
|
@ -39,27 +39,6 @@ let
|
|||||||
);
|
);
|
||||||
|
|
||||||
postfixCfg = config.services.postfix;
|
postfixCfg = config.services.postfix;
|
||||||
dovecot2Cfg = config.services.dovecot2;
|
|
||||||
|
|
||||||
stateDir = "/var/lib/dovecot";
|
|
||||||
|
|
||||||
pipeBin = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "pipe_bin";
|
|
||||||
src = ./dovecot/pipe_bin;
|
|
||||||
buildInputs = with pkgs; [ makeWrapper coreutils bash rspamd ];
|
|
||||||
buildCommand = ''
|
|
||||||
mkdir -p $out/pipe/bin
|
|
||||||
cp $src/* $out/pipe/bin/
|
|
||||||
chmod a+x $out/pipe/bin/*
|
|
||||||
patchShebangs $out/pipe/bin
|
|
||||||
|
|
||||||
for file in $out/pipe/bin/*; do
|
|
||||||
wrapProgram $file \
|
|
||||||
--set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin"
|
|
||||||
done
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
ldapConfig = pkgs.writeTextFile {
|
ldapConfig = pkgs.writeTextFile {
|
||||||
name = "dovecot-ldap.conf.ext.template";
|
name = "dovecot-ldap.conf.ext.template";
|
||||||
@ -109,7 +88,7 @@ let
|
|||||||
# Prevent world-readable password files, even temporarily.
|
# Prevent world-readable password files, even temporarily.
|
||||||
umask 077
|
umask 077
|
||||||
|
|
||||||
for f in ${builtins.toString (lib.mapAttrsToList (name: value: passwordFiles."${name}") cfg.loginAccounts)}; do
|
for f in ${builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts)}; do
|
||||||
if [ ! -f "$f" ]; then
|
if [ ! -f "$f" ]; then
|
||||||
echo "Expected password hash file $f does not exist!"
|
echo "Expected password hash file $f does not exist!"
|
||||||
exit 1
|
exit 1
|
||||||
@ -117,7 +96,7 @@ let
|
|||||||
done
|
done
|
||||||
|
|
||||||
cat <<EOF > ${passwdFile}
|
cat <<EOF > ${passwdFile}
|
||||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
|
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: _:
|
||||||
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
|
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
|
||||||
) cfg.loginAccounts)}
|
) cfg.loginAccounts)}
|
||||||
EOF
|
EOF
|
||||||
@ -130,7 +109,7 @@ let
|
|||||||
EOF
|
EOF
|
||||||
'';
|
'';
|
||||||
|
|
||||||
junkMailboxes = builtins.attrNames (lib.filterAttrs (n: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes);
|
junkMailboxes = builtins.attrNames (lib.filterAttrs (_: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes);
|
||||||
junkMailboxNumber = builtins.length junkMailboxes;
|
junkMailboxNumber = builtins.length junkMailboxes;
|
||||||
# The assertion garantees there is exactly one Junk mailbox.
|
# The assertion garantees there is exactly one Junk mailbox.
|
||||||
junkMailboxName = if junkMailboxNumber == 1 then builtins.elemAt junkMailboxes 0 else "";
|
junkMailboxName = if junkMailboxNumber == 1 then builtins.elemAt junkMailboxes 0 else "";
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -o errexit
|
|
||||||
exec rspamc -h /run/rspamd/worker-controller.sock learn_ham
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -o errexit
|
|
||||||
exec rspamc -h /run/rspamd/worker-controller.sock learn_spam
|
|
@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{ config, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
|
@ -25,7 +25,7 @@ let
|
|||||||
# Merge several lookup tables. A lookup table is a attribute set where
|
# Merge several lookup tables. A lookup table is a attribute set where
|
||||||
# - the key is an address (user@example.com) or a domain (@example.com)
|
# - the key is an address (user@example.com) or a domain (@example.com)
|
||||||
# - the value is a list of addresses
|
# - the value is a list of addresses
|
||||||
mergeLookupTables = tables: lib.zipAttrsWith (n: v: lib.flatten v) tables;
|
mergeLookupTables = tables: lib.zipAttrsWith (_: v: lib.flatten v) tables;
|
||||||
|
|
||||||
# valiases_postfix :: Map String [String]
|
# valiases_postfix :: Map String [String]
|
||||||
valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
||||||
@ -123,13 +123,8 @@ let
|
|||||||
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}>
|
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}>
|
||||||
'');
|
'');
|
||||||
|
|
||||||
inetSocket = addr: port: "inet:[${toString port}@${addr}]";
|
|
||||||
unixSocket = sock: "unix:${sock}";
|
|
||||||
|
|
||||||
smtpdMilters = [ "unix:/run/rspamd/rspamd-milter.sock" ];
|
smtpdMilters = [ "unix:/run/rspamd/rspamd-milter.sock" ];
|
||||||
|
|
||||||
policyd-spf = pkgs.writeText "policyd-spf.conf" cfg.policydSPFExtraConfig;
|
|
||||||
|
|
||||||
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
||||||
mappedRegexFile = name: "pcre:/var/lib/postfix/conf/${name}";
|
mappedRegexFile = name: "pcre:/var/lib/postfix/conf/${name}";
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ in
|
|||||||
nativeBuildInputs = with pkgs; [ makeWrapper ];
|
nativeBuildInputs = with pkgs; [ makeWrapper ];
|
||||||
}''
|
}''
|
||||||
makeWrapper ${pkgs.rspamd}/bin/rspamc $out/bin/rspamc \
|
makeWrapper ${pkgs.rspamd}/bin/rspamc $out/bin/rspamc \
|
||||||
--add-flags "-h /var/run/rspamd/worker-controller.sock"
|
--add-flags "-h /run/rspamd/worker-controller.sock"
|
||||||
'')
|
'')
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
from textwrap import indent
|
||||||
|
from typing import Any, Mapping
|
||||||
|
|
||||||
header = """
|
header = """
|
||||||
# Mailserver options
|
# Mailserver options
|
||||||
@ -21,62 +23,87 @@ 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):
|
|
||||||
if attr in opt:
|
|
||||||
if isinstance(opt[attr], dict) and '_type' in opt[attr]:
|
|
||||||
if opt[attr]['_type'] == 'literalExpression':
|
|
||||||
if '\n' in opt[attr]['text']:
|
|
||||||
res = '\n```nix\n' + opt[attr]['text'].rstrip('\n') + '\n```'
|
|
||||||
else:
|
|
||||||
res = '```{}```'.format(opt[attr]['text'])
|
|
||||||
elif opt[attr]['_type'] == 'literalMD':
|
|
||||||
res = opt[attr]['text']
|
|
||||||
else:
|
|
||||||
s = str(opt[attr])
|
|
||||||
if s == "":
|
|
||||||
res = '`""`'
|
|
||||||
elif '\n' in s:
|
|
||||||
res = '\n```\n' + s.rstrip('\n') + '\n```'
|
|
||||||
else:
|
|
||||||
res = '```{}```'.format(s)
|
|
||||||
res = '- ' + attr + ': ' + res
|
|
||||||
else:
|
|
||||||
res = ""
|
|
||||||
return res
|
|
||||||
|
|
||||||
def print_option(opt):
|
def md_literal(value: str) -> str:
|
||||||
if isinstance(opt['description'], dict) and '_type' in opt['description']: # mdDoc
|
return f"`{value}`"
|
||||||
description = opt['description']['text']
|
|
||||||
|
|
||||||
|
def md_codefence(value: str, language: str = "nix") -> str:
|
||||||
|
return indent(
|
||||||
|
f"\n```{language}\n{value}\n```",
|
||||||
|
prefix=2 * " ",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def render_option_value(option: Mapping[str, Any], key: str) -> str:
|
||||||
|
if key not in option:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if isinstance(option[key], dict) and "_type" in option[key]:
|
||||||
|
if option[key]["_type"] == "literalExpression":
|
||||||
|
# multi-line codeblock
|
||||||
|
if "\n" in option[key]["text"]:
|
||||||
|
text = option[key]["text"].rstrip("\n")
|
||||||
|
value = md_codefence(text)
|
||||||
|
# inline codeblock
|
||||||
|
else:
|
||||||
|
value = md_literal(option[key]["text"])
|
||||||
|
# literal markdown
|
||||||
|
elif option[key]["_type"] == "literalMD":
|
||||||
|
value = option[key]["text"]
|
||||||
|
else:
|
||||||
|
assert RuntimeError(f"Unhandled option type {option[key]['_type']}")
|
||||||
else:
|
else:
|
||||||
description = opt['description']
|
text = str(option[key])
|
||||||
print(template.format(
|
if text == "":
|
||||||
key=opt['name'],
|
value = md_literal('""')
|
||||||
description=description or "",
|
elif "\n" in text:
|
||||||
type="- type: ```{}```".format(opt['type']),
|
value = md_codefence(text.rstrip("\n"))
|
||||||
default=render_option_value(opt, 'default'),
|
else:
|
||||||
example=render_option_value(opt, 'example')))
|
value = md_literal(text)
|
||||||
|
|
||||||
|
return f"- {key}: {value}" # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def print_option(option):
|
||||||
|
if (
|
||||||
|
isinstance(option["description"], dict) and "_type" in option["description"]
|
||||||
|
): # mdDoc
|
||||||
|
description = option["description"]["text"]
|
||||||
|
else:
|
||||||
|
description = option["description"]
|
||||||
|
print(
|
||||||
|
template.format(
|
||||||
|
key=option["name"],
|
||||||
|
description=description or "",
|
||||||
|
type=f"- type: {md_literal(option['type'])}",
|
||||||
|
default=render_option_value(option, "default"),
|
||||||
|
example=render_option_value(option, "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(f"## `{c}`\n")
|
||||||
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)
|
||||||
|
@ -1,32 +1,31 @@
|
|||||||
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
|
||||||
|
|
||||||
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(f"Sending mail with subject '{subject}'")
|
||||||
|
message = "\n".join(
|
||||||
|
[
|
||||||
|
f"From: {from_addr}",
|
||||||
|
f"To: {to_addr}",
|
||||||
|
f"Subject: {subject}",
|
||||||
|
f"Message-ID: {uuid.uuid4()}@mail-check.py",
|
||||||
|
f"Date: {email.utils.formatdate()}",
|
||||||
|
"",
|
||||||
|
"This validates our mail server can send to Gmail :/",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
retry = RETRY
|
retry = RETRY
|
||||||
while True:
|
while True:
|
||||||
@ -43,7 +42,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
|
||||||
@ -61,16 +62,18 @@ 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 {imap_username}")
|
||||||
|
|
||||||
message = None
|
message = None
|
||||||
|
|
||||||
@ -80,49 +83,62 @@ 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()
|
||||||
typ, data = obj.search(None, '(SINCE %s) (SUBJECT "%s")'%(dt, subject))
|
_, data = obj.search(None, f'(SINCE {dt}) (SUBJECT "{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(
|
||||||
|
f"Warning: {len(uids)} messages have been found with subject containing {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()
|
||||||
message = email.message_from_bytes(raw[0][1])
|
assert raw[0] and raw[0][1]
|
||||||
print("Message with subject '%s' has been found" % message['subject'])
|
message = email.message_from_bytes(cast(bytes, raw[0][1]))
|
||||||
|
print(f"Message with subject '{message['subject']}' has been found")
|
||||||
if show_body:
|
if show_body:
|
||||||
for m in message.get_payload():
|
if message.is_multipart():
|
||||||
if m.get_content_type() == 'text/plain':
|
for part in message.walk():
|
||||||
print("Body:\n%s" % m.get_payload(decode=True).decode('utf-8'))
|
ctype = part.get_content_type()
|
||||||
|
if ctype == "text/plain":
|
||||||
|
body = cast(bytes, part.get_payload(decode=True)).decode()
|
||||||
|
print(f"Body:\n{body}")
|
||||||
|
else:
|
||||||
|
print(f"Body with content type {ctype} not printed")
|
||||||
|
else:
|
||||||
|
body = cast(bytes, message.get_payload(decode=True)).decode()
|
||||||
|
print(f"Body:\n{body}")
|
||||||
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(
|
||||||
|
f"Error: no message with subject '{subject}' has been found in INBOX of {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:")
|
||||||
@ -132,71 +148,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 = f"{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,
|
||||||
to_addr=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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user