A set of hMailServer event handlers written in JScript. It filters inbound connections and auto-bans abusive hosts (geographic filtering, AbuseIPDB reputation, failed-login, recipient-harvesting and connection-flood protection), cleans up a few outbound headers, and writes a single, column-aligned log file per day.
- Country blocking on every port (SMTP, IMAP, POP).
- Geographic restriction on non-SMTP ports: mailbox access (IMAP/POP) is allowed only from a configurable list of countries. When the country can't be read (DNS error or IPv6 address), the connection gets a temporary refusal, without a ban.
- AbuseIPDB reputation check on the submission ports (587/465).
- Auto-ban of unauthenticated / failed logins.
- Recipient-probe (directory harvesting) protection: bans an IP after a number of unknown recipients within a sliding window. Uses a small counter file, no database. An unauthenticated sender that claims one of your own domains is banned at the first unknown recipient.
- Connection-flood auto-ban: bans an IP that opens too many connections within a
sliding window on the SMTP ports (25/465/587 by default). Catches brute-force bursts that never complete a login (for
example repeated pre-STARTTLS AUTH attempts answered with
504), which do not trigger the failed-login handler. Uses a small counter file, no database. - Outbound
Receivedheader anonymisation (removes the client IP). - Fills a missing
Message-Idand reformatsX-Spam-Reportso it is readable. - Optional SMTP rejection of messages whose spam score is above a threshold. It
reads the score your anti-spam layer already added (for example SpamAssassin's
X-Spam-Score, or thescore=field ofX-Spam-Status). - The admin password and the AbuseIPDB key live in a separate secrets file, not in the script.
- Daily log with fixed-width, aligned columns, UTF-8 without BOM. One line per event.
Each filtering feature can be enabled or disabled, and set to ban (a temporary IP range in hMailServer, so a banned host is dropped before it reaches the script again) or reject (refuse only the current session with a professional SMTP message, without a persistent ban).
-
hMailServer with scripting set to JScript. Tested with the official 5.7.1 release, build 3116: https://github.com/hmailserver/hmailserver/releases
-
Disconnect.exe to drop the current session of a banned host. Copy RvdH's
Disconnect.exeto the hMailServerEventsfolder: https://d-fault.nl/files/ -
For the country lookup and AbuseIPDB checks, two COM components must be registered on the server:
DNSLibrary.DNSResolver(country lookup)AbuseIPDBComponent.AbuseIPDBRestClient(AbuseIPDB)
These are part of RvdH's community tools (see the d-fault.nl link above). If a component is not present, the matching check is skipped, the connection is allowed, and the error is written to the log. The rest keeps working.
-
An AbuseIPDB API key, if you use the AbuseIPDB check.
Country lookup uses the public reverse-DNS service
country.junkemailfilter.com.
- In hMailServer Administrator, go to Settings → Advanced → Scripts, set the language to JScript and enable scripting.
- Copy
EventHandlers.jsinto theEventsfolder (defaultC:\Program Files\hMailServer\Events). The file must keep that exact name. - Copy
Disconnect.exeinto the sameEventsfolder. - Create
EventHandlers.secrets.ini(see below). - Open
EventHandlers.jsand edit the settings at the top of the file (see below). - Back in the Administrator, click Save, then Reload script.
The admin password and the AbuseIPDB API key are not stored in the script. The
script reads them from a plain-text file whose path is set in SECRETS_FILE.
Create it next to EventHandlers.js with these two lines and put in your own
values:
ADMIN_PASSWORD = YOUR_ADMIN_PASSWORD
ABUSEIPDB_KEY = YOUR_ABUSEIPDB_API_KEY- One
KEY = valueper line. Spaces around=and at the end of the value are ignored. Don't put quotes around the value: they would be kept as part of it. - Key names are case-sensitive. Lines that don't start with a key, such as
;or#comments, are ignored. - The file is read once, the first time a secret is needed. Click Reload script after you change it.
- If the file can't be read, or a key is missing, the script writes the error to
the log and goes on: without
ADMIN_PASSWORDno ban is created, withoutABUSEIPDB_KEYthe AbuseIPDB check is skipped and the connection is allowed. - Limit access to this file to the account that runs hMailServer and to
administrators, and keep it out of any backup or repository you share. The
.gitignoreof this repository already excludes it.
All settings sit at the top of the file:
ADMIN: admin user name used to create the bans. Its password goes in the secrets file.LOGDIR,LOGPREFIX,DISCONNECT_EXE,SECRETS_FILE: log folder, log file prefix, path toDisconnect.exeand path toEventHandlers.secrets.ini. Use full paths: a relative path is resolved against the current folder of the hMailServer service (usuallyC:\Windows\System32), not theEventsfolder. Spaces are fine, but double every backslash, such as"C:\\Program Files (x86)\\hMailServer\\Events\\EventHandlers.secrets.ini". The script createsLOGDIRif it's missing, but only its last folder: the parent has to exist.LOCAL_DOMAINS: the domains hosted on this server, lowercase and pipe-delimited, such as"|example.com|example.org|". Used to spot a sender that fakes one of them.- One object per filtering feature (
GEOBLOCK,GEORESTRICT,ABUSEIPDB,UNKNOWNUSER,RCPTPROBE,CONNFLOOD,SPAMREJECT), each with:enabled: turn the feature on or off.action:"ban"(temporary IP-range ban + disconnect) or"reject"(refuse only the current session with a professional SMTP message, no ban).ban:{ qty, unit }duration (unitd/h/n/s), used in"ban"mode;qty0disconnects without banning.msg: the SMTP message returned in"reject"mode.- plus feature-specific fields:
ABUSEIPDBhasmaxConfidence,maxAgeDays;RCPTPROBEhasthreshold,windowMinandspoofLocal(ban at the first unknown recipient when the sender claims a domain fromLOCAL_DOMAINS);CONNFLOODhasthreshold,windowMinandports(the only ports counted);GEORESTRICThasfailMsg(temporary refusal when the country can't be read);SPAMREJECThasscore,header.SPAMREJECTis on by default with a score of 20: turn it off if no anti-spam layer adds a score header.
RECEIVEDANON_ENABLED,MESSAGEID_ENABLED,SPAMREPORT_ENABLED: on/off flags for the header-transform features.BLOCKED_COUNTRIES,ALLOWED_GEO,SMTP_PORTS,SUBMISSION_PORTS: pipe-delimited lists such as"|cn|cz|ru|".BAN_PRIORITY,LOG_SOURCES, local network exemption (LOCAL_IP_PREFIX,LOCALHOST_IP,LOCALHOST_IP6), and the sliding-window counter files: recipient-probe (RCPT_DATA_FILE,RCPT_LOCK_FILE) and connection-flood (CONN_DATA_FILE,CONN_LOCK_FILE), plusBAN_LOCK_FILE,LOCK_TRIES,LOCK_WAIT_MS(pause between two lock attempts),LOCK_STALE_SEC.LOCAL_PUBLIC_IPS: your own public IP, pipe-delimited ("|203.0.113.7|"). A device on your LAN that reaches the server by its public name goes through the router's NAT loopback, which replaces the device's address with this one, so every device at home arrives under the same IP. Without it, one phone sending a few mails in a row can get the whole house banned for 7 days. The default,"|auto|", asksPUBLIC_IP_URL(https://api.ipify.org) for that IP and keeps the answerPUBLIC_IP_TTL_MINminutes (60) inPUBLIC_IP_FILE, so it follows a dynamic IP. If the service is down, the last known IP stays exempt and the script tries again after the same delay. With a fixed IP, write it instead ofautoand the script makes no outside call.RANGE_NAME_MAX(100) andLOG_DETAIL_MAX(300): longest IP-range name and longest free-text detail in a log line. Longer text is cut.
Connection-flood tuning.
CONNFLOOD.thresholdconnections withinCONNFLOOD.windowMinminutes trigger a ban (defaults: 5 in 1 minute, banned for 7 days). Only the ports inCONNFLOOD.portsare counted. The LAN, localhost andLOCAL_PUBLIC_IPSare exempt. Lower the threshold to be more aggressive; raise it if a legitimate high-volume relay gets banned. This check runs on every connection to those ports, so it adds one small file access (under a lock) per connect.
- Local network, localhost and
LOCAL_PUBLIC_IPSare skipped. - Connection flood: an IP over the connection threshold on
CONNFLOOD.portsis banned. - Blocked country: rejected on any port.
- Non-SMTP port from a country outside the allowed list: rejected. If the
country can't be read (code
xx), the connection gets a temporary refusal and no ban. - Submission port with an IP listed on AbuseIPDB: rejected.
The log is written to LOGDIR\<prefix>_YYYYMMDD.log, one file per day, in fixed
columns:
2026-07-04 00:39:59 AutoBan 111.18.196.17 cn +7d Bad Country 143
2026-07-04 05:28:56 AutoBan 85.121.183.244 ro +1d Unknown USER 587
2026-07-04 17:39:24 AutoBan 20.12.240.184 us +1d AbuseIPDB 587
2026-07-09 11:47:15 AutoBan 193.138.195.94 zz +1d Conn flood 587
Columns: date and time, source, IP, country, ban duration, detail. Fields are
separated by two or more spaces, so a log line can be split on / {2,}/. The
source column is one of AutoBan, Disconnect, Reject, Spam, AbuseIPDB,
GeoLookup, Debug, System.
- Geographic restriction on non-SMTP ports can also lock out a legitimate user
who reads mail over IMAP/POP from abroad. Keep the ban short, set
GEORESTRICT.ban.qtyto0to only disconnect, orGEORESTRICT.actionto"reject"to avoid a persistent ban. - Connection-flood protection counts every connection on
CONNFLOOD.ports, so a legitimate relay that opens many short-lived connections could be banned. RaiseCONNFLOOD.thresholdor widenCONNFLOOD.windowMinif that happens; setCONNFLOOD.enabledtofalseto turn it off. IMAP and POP are not counted by default: a mail client often opens several connections at once and would be banned for nothing. xxin the country column of the log means the country couldn't be read: the DNS lookup failed or timed out, or the address is IPv6. On non-SMTP ports such a connection is refused for now and can retry. To let it through instead, addxxtoALLOWED_GEO.zzmeans the lookup worked but the service doesn't know the IP.- Bans are stored as IP ranges and are removed automatically when they expire. Long ban durations on high-volume sources will accumulate more ranges.
- Two simultaneous connections from the same host can both try to create the same
ban; the duplicate is handled safely and logged once at
Debuglevel, not as an error. - Spam-score rejection is on by default and needs an anti-spam layer that adds a score header before the message is accepted (SpamAssassin does this). Without that header the check never triggers. If you also use hMailServer's built-in spam delete threshold, keep only one of the two: the built-in threshold deletes silently, while the script returns a 550 to the sender.
- The script is written for the classic JScript engine used by hMailServer.
oMessage.HeaderValue("X") = valueis the correct way to set a header there and matches hMailServer's own tests; it is not standard browser JavaScript.
MIT. See LICENSE.
Ensemble de gestionnaires d'évènements hMailServer écrits en JScript. Il filtre les connexions entrantes et bannit automatiquement les hôtes abusifs (filtrage géographique, réputation AbuseIPDB, protection contre les échecs d'authentification, la collecte d'adresses et les floods de connexions), nettoie quelques en-têtes sortants et écrit un seul fichier de log par jour, en colonnes alignées.
- Blocage par pays sur tous les ports (SMTP, IMAP, POP).
- Restriction géographique sur les ports non-SMTP : l'accès aux boîtes (IMAP/POP) n'est autorisé que depuis une liste de pays configurable. Quand le pays ne peut pas être lu (erreur DNS ou adresse IPv6), la connexion reçoit un refus temporaire, sans ban.
- Contrôle de réputation AbuseIPDB sur les ports de soumission (587/465).
- Bannissement automatique des connexions non authentifiées.
- Protection contre les sondes de destinataires (collecte d'adresses) : bannit une IP après un certain nombre de destinataires inconnus dans une fenêtre glissante. Utilise un petit fichier compteur, sans base de données. Un expéditeur non authentifié qui se réclame d'un de vos propres domaines est banni dès le premier destinataire inconnu.
- Bannissement automatique des floods de connexions : bannit une IP qui ouvre
trop de connexions dans une fenêtre glissante, sur les ports SMTP (25/465/587
par défaut). Attrape les rafales de force
brute qui n'aboutissent jamais à une authentification (par exemple les
tentatives AUTH répétées avant STARTTLS, auxquelles le serveur répond
504), lesquelles ne déclenchent pas le gestionnaire d'échec d'authentification. Utilise un petit fichier compteur, sans base de données. - Anonymisation de l'en-tête
Receivedsortant (retire l'IP du client). - Complète un
Message-Idmanquant et remet en formeX-Spam-Reportpour le rendre lisible. - Rejet SMTP optionnel des messages dont le score de spam dépasse un seuil. Il
lit le score déjà ajouté par votre couche anti-spam (par exemple le
X-Spam-Scorede SpamAssassin, ou le champscore=deX-Spam-Status). - Le mot de passe d'administration et la clé AbuseIPDB sont rangés dans un fichier de secrets à part, pas dans le script.
- Log quotidien en colonnes de largeur fixe, UTF-8 sans BOM. Une ligne par évènement.
Vous pouvez activer ou désactiver chaque fonction de filtrage, et la régler sur ban (plage d'IP temporaire dans hMailServer : l'hôte banni est coupé avant même d'atteindre le script à sa tentative suivante) ou reject (refus de la seule session courante avec un message SMTP professionnel, sans ban persistant).
-
hMailServer avec le langage de script réglé sur JScript. Testé avec la version officielle 5.7.1, build 3116 : https://github.com/hmailserver/hmailserver/releases
-
Disconnect.exe pour couper la session en cours d'un hôte banni. Copiez le
Disconnect.exede RvdH dans le dossierEventsde hMailServer : https://d-fault.nl/files/ -
Pour la résolution de pays et le contrôle AbuseIPDB, il faut enregistrer deux composants COM sur le serveur :
DNSLibrary.DNSResolver(résolution de pays)AbuseIPDBComponent.AbuseIPDBRestClient(AbuseIPDB)
Ils font partie des outils communautaires de RvdH (voir le lien d-fault.nl ci-dessus). Si un composant est absent, le contrôle correspondant est ignoré, la connexion est acceptée et l'erreur est écrite dans le log. Le reste continue de fonctionner.
-
Une clé d'API AbuseIPDB, si vous utilisez le contrôle AbuseIPDB.
La résolution de pays utilise le service DNS inversé public
country.junkemailfilter.com.
- Dans hMailServer Administrator, allez dans Settings → Advanced → Scripts, réglez le langage sur JScript et activez les scripts.
- Copiez
EventHandlers.jsdans le dossierEvents(par défautC:\Program Files\hMailServer\Events). Le fichier doit garder ce nom exact. - Copiez
Disconnect.exedans ce même dossierEvents. - Créez
EventHandlers.secrets.ini(voir ci-dessous). - Ouvrez
EventHandlers.jset modifiez les paramètres en tête de fichier (voir ci-dessous). - De retour dans l'Administrator, cliquez sur Save, puis sur Reload script.
Le mot de passe d'administration et la clé d'API AbuseIPDB ne sont pas écrits
dans le script. Il les lit dans un fichier texte dont SECRETS_FILE donne le
chemin. Créez-le à côté de EventHandlers.js avec ces deux lignes, en
mettant vos propres valeurs :
ADMIN_PASSWORD = YOUR_ADMIN_PASSWORD
ABUSEIPDB_KEY = YOUR_ABUSEIPDB_API_KEY- Une ligne
CLE = valeurpar réglage. Les espaces autour du=et en fin de valeur sont ignorés. Pas de guillemets autour de la valeur : ils en feraient partie. - Les noms de clés respectent la casse. Les lignes qui ne commencent pas par une
clé, comme les commentaires
;ou#, sont ignorées. - Le fichier est lu une fois, au premier besoin d'un secret. Cliquez sur Reload script après l'avoir modifié.
- Si le fichier est illisible ou qu'une clé manque, le script écrit l'erreur dans
le log et continue : sans
ADMIN_PASSWORD, aucun ban n'est créé ; sansABUSEIPDB_KEY, le contrôle AbuseIPDB est sauté et la connexion acceptée. - Réservez l'accès à ce fichier au compte qui fait tourner hMailServer et aux
administrateurs, et gardez-le hors de toute sauvegarde ou de tout dépôt
partagé. Le
.gitignorede ce dépôt l'exclut déjà.
Tous les paramètres se trouvent en tête de fichier :
ADMIN: nom de l'administrateur servant à créer les bans. Son mot de passe va dans le fichier de secrets.LOGDIR,LOGPREFIX,DISCONNECT_EXE,SECRETS_FILE: dossier des logs, préfixe du fichier, chemin versDisconnect.exeet chemin versEventHandlers.secrets.ini. Mettez des chemins complets : un chemin relatif part du dossier courant du service hMailServer (en généralC:\Windows\System32), pas du dossierEvents. Les espaces ne posent pas de problème, mais chaque antislash se double, par exemple"C:\\Program Files (x86)\\hMailServer\\Events\\EventHandlers.secrets.ini". Le script créeLOGDIRs'il manque, mais seulement son dernier dossier : le dossier parent doit exister.LOCAL_DOMAINS: les domaines hébergés par ce serveur, en minuscules et délimités par des barres, par exemple"|example.com|example.org|". Sert à repérer un expéditeur qui en usurpe un.- Un objet par fonction de filtrage (
GEOBLOCK,GEORESTRICT,ABUSEIPDB,UNKNOWNUSER,RCPTPROBE,CONNFLOOD,SPAMREJECT), chacun avec :enabled: active ou désactive la fonction.action:"ban"(ban temporaire par plage d'IP + déconnexion) ou"reject"(refus de la seule session courante avec un message SMTP professionnel, sans ban).ban: durée{ qty, unit }(unitéd/h/n/s), utilisée en mode"ban";qtyà0déconnecte sans bannir.msg: le message SMTP renvoyé en mode"reject".- plus des champs propres à la fonction :
ABUSEIPDBamaxConfidence,maxAgeDays;RCPTPROBEathreshold,windowMinetspoofLocal(ban dès le premier destinataire inconnu si l'expéditeur annonce un domaine deLOCAL_DOMAINS) ;CONNFLOODathreshold,windowMinetports(les seuls ports comptés) ;GEORESTRICTafailMsg(refus temporaire quand le pays ne peut pas être lu) ;SPAMREJECTascore,header.SPAMREJECTfonctionne par défaut, avec un score de 20 : désactivez-le si aucune couche anti-spam n'ajoute d'en-tête de score.
RECEIVEDANON_ENABLED,MESSAGEID_ENABLED,SPAMREPORT_ENABLED: drapeaux d'activation des fonctions de transformation d'en-têtes.BLOCKED_COUNTRIES,ALLOWED_GEO,SMTP_PORTS,SUBMISSION_PORTS: listes délimitées par des barres, par exemple"|cn|cz|ru|".BAN_PRIORITY,LOG_SOURCES, exemption du réseau local (LOCAL_IP_PREFIX,LOCALHOST_IP,LOCALHOST_IP6) et les fichiers des compteurs à fenêtre glissante : sondes de destinataires (RCPT_DATA_FILE,RCPT_LOCK_FILE) et flood de connexions (CONN_DATA_FILE,CONN_LOCK_FILE), plusBAN_LOCK_FILE,LOCK_TRIES,LOCK_WAIT_MS(pause entre deux essais de verrou),LOCK_STALE_SEC.LOCAL_PUBLIC_IPS: votre IP publique, entre barres ("|203.0.113.7|"). Quand un appareil du LAN joint le serveur par son nom public, le routeur (NAT loopback) remplace l'adresse de l'appareil par celle-ci : tous les appareils de la maison arrivent sous cette adresse. Sans ce réglage, un téléphone qui envoie quelques mails à la suite peut faire bannir toute la maison pour 7 jours. Par défaut,"|auto|"demande cette IP àPUBLIC_IP_URL(https://api.ipify.org) et garde la réponsePUBLIC_IP_TTL_MINminutes (60) dansPUBLIC_IP_FILE: une IP dynamique est suivie. Si le service ne répond pas, la dernière IP connue reste exemptée et le script réessaie après le même délai. Avec une IP fixe, écrivez-la à la place deauto: le script ne fait alors aucun appel extérieur.RANGE_NAME_MAX(100) etLOG_DETAIL_MAX(300) : longueur maximale d'un nom de plage d'IP et du détail libre d'une ligne de log. Le texte plus long est coupé.
Réglage du flood de connexions.
CONNFLOOD.thresholdconnexions dans une fenêtre deCONNFLOOD.windowMinminutes déclenchent un ban (par défaut : 5 en 1 minute, ban de 7 jours). Seuls les ports deCONNFLOOD.portssont comptés. Le LAN, localhost etLOCAL_PUBLIC_IPSsont exemptés. Baissez le seuil pour être plus agressif ; augmentez-le si un relais légitime à fort volume est banni. Ce contrôle s'exécute à chaque connexion sur ces ports : il ajoute un petit accès fichier (sous verrou) par connexion.
- Réseau local, localhost et
LOCAL_PUBLIC_IPSsont ignorés. - Flood de connexions : une IP dépassant le seuil de connexions sur
CONNFLOOD.portsest bannie. - Pays bloqué : rejet sur tous les ports.
- Port non-SMTP depuis un pays hors liste autorisée : rejet. Si le pays ne
peut pas être lu (code
xx), la connexion reçoit un refus temporaire, sans ban. - Port de soumission avec une IP listée sur AbuseIPDB : rejet.
Le log est écrit dans LOGDIR\<préfixe>_AAAAMMJJ.log, un fichier par jour, en
colonnes fixes :
2026-07-04 00:39:59 AutoBan 111.18.196.17 cn +7d Bad Country 143
2026-07-04 05:28:56 AutoBan 85.121.183.244 ro +1d Unknown USER 587
2026-07-04 17:39:24 AutoBan 20.12.240.184 us +1d AbuseIPDB 587
2026-07-09 11:47:15 AutoBan 193.138.195.94 zz +1d Conn flood 587
Colonnes : date et heure, source, IP, pays, durée de ban, détail. Au moins
deux espaces séparent les champs, on peut donc découper une ligne sur
/ {2,}/. La colonne source vaut AutoBan, Disconnect, Reject, Spam,
AbuseIPDB, GeoLookup, Debug ou System.
- La restriction géographique sur les ports non-SMTP peut aussi bloquer un
utilisateur légitime qui relève son courrier en IMAP/POP depuis l'étranger.
Gardez une durée de ban courte, mettez
GEORESTRICT.ban.qtyà0pour seulement déconnecter, ouGEORESTRICT.actionà"reject"pour éviter un ban persistant. - La protection contre les floods de connexions compte toutes les connexions
sur
CONNFLOOD.ports: le script peut bannir un relais légitime qui ouvre beaucoup de connexions courtes. AugmentezCONNFLOOD.thresholdou élargissezCONNFLOOD.windowMinle cas échéant ; mettezCONNFLOOD.enabledàfalsepour la désactiver. Par défaut, le script ne compte ni IMAP ni POP. Un client mail y ouvre souvent plusieurs connexions d'un coup, et serait banni pour rien. xxdans la colonne pays du journal veut dire que le pays n'a pas pu être lu : la requête DNS a échoué ou n'a pas répondu à temps, ou l'adresse est en IPv6. Sur les ports non-SMTP, une telle connexion est refusée pour l'instant et peut réessayer. Pour la laisser passer, ajoutezxxàALLOWED_GEO.zzveut dire que la requête a marché mais que le service ne connaît pas l'IP.- Les bans sont stockés en plages d'IP et supprimés automatiquement à expiration. Des durées longues sur des sources à fort volume accumulent davantage de plages.
- Deux connexions simultanées d'un même hôte peuvent tenter de créer le même ban ;
le doublon est géré proprement et journalisé une fois en
Debug, pas en erreur. - Le rejet sur score de spam fonctionne par défaut et suppose une couche anti-spam qui ajoute un en-tête de score avant l'acceptation du message (SpamAssassin le fait). Sans cet en-tête, le contrôle ne se déclenche jamais. Si vous utilisez aussi le seuil de suppression natif de hMailServer, n'en gardez qu'un seul : le seuil natif supprime silencieusement, le script renvoie un 550 à l'expéditeur.
- Le script vise le moteur JScript classique utilisé par hMailServer.
oMessage.HeaderValue("X") = valeury est la bonne façon de définir un en-tête et correspond aux propres tests de hMailServer ; ce n'est pas du JavaScript navigateur standard.
MIT. Voir LICENSE.