Différences
Ci-dessous, les différences entre deux révisions de la page.
| Les deux révisions précédentesRévision précédenteProchaine révision | Révision précédente | ||
| pratique:informatique:parefeu:reaction [11/06/2025 10:37] – on continue les maj, mais c'est pas fini. Zatalyz | pratique:informatique:parefeu:reaction [16/06/2026 12:25] (Version actuelle) – Zatalyz | ||
|---|---|---|---|
| Ligne 7: | Ligne 7: | ||
| Et autres qualités que l' | Et autres qualités que l' | ||
| - | Faudra que je participe au wiki officiel mais en attendant je met mes idées en place ici. | + | Faudra que je participe au [[https:// |
| <WRAP center round tip 60%> | <WRAP center round tip 60%> | ||
| Ligne 51: | Ligne 51: | ||
| * '' | * '' | ||
| + | ==== Les plus fréquement utilisées ==== | ||
| + | |||
| + | Voir qui est banni : | ||
| + | |||
| + | reaction show | ||
| + | |||
| + | Débannir quelqu' | ||
| + | |||
| + | reaction flush IP | ||
| + | |||
| + | Tester le dossier de configuration : | ||
| + | reaction test-config -c / | ||
| + | |||
| + | Démarrer (sans systemd) : | ||
| + | reaction start -c / | ||
| + | |||
| + | ==== Base de donnée ==== | ||
| Par défaut les bases de données sont dans ''/ | Par défaut les bases de données sont dans ''/ | ||
| + | |||
| + | Si on lance avec la commande dans le terminal, ces deux fichiers sont là où on lance la commande (je crois, j'ai un doute... en tout cas j' | ||
| + | ===== Concepts ===== | ||
| + | * Patterns : mots-clés utilisés ensuite au sein des expressions régulières pour " | ||
| + | * Streams : équivalent des jails de fail2ban. On va par exemple déclarer " | ||
| + | * Filters : groupe d' | ||
| + | * Trigger : conditions, quand l' | ||
| + | * Actions : ce qui est exécuté quand le filter est déclenché. Par exemple, bannir. Le cœur du logiciel ! | ||
| + | * Start/Stop : commandes qui seront exécuté au démarrage et à l' | ||
| + | |||
| + | La syntaxe est en jsonnet, ce qui permet d' | ||
| ===== Installation et lancement automatique ===== | ===== Installation et lancement automatique ===== | ||
| - | Pour l' | + | Pour l' |
| < | < | ||
| - | wget https:// | + | wget https:// |
| - | | + | wget https:// |
| - | minisign -VP RWSpLTPfbvllNqRrXUgZzM7mFjLUA7PQioAItz80ag8uU4A2wtoT2DzX -m reaction_1.4.1-1_amd64.deb | + | minisign -VP RWSpLTPfbvllNqRrXUgZzM7mFjLUA7PQioAItz80ag8uU4A2wtoT2DzX -m reaction_1.4.1-1_amd64.deb |
| - | rm reaction_1.4.1-1_amd64.deb.minisig | + | rm reaction_1.4.1-1_amd64.deb.minisig |
| - | sudo apt install ./ | + | sudo apt install ./reaction_1.4.1-1_amd64.deb |
| + | rm reaction_1.4.1-1_amd64.deb | ||
| </ | </ | ||
| Ensuite on crée le ou les fichiers de config, et on les bidouillent. Voir plus bas la/les confs. | Ensuite on crée le ou les fichiers de config, et on les bidouillent. Voir plus bas la/les confs. | ||
| sudo mkdir / | sudo mkdir / | ||
| - | sudo nano / | + | sudo nano / |
| ==== Démarrage automatique ==== | ==== Démarrage automatique ==== | ||
| Ligne 79: | Ligne 108: | ||
| </ | </ | ||
| - | On va créer | + | Avec la version 2 il y a un à présent |
| - | sudo nano / | + | |
| + | Après installation et vérification, | ||
| + | |||
| + | < | ||
| + | sudo systemctl enable --now reaction@reaction | ||
| + | </ | ||
| + | |||
| + | Ce qui suit après le @ est le chemin vers la configuration dans /etc/ ; comme je lui fais lire le dossier, c'est donc juste " | ||
| + | |||
| + | <WRAP center round alert 100%> | ||
| + | <WRAP center round info 100%> | ||
| + | Je laisse la vieille doc "pour mémoire" | ||
| + | </ | ||
| + | |||
| + | Je vais créer deux services : un pour Reaction proprement dit, un pour avertir en cas de plantage. Vu que j'ai eu des plantages muets, je lui dis de se relancer si ça lui arrive((Je précise que ça date des premières versions du logiciel, qui a bien évolué depuis.)) et sinon, j'ai une alerte. | ||
| + | |||
| + | |||
| + | <code bash / | ||
| + | [Unit] | ||
| + | Description=Reaction to ban bad ip | ||
| + | After=network.target | ||
| + | # Alerte si ça " | ||
| + | OnFailure=reaction-alert.service | ||
| - | < | ||
| [Install] | [Install] | ||
| WantedBy=multi-user.target | WantedBy=multi-user.target | ||
| + | |||
| [Service] | [Service] | ||
| ExecStart=/ | ExecStart=/ | ||
| StateDirectory=reaction | StateDirectory=reaction | ||
| RuntimeDirectory=reaction | RuntimeDirectory=reaction | ||
| - | WorkingDirectory=/ | + | WorkingDirectory=/ |
| Restart=on-failure | Restart=on-failure | ||
| RestartSec=3 | RestartSec=3 | ||
| + | # Anti-flood de redémarrage en boucle | ||
| + | StartLimitIntervalSec=400 | ||
| + | StartLimitBurst=3 | ||
| </ | </ | ||
| - | Vu que j'ai eu des plantages muets, je lui dis de se relancer | + | <code bash / |
| + | [Unit] | ||
| + | Description=Envoie une alerte | ||
| + | |||
| + | [Service] | ||
| + | Type=oneshot | ||
| + | ExecStart=/ | ||
| + | </ | ||
| + | |||
| + | Et le script pour envoyer un mail (ça aurait pu être autre chose, mais j' | ||
| + | <code> | ||
| + | # | ||
| + | LOCKFILE="/ | ||
| + | # Délai en minutes, donc 3 h = 180 | ||
| + | DELAY=180 | ||
| + | |||
| + | # Si une alerte a déjà été envoyée il y a moins de $DELAY, on quitte | ||
| + | if [ -e " | ||
| + | logger -t reaction-alert " | ||
| + | exit 0 | ||
| + | fi | ||
| + | |||
| + | touch " | ||
| + | |||
| + | SUBJECT=" | ||
| + | TO=" | ||
| + | BODY=" | ||
| + | Arrêt à $(date). | ||
| + | " | ||
| + | |||
| + | echo " | ||
| + | logger -t reaction-alert " | ||
| + | |||
| + | </code> | ||
| On démarre le service : | On démarre le service : | ||
| < | < | ||
| sudo service reaction start</ | sudo service reaction start</ | ||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | |||
| ===== Configuration ===== | ===== Configuration ===== | ||
| Ligne 107: | Ligne 199: | ||
| * Rapport (journalier ?) sur les ip bannies (histoire de pouvoir suivre ce qui se passe et la charge) ? | * Rapport (journalier ?) sur les ip bannies (histoire de pouvoir suivre ce qui se passe et la charge) ? | ||
| + | Point côté syntaxe : dans les noms, on peut utiliser le tiret bas, mais pas de tiret classique, ça va faire des erreurs car jsonnet l' | ||
| ==== Découpage des fichiers ==== | ==== Découpage des fichiers ==== | ||
| Il s'agit de me simplifier un peu la lecture et la maintenance. Sur l' | Il s'agit de me simplifier un peu la lecture et la maintenance. Sur l' | ||
| Ligne 117: | Ligne 210: | ||
| === server.jsonnet === | === server.jsonnet === | ||
| Ici c'est assez classique, le seul point important étant que je tourne avec nftables. Il peut être utile de spécifier les ip à ne pas bannir avec Reaction, couplé avec un fichier [[pratique: | Ici c'est assez classique, le seul point important étant que je tourne avec nftables. Il peut être utile de spécifier les ip à ne pas bannir avec Reaction, couplé avec un fichier [[pratique: | ||
| + | |||
| + | Sur un pare-feu, l' | ||
| + | |||
| + | <code bash nftables.conf_ou_autre> | ||
| + | set reaction_v4 { | ||
| + | type ipv4_addr | ||
| + | flags interval | ||
| + | } | ||
| + | set reaction_v6 { | ||
| + | type ipv6_addr | ||
| + | flags interval | ||
| + | } | ||
| + | chain input_all { | ||
| + | # Par défaut, on rejette tout à moins de suivre une des règles ci-dessous. | ||
| + | type filter hook input priority 0; policy drop; | ||
| + | |||
| + | # Vérifie si l'IP est bloquée (du plus long au plus court) | ||
| + | # ... Durablement | ||
| + | ip saddr @blocklist_v4 drop | ||
| + | ip6 saddr @blocklist_v6 drop | ||
| + | # ... Par Reaction | ||
| + | ip saddr @reaction_v4 drop | ||
| + | ip6 saddr @reaction_v6 drop | ||
| + | # Ou dans la liste temporaire | ||
| + | ip saddr @tempblock_v4 drop | ||
| + | ip6 saddr @tempblock_v6 drop | ||
| + | # [...] | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | De ce fait, je ne crée pas les sets à la volée au démarrage de Reaction (l' | ||
| + | |||
| + | Les patterns des ip sont à présent dans le logiciel (depuis la version 2.0?) ce qui rend certaines déclarations plus rapides. Notons aussi l' | ||
| <code json server.jsonnet> | <code json server.jsonnet> | ||
| Ligne 122: | Ligne 248: | ||
| patterns: { | patterns: { | ||
| ip: { | ip: { | ||
| - | // Both IPv4 and IPv6, do not accept malformed IPs | + | // IPv4 et IPv6, et masque des ipv6. |
| - | | + | |
| + | ipv6mask: 64, | ||
| ignore: [ | ignore: [ | ||
| + | // Ne pas bannir les ip des sysadmins et sur le local | ||
| // Ne pas oublier la virgule après chaque ip, pour l' | // Ne pas oublier la virgule après chaque ip, pour l' | ||
| ' | ' | ||
| ':: | ':: | ||
| // Sous-réseau d'une box | // Sous-réseau d'une box | ||
| - | ' | + | ' |
| // Sous-réseau proxmox ? | // Sous-réseau proxmox ? | ||
| - | //' | + | //' |
| - | // Ip fixes de sysadmins | + | // Ip fixes de sysadmins |
| - | // ip de bastion | + | ], |
| + | }, | ||
| + | ipmask: { | ||
| + | | ||
| + | type: 'ip', | ||
| + | // ipv4 : 24 = tout le dernier bit, soit toute ip du type a.b.c.* (tout est dans le joker). | ||
| + | ipv4mask: 24, | ||
| + | // ipv6 : 48 = ban des datacenters. 56 est un peu moins excessif. 64 est une norme de particulier. | ||
| + | ipv6mask: 56, | ||
| + | ignore: [ | ||
| + | ' | ||
| + | ':: | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| ], | ], | ||
| }, | }, | ||
| }, | }, | ||
| - | | + | // nftables est directement paramétré avec des tables/sets pour Reaction, histoire que ce soit au bon endroit dans la chaine. |
| - | [' | + | |
| - | table inet reaction { | + | |
| - | set ipv4bans { | + | |
| - | type ipv4_addr | + | |
| - | flags interval | + | |
| - | auto-merge | + | |
| - | } | + | |
| - | set ipv6bans { | + | |
| - | type ipv6_addr | + | |
| - | flags interval | + | |
| - | auto-merge | + | |
| - | } | + | |
| - | chain input { | + | |
| - | type filter hook input priority 0 | + | |
| - | policy accept | + | |
| - | ip saddr @ipv4bans drop | + | |
| - | ip6 saddr @ipv6bans drop | + | |
| - | } | + | |
| - | } | + | |
| - | ||| ], | + | |
| - | | + | |
| stop: [ | stop: [ | ||
| - | [' | + | [' |
| + | [' | ||
| ], | ], | ||
| } | } | ||
| Ligne 172: | Ligne 296: | ||
| On peut ainsi définir des actions par défaut : combien de temps on bannit, combien de lignes dans les logs avant de bannir, etc. Cela permet par exemple de définir pour tout le monde la même durée de bannissement et de la changer à un seul endroit si besoin. | On peut ainsi définir des actions par défaut : combien de temps on bannit, combien de lignes dans les logs avant de bannir, etc. Cela permet par exemple de définir pour tout le monde la même durée de bannissement et de la changer à un seul endroit si besoin. | ||
| - | Le fait que le fichier commence par " | + | Le fait que le fichier commence par " |
| + | |||
| + | Mon " | ||
| + | |||
| + | Je n' | ||
| <code json _lib.jsonnet> | <code json _lib.jsonnet> | ||
| - | local banFor(time) = { | + | local banFor(time, name) = { |
| - | ban: { | + | |
| - | cmd: ['nft46', 'add element inet reaction ipvXbans | + | cmd: ['nft', 'add', 'element', 'inet', ' |
| - | }, | + | |
| - | unban: { | + | |
| - | cmd: ['nft46', ' | + | |
| - | after: time, | + | cmd: [' |
| - | }, | + | ipv6only: true, |
| + | | ||
| + | unban_v4: { | ||
| + | after: time, | ||
| + | | ||
| + | ipv4only: true, | ||
| + | | ||
| + | unban_v6: { | ||
| + | | ||
| + | cmd: [' | ||
| + | ipv6only: true, | ||
| + | }, | ||
| + | log: { | ||
| + | cmd: ['/ | ||
| + | oneshot: true, | ||
| + | | ||
| }; | }; | ||
| + | // retry et retryperiod sont quand il y a plusieurs tentatives autorisées | ||
| + | // juste mettre le banFor sinon... Le ban sera alors à la première tentative. | ||
| + | |||
| + | // Filtre (et options) par défaut : ni trop doux, ni trop cruel. | ||
| local filter_default = { | local filter_default = { | ||
| retry: 3, | retry: 3, | ||
| - | retryperiod: | + | retryperiod: |
| - | actions: banFor(' | + | actions: banFor(' |
| }; | }; | ||
| + | // Filtre doux : c'est peut-être légitime. Et peut-être pas. Ça délaye les attaques. | ||
| + | local filter_soft = { | ||
| + | retry: 6, | ||
| + | retryperiod: | ||
| + | actions: banFor(' | ||
| + | }; | ||
| + | |||
| + | // Filtre violent : un seul essai, banni un mois. | ||
| + | local filter_hard = { | ||
| + | actions: banFor(' | ||
| + | }; | ||
| + | |||
| + | |||
| + | // Exposer les définitions précédentes pour qu' | ||
| { | { | ||
| banFor: banFor, | banFor: banFor, | ||
| filter_default: | filter_default: | ||
| + | filter_soft: | ||
| + | filter_hard: | ||
| } | } | ||
| + | |||
| </ | </ | ||
| + | == Script bash de log, notif, etc == | ||
| + | Un script très basique pour " | ||
| + | * les ips bloquées en longue durée (celles qui reviennent plusieurs fois via les filtres doux et par défaut : 6 fois c'est peut-être une erreur, 12 c'est de la maladresse, 24 c'est de l' | ||
| + | * les ips en bloc (ipmask). Ce n'est pas du tout paramétré pour le moment ! | ||
| + | Le format actuel de mon log n'est sans doute pas parfait pour ça, mais s' | ||
| + | <code bash _ban.sh> | ||
| + | #!/bin/bash | ||
| + | # Envoi d'un mail ou notif ou ce qu'on veut | ||
| + | # Log des ip | ||
| + | echo "$1 banni car $2 // Date : $(date)" | ||
| + | </ | ||
| === Streams === | === Streams === | ||
| - | Je met ici une config très basique pour ssh (<wrap todo> | + | Je met ici une config très basique pour ssh (voir [[https:// |
| + | |||
| + | <WRAP center round tip 100%> | ||
| + | [[https:// | ||
| + | </ | ||
| <code json ssh.jsonnet> | <code json ssh.jsonnet> | ||
| Ligne 210: | Ligne 389: | ||
| cmd: [' | cmd: [' | ||
| filters: { | filters: { | ||
| - | | + | |
| + | regex: [ | ||
| + | @'User root from <ip> not allowed because not listed in AllowUsers', | ||
| + | ], | ||
| + | // ce filtre est peu pertinent avec sshguard et va le ralentir dans sa lutte contre les méchants. | ||
| + | failedlogin: | ||
| regex: [ | regex: [ | ||
| @' | @' | ||
| Ligne 218: | Ligne 402: | ||
| retry: 3, | retry: 3, | ||
| retryperiod: | retryperiod: | ||
| - | actions: lib.banFor(' | + | actions: lib.banFor(' |
| }, | }, | ||
| }, | }, | ||
| Ligne 230: | Ligne 414: | ||
| * '' | * '' | ||
| + | Concernant l' | ||
| - | <WRAP center round todo 100%> | + | <code> |
| - | Ce qui est ici date des premières versions de reaction, on va voir ce qu'on garde. | + | [...] |
| + | apache_auth: | ||
| + | regex: [ | ||
| + | @' | ||
| + | ], | ||
| + | actions: lib.filter_default.actions + lib.stream_name('apache_auth' | ||
| + | }, | ||
| + | [...] | ||
| + | </ | ||
| - | ==== Envoyer | + | Ici '' |
| - | J' | + | ==== Réaliser des actions plus compliqués ==== |
| - | < | + | <WRAP center round tip 60%> |
| + | Ce qui suit est plus pour retrouver comment faire au besoin, mais la configuration précédente (log, sans mail en plus, intégré à banFor) me semble suffisante en général. | ||
| + | </ | ||
| + | |||
| + | Lorsqu' | ||
| + | |||
| + | Bon, en vrai, ça dépend. Sur certains services, la surveillance via des mails, au début, permet d' | ||
| + | |||
| + | Bref, tout ça va se faire via notre fichier _lib.jsonnet, | ||
| + | |||
| + | === Lancer un script | ||
| + | Si on veut bannir ET réaliser | ||
| + | |||
| + | <code bash _ban.sh> | ||
| + | # | ||
| + | # Bannissement | ||
| + | nft46 add element inet reaction ipvXbans { " | ||
| + | |||
| + | # Envoi d'un mail | ||
| + | # cf le script plus bas... | ||
| + | </ | ||
| + | |||
| + | On modifie alors _lib.jsonnet sur la partie cmd : | ||
| + | <code json _lib.jsonnet> | ||
| + | local banFor(time) = { | ||
| + | ban: { | ||
| + | cmd: [' | ||
| + | } | ||
| + | </ | ||
| + | devient | ||
| + | <code json _lib.jsonnet> | ||
| + | local banFor(time) = { | ||
| + | ban: { | ||
| + | cmd: [' | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Ce qui autorise toutes les fantaisies. | ||
| + | |||
| + | === Action : envoi de mail === | ||
| + | On peut aussi déclarer une action spéciale pour l' | ||
| + | |||
| + | Le script suivant prend deux variables : '' | ||
| + | |||
| + | < | ||
| mail: { | mail: { | ||
| - | cmd: [' | + | cmd: [' |
| }, | }, | ||
| }; | }; | ||
| - | |||
| </ | </ | ||
| Ligne 247: | Ligne 483: | ||
| < | < | ||
| # Envoyer un mail | # Envoyer un mail | ||
| - | dossiermail="/ | + | dossiermail="/ |
| titremail=" | titremail=" | ||
| # Création du fichier du corps du mail dans un fichier temporaire qui évite toute collision | # Création du fichier du corps du mail dans un fichier temporaire qui évite toute collision | ||
| Ligne 267: | Ligne 503: | ||
| fi</ | fi</ | ||
| - | Penser évidement à créer | + | Penser évidement à créer |
| - | Plus loin dans le fichier de configuration de Reaction, dans les streams, comment dire qu'il faut envoyer un mail (exemple sur ssh) : | + | Dans les streams |
| < | < | ||
| ssh: { | ssh: { | ||
| Ligne 282: | Ligne 518: | ||
| retry: 6, | retry: 6, | ||
| retryperiod: | retryperiod: | ||
| - | actions: banFor(' | + | actions: |
| }, | }, | ||
| }, | }, | ||
| Ligne 288: | Ligne 524: | ||
| </ | </ | ||
| + | ===== Transformer les règles de fail2ban ===== | ||
| + | Ça c'est du chantier... | ||
| - | ==== Mes bouts de stream et de config ==== | + | Quand on a fail2ban installé, retrouver les regex est un sacré bazar. Il n'y a pas de solution " |
| - | Fichier principal | + | |
| - | <code jsonnet server.jsonnet> | + | |
| - | // Envoyer | + | fail2ban-client -d |
| - | local sendmail(ip, | + | Celle-ci va lister toutes les règles actives, dans un format permettant de voir quel fichier de log est surveillé |
| - | mail: { | + | |
| - | cmd: ['sh', | + | |
| - | }, | + | |
| - | }; | + | |
| - | // pourquoi ça ouvre ici ? Mais, ça marche. | + | fail2ban-client get JAIL failregex |
| - | { | + | Remplacer " |
| - | // patterns are substitued in regexes. when a filter performs an action, it replaces the found pattern. | + | |
| - | // reaction | + | |
| - | // jsonnet's @'string' | + | |
| - | patterns: { | + | |
| - | // IPs can be IPv4 or IPv6 | + | |
| - | // ip46tables (C program also in this repo) handles running the good commands | + | |
| - | | + | |
| - | regex: @'(([0-9]{1, | + | |
| - | ignore: ['127.0.0.1', ':: | + | |
| - | }, | + | |
| - | }, | + | |
| - | // Commandes exécutées au lancement | + | Jails que je tente d' |
| - | | + | |
| - | | + | |
| - | | + | |
| - | | + | * '' |
| - | ], | + | |
| - | // Commandes exécutées à l' | + | * '' |
| - | stop: [ | + | |
| - | | + | * '' |
| - | | + | * Apache access.log |
| - | [' | + | |
| - | | + | * Notons cependant qu'on a des badbots repérables aux user-agents et que les ban peut réduire le trafic simplement. |
| - | ], | + | |
| - | // Streams : c'est là qu'on va définir les services | + | Autres |
| - | streams: { | + | |
| - | ssh: import ' | + | |
| - | kernel: import ' | + | |
| - | badguypostfix: | + | |
| - | }, | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Pour ssh et kernel, il s'agit des configurations par défaut auxquelles j'ai ajouté mon envoi de mail : | + | |
| - | <code jsonnet ssh.jsonnet> | + | |
| - | { | + | |
| - | cmd: [' | + | |
| - | filters: { | + | |
| - | failedlogin: | + | |
| - | regex: [ | + | |
| - | @' | + | |
| - | @' | + | |
| - | @' | + | |
| - | ], | + | |
| - | retry: 6, | + | |
| - | retryperiod: | + | |
| - | actions: banFor(' | + | |
| - | }, | + | |
| - | }, | + | |
| - | }, | + | |
| - | + | ||
| - | </ | + | |
| - | + | ||
| - | <code jsonnet kernel.jsonnet> | + | |
| - | // Ban hosts which knock on closed ports. | + | |
| - | // It needs this iptables chain to be used to drop packets: | + | |
| - | // ip46tables -N log-refuse | + | |
| - | // ip46tables -A log-refuse -p tcp --syn -j LOG --log-level info --log-prefix ' | + | |
| - | // ip46tables -A log-refuse -m pkttype ! --pkt-type unicast -j nixos-fw-refuse | + | |
| - | // ip46tables -A log-refuse -j DROP | + | |
| - | { | + | |
| - | cmd: [' | + | |
| - | filters: { | + | |
| - | portscan: { | + | |
| - | regex: [' | + | |
| - | retry: 4, | + | |
| - | retryperiod: | + | |
| - | actions: banFor(' | + | |
| - | }, | + | |
| - | }, | + | |
| - | }, | + | |
| - | + | ||
| - | + | ||
| - | </ | + | |
| - | + | ||
| - | Pour postfix, pour le moment je cible certains bot cons. | + | |
| - | <code jsonnet badguypostfix.jsonnet> | + | |
| - | { | + | |
| - | cmd: [' | + | |
| - | filters: { | + | |
| - | badguy: { | + | |
| - | regex: [ | + | |
| - | @'^.* improper command pipelining after CONNECT from unknown\[< | + | |
| - | @' | + | |
| - | @'^.* NOQUEUE: reject: RCPT from unknown\[< | + | |
| - | @' | + | |
| - | @' | + | |
| - | @' | + | |
| - | @' | + | |
| - | + | ||
| - | ], | + | |
| - | retry: 1, | + | |
| - | retryperiod: | + | |
| - | actions: banFor(' | + | |
| - | }, | + | |
| - | }, | + | |
| - | }, | + | |
| - | </ | + | |
| - | </ | + | |
| + | * postfix | ||
| + | * dovecot | ||
| + | * sieve | ||
| + | * sshd | ||
| - | {{tag> | + | {{tag> |
| [[https:// | [[https:// | ||