Rumble
a Reasonable Uncomplicated Mailserver Bundled with Lots of Extensions
server status Front page domains Download Rumble users Documentation settings C/C++ API modules Lua API

Rumble - Lua API

The Lua interpreter in Rumble comes with the standard libraries as well as the extensions listed below.
To add a Lua script to the mail server, simply create a Lua file and add the following command to rumble.conf:

LoadScript    /path/to/your/script    
For an example of how to use the Lua API, check out RumbleLua, which ships with the Rumble packages, or check out these simple example modules.

 

 


String manipulation



bulletstring.SHA256(s)

Returns the SHA-256 message digest of the string s.
This is primarilly used for creating passwords for saving new accounts or updating old ones.

local text = "some text";
local digest = text:SHA256(); -- The same as string.SHA256(text);
-- yields: b94f6f125c79e3a5ffaa826f584c10d52ada669e6762051b826b55776d05aed2

 

 

bulletstring.decode64(s) / string.encode64(s)

Decodes or encodes a string using the base-64 algorithm.

local text = "This is a test!";
local enc = text:encode64(); -- base64-encode it
local dec = enc:decode64(); -- decode the string again
print(dec); -- prints "This is a test!"

 

 

 

 


File manipulation


 

bulletfile.stat(f):

Returns a table containing information about the file f, similar to fstat().

local info = file.stat("myfile.cfg");
print("myfile.cfg has a size of:" .. info.size .. " bytes");
print("myfile.cfg was created at " .. info.created);

 

bulletfile.exists(f):

Returns true if the file exists, otherwise false.

if ( file.exists("somefile.txt") ) then
   print("somefile.txt exists!");
else
   print("somefile.txt doesn't exist :(");
end

 

 

 

 

 


Networking functions


 

bulletnetwork.getHostByName(host):

Resolves the host name host into its IP address.

local IP = network.getHostByName("lua.org");
print("lua.org is " .. IP); -- lua org is 89.238.129.34

 

bulletnetwork.getMX(domain):

Looks up the MX records of domain and returns a list of entries found.

local mxlist = network.getMX("sourceforge.net");
for k, entry in pairs(mxlist) do
   print("I found mail server " .. entry.host .. " with pref " .. entry.preference);
   -- I found mail server mx.sourceforge.net with pref 10
end

 

 

 

 

 


Mailman functions


 

bulletMailman.accountExists(domain, user):

Returns true if an account exists with the literal domain and username.
Unlike Mailman.addressExists, this function will only return true if an account with the exact credentials exists.

if ( Mailman.accountExists("mydomain.tld", "someuser") ) then
    print ("someuser@mydomain.tld is a real account!");
else
    print ("someuser@mydomain.tld doesn't exist!");
end

 

 

 

bulletMailman.addressExists(domain, user):

Returns true if the address user@domain is either an existing email account on the server, or if any wildcard or GLOB'ed alias will pick up mail sent for it. For example, Mailman.addressExists("domain.tld", "somename") will return true if you have created the account *@domain.tld, whereas Mailman.accountExists would return false:

if ( Mailman.accountExists("mydomain.tld", "someuser") ) then
    print ("someuser@mydomain.tld is a real account!");
else
    print ("someuser@mydomain.tld is not a valid account!");
    if ( Mailman.addressExists("mydomain.tld", "someuser") ) then
        print ("...but it is a valid address!");
    end
end

 

 

 


bulletMailman.createDomain(domain):

Adds domain as a new domain in the mail service if it doesn't already exist.

Mailman.createDomain("mynewdomain.com");

 

bulletMailman.deleteAccount([uid], domain, user):

If no UID is provided, deletes the account user@domain from the database, otherwise deletes the account with the specified UID.

Mailman.deleteAccount("mydomain.tld", "johndoe"); -- Delete johndoe@mydomain.tld
Mailman.deleteAccount(1673); -- Delete the account with UID 1673

 

 

bulletMailman.deleteDomain(domain):

Deletes domain from the database if it exists.

Mailman.createDomain("mynewdomain.tld"); -- Make a new domain, just for fun.
Mailman.deleteDomain("mynewdomain.tld"); -- Delete it again.

 

 

bulletMailman.listDomains():

Returns a table containing all the domains in the mail server database:

for id, domain in pairs(Mailman.listDomains()) do
    print("I found " .. domain);
end

 

 

bulletMailman.listAccounts(domain):

Returns a table containing all the accounts associated with domain:

for id, account in pairs(Mailman.listAccounts("mydomain.tld")) do
    print(("Account: %s@%s, UID: %u, type: %s"):format(
        account.name, account.domain, account.uid, account.type
    ));
end

 

 

 

bulletMailman.listHeaders(uid[, folder]):

Retrieves the headers of all messages belonging to the specified user id or, optionally, the messages in the folder specified by folder:

-- get johndoe@mydomain.tld's account info
local account = Mailman.readAccount("mydomain.tld", "Johndoe");
-- Get the messages
local headers = Mailman.listHeaders(account.id);
for id, header in pairs(headers) do
    local text = string.format("Message no. %d - From: %s, Subject: %s, Content-type: %s",
    header.from, header.subject, header['content-type']) ;
    print(text);
    if (not header.read) then print("This message hasn't been read yet!"); end
end

 

 

 

bulletMailman.readAccount([uid,] domain, user):


If the account, specified either by the UID or by domain and user, exists, returns a table containing the infomation about the account, otherwise returns false:

local myAccount = Mailman.readAccount("mydomain.tld", "johndoe");
if (myAccount) then
    print( string.format("I found %s@%s!", myAccount.name, myAccount.domain);
end

 

 

bulletMailman.readMail(uid, lid):

Reads the message with id lid. The account uid is required to prevent exploitation.
If found, the entire message is read and parsed into both headers, message body and
individual message parts in case of multipart messages (text/html versions, attached files
and so on). The returned message structure can be quite complex, and as such, the example
below only shows a fragment of it. For an in-depth example, please see the webmail module
included in the mail server package.

local message = Mailman.readMail(123, 23);
print("From: " .. message.headers.from);
print("Message reads:");
print(message.body);

 

 

bulletMailman.saveAccount(account):

If account, specified as a table containing the same information as given by Mailman.readAccount, exists, saves and updates the information stored, otherwise creates a new account with the specified credentials.
Note: If you are saving or updating passwords, remember to first run them through the string.SHA256().

-- Make a new account
local newAccount = {
    name = "newuser", domain = "mydomain.tld",
    type = "mbox", password = ("mypass"):SHA256()
};
Mailman.saveAccount(newAccount);

-- Update an existing account

local oldAccount = Mailman.readAccount("mydomain.tld", "olduser");
if (oldAccount) then
   oldAccount.name = "updatedName";
   Mailman.saveAccount(oldAccount); -- updated!
end

 

 

 

 

 

 

bulletMailman.sendMail(from, to, message)

Creates and sends an email using the specified parameters.
The message should be properly formatted
with headers and a body.

local message = [[
To: john@doe.com
From: some@one.org
Subject: Hello!

Testing 123
]];
-- Now send the email:
Mailman.sendMail("some@one.org", "john@doe.com", message);

 

 

 

 

Note: This API call is considered an internal mechanism and will still work even if the
BlockOutgoingMail directive is enabled in rumble.conf.

 

 


Service functions


 

bulletRumble.createService(luaFunction, port, [threadCount]):

Creates a TCP service on port, hooking into the function luaFunction. The optional argument
threadCount specifies the number of threads to create (default is 10). Returns true if the service
was created, otherwise returns false. If another thread has already created this service,
createService returns nil.

function acceptCall(session) -- The Lua function that accepts incoming connections.
   session:send("Content-Type: text/html\r\n\r\n");
   session:send("Hello there!");
end

Rumble.createService(acceptCall, 80, 10); -- Create an example web service on port 80

 

 

 

bulletRumble.listModules():

Returns a list of the C/C++ modules enabled on the server:

for id, module in pairs(Rumble.listModules()) do
    print("Found module: " .. module.title);
    print("What it does is: " .. module.description);
    print("It's located at: " .. module.file);
end

 

 

bulletRumble.readConfig(key)

Returns the value of key in the configuration file:

local servername = Rumble.readConfig("servername");
print("Hi, this server is called " .. servername);

 

bulletRumble.resumeService(svcName)

If suspended, resumes the service called svcName:

Rumble.suspendService("smtp");
print("SMTP is paused!");
Rumble.resumeService("smtp");
print("And now it's running again!");

 

bulletRumble.serviceInfo(svc):

If the service specified in name by svc exists, returns a table containing information about the
service, otherwise returns nil:

local svcinfo = Rumble.serviceInfo("smtp");
if ( svcinfo.enabled ) then
    print("SMTP is running!");
    print("So far, it's handled " .. svc.sessions .. " mail sessions");
    print("It's transmitted " .. (svc.received + svc.sent) .. " bytes of data");
else
    print("SMTP is disabled!");
end

 

 

bulletRumble.serverInfo():

Returns a table containing information about the server:

local info = Rumble.serverInfo();
print("This server is running on " .. server.os);
print("It's been running for " .. server.uptime .. " seconds!");

 

 

bulletRumble.getLog():

Returns a table containing the last 200 logged messages from the server:

for i, entry in pairs(Rumble.getLog()) do
    print("Entry no." .. i .. ": " .. entry);
end

 

 

bulletRumble.setHook(luaFunction(session, cmd), svcName, when [, cmd])

Adds a Lua hook to the service defined as svcName, hooking the function luaFunction to the
moment in a session specified by when:

  • accept: Executes the Lua function whenever a new connection is made to the service
  • close: Executes the Lua function when a connection is being closed.
  • command: if cmd is specified, executes the Lua function whenever that specific command
    is issued by the client.

Hooks can issue a return value to control the sessions. A return value of false or "failure" will terminate a connection.

local function onAccept(session, cmd)
    session:send("Welcome " ..session.address .. "!"); -- fx. Welcome 127.0.0.1!
end
local function onHELO(session, cmd)
    if (cmd == "") then
        session:send("You sent a bad HELO!");
        return false;
    else
        session:send(string.format("Hello there, %s!\r\n", cmd));
        return true;
    end
end Rumble.setHook(onAccept, "smtp", "accept"); -- hook to new SMTP connections
Rumble.setHook(onHELO, "smtp", "command", "helo"); -- hook to the HELO command

 

  

bulletRumble.startService(svcName)

If stopped (disabled), tries to start up the service called svcName:

Rumble.stopService("smtp");
print("SMTP was killed!");
Rumble.startService("smtp");
print("Restarting SMTP service now!");

 

bulletRumble.stopService(svcName)

If running or suspended, stops (kills) the service called svcName:

Rumble.stopService("smtp");
print("SMTP was killed!");
Rumble.startService("smtp");
print("Restarting SMTP service now!");

 

bulletRumble.suspendService(svcName)

If running, suspends the service called svcName gracefully. Unlike stopService(), it will only stop active server sessions once they have finished:

Rumble.suspendService("smtp");
print("SMTP is paused, no new connections can be made!");
Rumble.resumeService("smtp");
print("And now it's running again!");

 

 


Session handles


 

Session handles returned by hooking via Rumble.setHook or Rumble.createService have the following set of functions to communicate with the client:

 

bulletsession:send(msg):

Sends the message msg to the client:

function acceptCall(session)
   session:send("Hello there!"); -- Sends "Hello there!" to the client.
end

 

 

bulletsession:receive():

Returns one line of data received by the client.

function acceptCall(session)
   session:send("Hello there!"); -- Sends "Hello there!" to the client.
   local response = session:receive(); -- Get a line from the client
   session:send("You said: " .. response);
end

 

 

bulletsession:receivebytes(numBytes):

Reads numBytes of data from the client and returns the data received and the actual number of
bytes, or an empty string and a length of -1 if the operation failed:

function acceptCall(session)
   local response, length = session:receivebytes(100); -- Try to get 100 bytes.
   if ( length ~= -1 ) then
       print("We got a response!");
   else
       print("Something went wrong, client aborted?");
   end
end

 

 

 

 

bulletsession:lock() and session:unlock():

Locks or unlocks the core mutex of the entire service. This method can be used for performing
operations that would otherwise be unsafe in a threaded environment.
Rumble will automatically unlock the service once your script exits, but nonetheless, it's
important to remember to unlock it after you're done...just in case.

session:lock() -- Get a lock on the service
doSomeSecretStuff();
session:unlock(); -- Remove the lock.