Skip to content
  • Categories
  • Recent
  • Popular
  • Tags
  • Users
  • Groups
  • Documentation
  • Downloads
  • Report a bug
  • GitHub mirror
  • Paid support
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
hMailServer Community Forum
hMailServer 6.3.8 is the current release, signed 2 November 2026. Windows and Linux, and an in-place upgrade from 5.3 or later, but for the original hMailServer's 5.7.Download 6.3.8 Documentation FAQ
  1. Support and discussion for hMailServer
  2. Automation & integration
  3. Scripting & the COM API
  4. Event scripts: every event and its arguments, the limits, and the 6.3.4 security fix

Event scripts: every event and its arguments, the limits, and the 6.3.4 security fix

Scheduled Pinned Locked Moved Scripting & the COM API
securitywindowslinuxadministration
1 Posts 1 Posters 133 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • ProgressiverobotP
    ProgressiverobotP
    Progressiverobot
    hMailServer Team
    wrote on last edited by chris
    #1

    Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) refuses a rule's Run function action when the rule is saved unless it names a plain function, for a rule of every owner, over COM and REST, and setting one on a domain's or an account's rule is held to the server administrator alone, where up to 6.3.6 a credential confined to named domains, a domain administrator among them, could set it. An event script that goes on in a transaction after Database.ExecuteSQL has failed is refused every statement after it once the database has ended the transaction, and Database.CommitTransaction then fails: call Database.RollbackTransaction and run the whole transaction again, or stop. A ScriptTimeout above 4,294,967 seconds is taken as that, where it ended every Windows event script at once, and the VBScript calls of OnClientValidatePassword, OnExternalAccountDownload and OnDeliveryFailed are quoted as every other call is. See the 6.3.7 announcement. hMailServer 6.3.8 (2 November 2026) adds a sixteenth event, OnIMAPMessageMoved, called once for each message an IMAP MOVE or UID MOVE moved between two folders of the account signed in, after the command and off the IMAP thread, with the account and thirteen values, in VBScript and JScript on Windows and JavaScript on Linux; it is off as shipped, and IMAPMessageMovedEvents (COM Settings.Scripting.IMAPMessageMovedEvents, imap_message_moved_events in PUT /api/v1/settings/scripting) switches it on (EventScripts.md). Work that outlasts a stop of the servers now writes its interruption to the debug log, not the ERROR log, so OnError is not called for it, and an event script's call that meets a stop fails with "The server is stopping. The request was not completed.", its script error going to the debug log. See the 6.3.8 announcement.

    This topic covers event scripts in hMailServer 6.3.4: the languages, switching scripting on, the fifteen events, the objects, the limits and errors, and the 6.3.4 security fix. If you run a Windows server older than 6.3.4 with event scripting on, that fix is the one thing to know first: a value written into a JScript handler's call, a password among them, could run code inside the server, and so could the name in a rule's Run function action, in either language. The section on CVE-2026-100741 below says what to do until the server is upgraded.

    What an event script is

    An event script is one file, EventHandlers.vbs or EventHandlers.js, in the server's Events directory, holding functions named after the events they handle. When scripting is on, the server calls the function for each event as it happens, hands it the objects the event is about (the client, the message, the account) and reads back what the function decided through the Result object. A rule's Run function action calls a function of the same file by name, and it too needs scripting on.

    Scripting is off as shipped, and with it off no handler is called. The file is loaded all the same: at start, at every reload, at Reload() and at POST /api/v1/settings/scripting/reload. Loading a file runs its code outside functions, once to compile it and once more for each of the fifteen handlers looked for, so keep the top level to declarations. A top level that throws, or runs past the timeout, is reported as a load that failed (HM5016, HM5710 or HM5021), whether scripting is on or not.

    A handler runs inside the server, on the thread that is doing the work, as the service's account: LocalSystem on a Windows installation that began before 6.3.4, NT SERVICE\hMailServer on a new one from 6.3.4, the hmailserver user on Linux. Everything behind a slow script waits, so try a script first on a server not carrying your mail.

    Which language runs where

    Language Windows Linux Engine File
    VBScript runs refused at load Windows Active Scripting (vbscript.dll) EventHandlers.vbs
    JScript runs refused at load Windows Active Scripting (jscript.dll), ECMAScript 3 EventHandlers.js
    JavaScript refused at load runs QuickJS 2025-04-26, built into the server, ES2023 EventHandlers.js

    Up to 6.3.3 a Linux server had no script engine and refused any script; since 6.3.4 it runs JavaScript in QuickJS, with the same fifteen events, function names and objects. Windows keeps VBScript and JScript unchanged.

    The language is one setting: Settings.Scripting.Language over COM (Windows only), "language" in PUT /api/v1/settings/scripting over REST, and the Language (VBScript, JScript or JavaScript) field on the Control Panel's Advanced page. Since 6.3.4 it accepts all three words on both platforms, in any case, and refuses any other word without storing anything. It is seeded VBScript on every platform, so a Linux server switching scripting on sets JavaScript first.

    Storing the language loads nothing. The script in force goes on running, in the language it was loaded in, until the next load. A language the build does not run is refused when the script is loaded: one line in the ERROR log, HM5016 at a start or HM5710 at a reload (the earlier script then stays in force), naming the language to use, once per load. While scripting is switched off this refusal is not reported, so switch scripting on and reload before reading the log for it.

    GET /api/v1/status says which languages a build runs, under build.script_languages: ["VBScript","JScript"] on Windows, ["JavaScript"] on Linux.

    Switching it on

    On Windows there are two Control Panel pages under Maintenance: Advanced, whose Scripting tab turns the engine on and chooses the language, and Event scripts, which edits the file. Save & reload writes a .bak first, then reloads and checks the syntax. Over COM the switch is Settings.Scripting.Enabled; Settings.Scripting.Reload() re-reads the file and CheckSyntax() returns the error text, where empty means clean. There is one file and no include mechanism.

    On Linux, from the command line:

    1. Set the language: hmctl settings set scripting --set-string language=JavaScript
    2. Write the file as the service's user:
    sudo -u hmailserver tee /var/lib/hmailserver/events/EventHandlers.js <<'EOF'
    function OnAcceptMessage(client, message) {
       if (message.Subject.includes('[URGENT]') && !client.Authenticated) {
          Result.Value = 2;
          Result.Message = 'Urgent mail comes from people who have logged on.';
       }
    }
    EOF
    
    1. Switch scripting on: hmctl settings set scripting --set enabled=true
    2. Confirm which file the server reads: hmctl settings get scripting current_script_file
    3. Load it with POST /api/v1/settings/scripting/reload. POST /api/v1/settings/scripting/check answers {"result":""} when the file compiles and the error, with its line, when it does not.

    The fifteen events

    Every handler is optional. When the script is loaded the server checks which of these names the file declares and fires only those. Names are matched exactly: OnErrorLog is not a handler, the name is OnError.

    Handler Fires when Called with What Result.Value does
    OnClientConnect(client) any TCP connection is accepted, before a greeting the client 1 closes the connection at once
    OnHELO(client) the SMTP client sent HELO or EHLO the client 1 answers 554 5.7.1 Rejected; 2 answers 554 5.7.1 with Result.Message; 3 answers 453 4.7.0 with Result.Message
    OnClientLogon(client) an SMTP, POP3 or IMAP client finished a logon attempt, successful or not the client; client.Authenticated says how the logon went nothing: the logon has been decided
    OnClientValidatePassword(account, password) a password is being checked, before the built-in check the account and the password typed 0 lets the client in; 1 turns it away; 2 (as given) leaves it to the stored password
    OnSMTPData(client, message) the DATA command arrived, before the body the client and the message as OnHELO
    OnAcceptMessage(client, message) the body has been received and is about to be queued the client and the message as OnHELO
    OnRecipientUnknown(client, message) a RCPT TO named an address this server does not host the client and the message nothing
    OnTooManyInvalidCommands(client, message) an SMTP client sent too many invalid commands the client and the message nothing
    OnDeliveryStart(message) the first step of a delivery attempt, before virus scanning the message 1 deletes the message
    OnDeliverMessage(message) after virus scanning and global rules, before the recipient split the message 1 deletes the message
    OnDeliveryFailed(message, recipient, error) once per failed recipient; may fire several times per message the message, the recipient, the error text nothing
    OnExternalAccountDownload(fetchAccount, message, uid) a message was fetched from an external POP3 or IMAP account the fetch account, the message or null, its UID on the remote server 1 deletes it on the remote server now; 2 after Result.Parameter days; 3 never
    OnBackupCompleted() a backup finished nothing nothing
    OnBackupFailed(reason) a backup failed the reason nothing
    OnError(severity, code, source, description) anything is written to the ERROR log the error, as the ERROR log has it nothing

    OnError's severity is 1 Critical, 2 High, 3 Medium, 4 Low, and its code is the HM<nnnn> number without the prefix.

    OnClientValidatePassword setting 0 is a full authentication bypass for that account, on every protocol. A handler that bridges to another directory should fail closed: set 1 when the lookup errors, never 0.

    The objects a handler is given

    The objects an event has are also globals of the same names, HMAILSERVER_CLIENT, HMAILSERVER_MESSAGE, HMAILSERVER_ACCOUNT, HMAILSERVER_FETCHACCOUNT, Result and EventLog, and the parameter and the global are the same object. An object an event does not have is null as a parameter; as a global it may not be defined at all (the message of an external download that failed), and reading an undefined global throws a ReferenceError, so a handler reads its parameter.

    Do not use Result in the seven events that are notifications. EventScripts.md says it is not defined at all in OnClientLogon, OnRecipientUnknown, OnTooManyInvalidCommands, OnDeliveryFailed and OnError; the public documentation adds that the server makes no Result for OnBackupCompleted and OnBackupFailed.

    Object Members On JavaScript (Linux)
    Client Port, IPAddress, Username, HELO, Authenticated, EncryptedConnection, CipherVersion, CipherName, CipherBits, SessionID the same, all read-only
    Message ID, Filename, Subject, From, FromAddress, To, CC, Date, Body, HTMLBody, Charset, Recipients, AddRecipient(name, address), ClearRecipients(), HeaderValue(name), HasBodyType(type), Flag(flag), Size (KB), State, DeliveryAttempt, UID, InternalDate, Copy(folderId), RefreshContent(), Save(), Attachments, Headers Attachments and Headers are not there; SetHeaderValue(name, value) and SetFlag(flag, on) write; InternalDate is text
    Result Value, Message, Parameter Value and Parameter take a whole Number only
    EventLog Write(text) the same
    FetchAccount the COM FetchAccount readable, not assignable; Password, Save(), DownloadNow() and Delete() are not there
    Account the COM Account, freshly loaded the properties that describe the account are readable, not assignable; Password, the collections and every method are not there

    Save() is what writes a change to disk. Changes to the subject, the body and the headers are written to the message file by Save(), on both platforms; a handler that does not call it changes nothing on disk. Do not call it in OnSMTPData: the public documentation says the message is still being processed there and saving corrupts it, and that only the character set, the file name, the envelope sender and the recipients are reliable at that point. Change a message in OnAcceptMessage.

    Result is read after the handler returns. On JavaScript a string ('1' too), a boolean, null, undefined, NaN, a fraction or an object assigned to Value or Parameter throws a TypeError, and the event goes on with the Result it had. So in OnClientValidatePassword, Result.Value = decisions[user] for a user the table does not have leaves 2, the stored password. On Windows, COM turns a boolean, undefined, a fraction and a string of digits into numbers, and undefined into 0: the same line in JScript lets that client in.

    EventLog.Write is the debugger. It appends a line to hmailserver_events.log in the log directory, and there is no breakpoint.

    A rule's Run function

    A rule's Run script function action calls the named function as <function>(HMAILSERVER_MESSAGE), then reloads the message from disk, so anything the function saves is seen by the rest of the rule set. Since 6.3.4 it works on Linux, where the function is a JavaScript one in EventHandlers.js.

    Since 6.3.4 the action names the function alone, as a plain identifier: ASCII letters, digits, _ and $, not starting with a digit. StampReceivedDate qualifies; StampReceivedDate(x) does not. On Windows, in VBScript or JScript, any other name is refused as HM5018 each time the rule matches and the function is not run. On Linux such a name is never evaluated and finds no function: nothing is run, and the ERROR log has a Script Error line, not HM5018.

    A ported handler

    JScript and JavaScript share the file name, so a JScript file carried from Windows to Linux is the file the JavaScript engine is given. EventScripts.md's example starts from VBScript on Windows:

    Sub OnAcceptMessage(oClient, oMessage)
       If InStr(oMessage.HeaderValue("X-Spam-Flag"), "YES") > 0 And Not oClient.Authenticated Then
          oMessage.HeaderValue("X-Quarantine") = "yes"
          oMessage.Subject = "[SPAM] " & oMessage.Subject
          oMessage.Save
          EventLog.Write("Tagged spam from " & oClient.IPAddress)
       End If
    End Sub
    

    The same handler for a Linux server, in EventHandlers.js:

    function OnAcceptMessage(client, message) {
       if (message.HeaderValue('X-Spam-Flag').includes('YES') && !client.Authenticated) {
          message.SetHeaderValue('X-Quarantine', 'yes');
          message.Subject = `[SPAM] ${message.Subject}`;
          message.Save();
          EventLog.Write(`Tagged spam from ${client.IPAddress}`);
       }
    }
    

    What changes in a port:

    • Parameterised properties have no assignment. message.HeaderValue('X') = 'v' becomes message.SetHeaderValue('X', 'v'), and message.Flag(n) = true becomes message.SetFlag(n, true). The reading forms are unchanged.
    • Names are case-sensitive and spelled as the IDL spells them. JScript found message.FileName; JavaScript needs message.Filename.
    • No CreateObject, no new ActiveXObject. Both exist on JavaScript only to throw a TypeError that says so.
    • null, not Nothing, for an object an event does not have.

    The limits

    A fresh engine for every event, on Windows and Linux alike: the whole file is parsed and run, the handler is called, and the engine is freed before the event goes on, so nothing a script makes outlives the event.

    No file system, no network, no processes on JavaScript. QuickJS keeps these in quickjs-libc.c, and that file is not in the tree. No module loader is installed, so import() is refused and an import statement is a syntax error; there is no print and no console. What a handler can change is what its objects change: a message's file through Save(), a copy into a folder through Copy(), the event log through EventLog.Write.

    On Windows, ScriptAllowedObjects decides what a script may create with CreateObject or new ActiveXObject. Its shipped value is *, every class. Empty means no class beyond the server's own objects, and a comma-separated list of ProgIDs means only those. Up to 6.3.3 it applied at a service restart; since 6.3.4 a save applies it. It has nothing to apply to on Linux.

    ScriptTimeout is in seconds, with 0 for no limit. It is kept in the settings store: set it with PUT /api/v1/settings/ini/ScriptTimeout, COM Settings.SetIniSetting or the Control Panel, not by editing hMailServer.ini.

    Limit Platform Value When a handler reaches it
    ScriptTimeout Windows 60 seconds by default HM5019; the interrupt cannot release a handler blocked inside a COM call
    ScriptTimeout Linux 60 seconds by default HM7341; the event goes on with whatever Result the handler had set
    Memory Linux 64 MB for each run, a constant HM7342; the event goes on
    Stack Linux 512 KB of the calling thread's stack a runaway recursion throws an InternalError

    The Linux timeout does not stop one call into a built-in that loops in the engine's C code without polling, such as includes or indexOf on an object whose length is huge: do not call an array method on an object whose length the handler has not checked.

    Errors, and where they are written

    What Where
    A script that does not compile, or whose top level throws HM5016 at start (nothing loaded), HM5710 at a reload (the earlier script stays in force)
    A top level that runs past the timeout HM5021, and the load refused
    An exception a handler does not catch one Script Error line in the ERROR log; the event goes on

    The load is all or nothing on every engine, so a typo in a reload never switches a working OnClientValidatePassword off.

    The 6.3.4 security fix: CVE-2026-100741

    Up to 6.3.3 a password could run code inside the server. On Windows, with event scripting on, the language JScript and an OnClientValidatePassword handler in EventHandlers.js, any client that could reach a sign-in and name an existing, active account could have JScript of its own run inside hMailServer.exe, with the rights of the service's account, before it had authenticated. The server built each call as source text and escaped the apostrophe but not the backslash before it. With ScriptAllowedObjects at its shipped *, that script could create any COM object, WScript.Shell among them.

    Path Who could use it Open in
    The password given to OnClientValidatePassword any client that can reach a sign-in JScript
    The UID given to OnExternalAccountDownload a remote POP3 server JScript
    The error text and failed recipient given to OnDeliveryFailed a remote mail server JScript
    The name in a rule's Run function action whoever may edit a rule, a domain's own administrator included JScript and VBScript

    Which servers are affected. The flaw is CVE-2026-100741; the record was not yet public when this was written. The flaw is in every release from 6.0.0 to 6.3.3: the escaping was checked at every one of those tags (GitLab issue 27). Treat any server older than 6.3.4 with event scripting on and JScript as its language as affected. A rule's Run function action wrote its function name into the call unescaped, in either language, so VBScript servers too.

    What 6.3.4 changes. Every JScript value is escaped in one helper, ScriptServer::EscapeForJScriptLiteral: every backslash first, then both quotes, CR, LF, U+2028, U+2029 and NUL; OnBackupFailed's reason, which neither language escaped and the table above does not list, is escaped in both. A rule's Run function name must be a plain identifier. On Linux the handler is called with values as values, so nothing is quoted into source text. A JScript handler now receives every value exactly as it was, backslashes included; one that undid the old half-escaping by hand must stop doing so.

    Until you can upgrade, switch event scripting off. That is the only measure that closes every path, in either language. A server that cannot do without its scripts can narrow what is open:

    • Take the OnClientValidatePassword, OnExternalAccountDownload and OnDeliveryFailed handlers out of a JScript EventHandlers.js and reload the scripts. Check the reload: one that fails (HM5710 in the ERROR log) keeps the earlier script in force, those handlers included.
    • In either language, let nobody but the server administrator make or edit a rule with a Run function action. Roles.md lets an account's rules be written over COM by the server administrator, an administrator of its domain, or the account itself, so "whoever may edit a rule" is wider than administrators.
    • Narrow ScriptAllowedObjects from *, which limits what an injected script can create. The key exists from 6.2.25, and before 6.3.4 it applies only after a service restart.

    Moving a handler to VBScript closes the password, UID and error-text paths and leaves the rule path open.

    Before you upgrade, check your rules' Run function actions and put the function's bare name in any that does not qualify. After it, an HM5018 in the ERROR log (on Linux, a Script Error line naming the function) means one was missed. Upgrading to 6.3.4 covers the upgrade itself.

    Reporting a vulnerability goes through a confidential GitLab issue or the Service Desk address contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com, and not through a public issue or this forum.

    What it does not do

    • OnClientValidatePassword is not consulted for SCRAM, which has no password to give it.

    Where to read more

    • EventScripts.md: the member-by-member contract of every object, and the engine.
    • The COM API and Scripting, sections 23.6 to 23.10.
    • Rules and Sieve for the Run script function action, and Security Hardening, section 17.11.
    • The public documentation's event scripts chapter.
    • On this forum: hMailServer on Linux: what you get and what you do not, the 6.3.4 security announcement, the COM API on Windows and rules and the Run function action.
    1 Reply Last reply
    0
    • chrisC chris pinned this topic on

    Hello! It looks like you're interested in this conversation, but you don't have an account yet.

    Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

    With your input, this post could be even better 💗

    Register Login
    Reply
    • Reply as topic
    Log in to reply
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes


    hMailServer Community

    The community support forum for hMailServer — the free, open-source mail server for Windows and Linux — maintained by Progressive Robot Ltd. The software is AGPL-3.0 and stays that way; this is where it is discussed, debugged and documented.

    The software

    • Documentation
    • Downloads
    • hMailServer 6.3
    • Source on GitLab

    Getting help

    • Forum rules
    • About this forum
    • Support plans
    • Pay monthly support
    • Talk to Progressive Robot

    Legal

    • Privacy policy
    • Terms and conditions
    • Cookie policy

    Progressive Robot Ltd · 220 Chester Business Park, Heronsway, Chester, Cheshire CH4 9GB, United Kingdom · +44 (0)1244 911212 · [email protected] · VAT 506152326

    Powered by NodeBB

    • Login

    • Don't have an account? Register

    • Login or register to search.
    • First post
      Last post
    0
    • Categories
    • Recent
    • Popular
    • Tags
    • Users
    • Groups
    • Documentation
    • Downloads
    • Report a bug
    • GitHub mirror
    • Paid support