Scripted routing

From Halon, SMTP software for hosting providers
Jump to: navigation, search

Scripted routing is one of the most distinguished features of the Halon SMTP software. It allows users to implement the best solution for any given challenge, instead of having to compromise.

Examples

Since you have virtually unlimited possibilities with our product, we encourage you to contact the support to discuss the best solution for you.

Asking an external source

In most situations the routing information can be found by performing a lookup to an external system. It could be an HTTP lookup to a script that performs a database lookup, an LDAP lookup, or something entirely different. The HSL scripting language allows you to perform virtually any kind of external lookup.

Below is an example where an HTTP lookup is used. It could be defined in a function or include file. The example below should be placed in a include file, and also caches the result in case the lookup server is down.

// Domain route (destination)
$route json_decode(cache "ttl" => 3600 http("$apiurl&type=route&recipientdomain=$1"$httpoption, [$recipientdomain]));
// Long-time route backup
function route_backup($recipientdomain) {
    global 
$route;
    return 
$route;
}
// Domain found, store in backup
if (isset($route["dest"]))
    
cache ["force" => true"ttl" => 3600*24*7"size" => 1024*1024 route_backup($recipientdomain);
// API is down, use backup (cache)
if (!is_array($route))
    
$route cache "ttl" => 3600*24*7"size" => 1024*1024 route_backup($recipientdomain); 

The RCPT TO flow could contain a SMTP lookup to the correct server, as learnt over HTTP:

include "file:X"// Destination lookup
// Failed to find route in both API and backup
if (!is_array($route))
    
Defer("Try later, recipient database error");
// SMTP call-out
$dest["tls"] = "optional";
$dest["host"] = $route["dest"];
$dest["port"] = number($route["port"]);
if (
$route["sasl_user"]) {
    
$dest["sasl_user"] = $route["sasl_user"];
    
$dest["sasl_pass"] = $route["sasl_pass"];
}
$result cache ["ttl_function" => smtp_lookup_rcpt_ttl"size"=> 16384]
    
smtp_lookup_rcpt($dest""$recipient, ["error_code" => true]);
if (
$result["error_code"] == -1) {
    
$error 1;
    
$errormsg "SMTP recipient lookup failed";
}
if (
$result["error_code"] >= 200 and $result["error_code"] <= 299)
    
Accept();
if (
$result["error_code"] >= 400 and $result["error_code"] <= 499)
    
$error 1;
if (
$result["error_code"] >= 400 and $result["error_code"] <= 599)
    
$errormsg $result["error_message"];
// Support function
function smtp_lookup_rcpt_ttl($result) {
    
$code $result["error_code"];
    if (
$code == -1)
        return 
60;
    if (
$code >= 200 and $code <= 299)
        return 
86400;
    return 
300;

The pre delivery (queue) flow should perform the same lookup, to learn the which destination should be used.

// Route inbound messages to their destination server
if ($transportid == "mailtransport:1") {
    include 
"file:X"// Destination lookup
    
if (!isset($route["dest"]))
        
Reschedule(3600, ["reason" => "No route""increment_retry" => false]);
    
SetDestination($route["dest"], number($route["port"]));
    if (
$route["sasl_user"])
        
SetSASL($route["sasl_user"], $route["sasl_pass"]);

Dynamic routing to several mail servers

This will lookup, cache and deliver messages to the correct server dynamically. It's very important that you control all those servers, so that your users cannot re-route traffic themselves by adding a domain.

$servers = ["mailtransport:1""mailtransport:2"];
function 
choose_server($domain$email) {
    foreach (
$servers as $t)
        if (
smtp_lookup_rcpt($t""$email) == 1)
            return 
$t;
    return 
false;
}

$server cache ["argv_filter" => [1], "size" => 1000"ttl" => 86400choose_server($recipientdomain$recipient);

if (
$server == false)
    
Reject("Unknown user");
SetMailTransport($server); 

Auto-learning using external scripts

If there is no way for the system to determine which e-mail server different recipients should be routed to, other than trying, one can design a setup where domains are added statically to the configuration using SOAP, automatically.

The Mail > Relay table will then contain one domain for each route, and an "any" (catch-all) domain that catches all un-configured recipient domains. The RCPT TO flow on that "any" domain would contain something like:

echo "auto-configuration: $recipientdomain";
$result http("http://.../smtp.php?recipient=$1&recipientdomain=$2"60, [$recipient$recipientdomain]);
if (
$result == "ok") {
   echo 
"auto-configuration: $recipientdomain added";
   
Defer("Please come back");
}
echo 
"auto-configuration: failed for $recipientdomain$result";
Reject("No such domain"); 

and the script that performs the actual configuration over SOAP is located on an extern server, and performs:

  1. Fetch all mail transports using SOAP
  2. Connects to all main transports, to see if the recipient exists on any of those
  3. If so, adds the domain to the Halon system using SOAP

The external script, if written in PHP, could contain something like:

$host $_SERVER['REMOTE_ADDR']; // or one cluster node
$recipient $_GET['recipient'];
$recipientdomain $_GET['recipientdomain'];
$client = new SoapClient('https://'.$host.'/remote/?wsdl',array(
           
'location' => 'https://'.$host.'/remote/',
           
'uri' => 'urn:halon',
           
'login' => 'admin',
           
'password' => '...',
           
'connection_timeout' => 30,
           
'trace' => true
           
));
function 
cfg_array2keylist($array) {
       
$result = array();
       foreach (
$array as $k => $v)
               
$result[] = array('first' => $k'second' => $v);
       return 
$result;
}
function 
smtp_test($host$port$recipient) {
    
$fp fsockopen($host$port$errstr$errno10);
    
$ret fgets($fp); if ($ret[0] != '2') return false;
    
fwrite($fp"HELO foo\r\n");
    
$ret fgets($fp); if ($ret[0] != '2') return false;
    
fwrite($fp"MAIL FROM: <>\r\n");
    
$ret fgets($fp); if ($ret[0] != '2') return false;
    
fwrite($fp"RCPT TO: <$recipient>\r\n");
    
$ret fgets($fp); if ($ret[0] != '2') return false;
    return 
true;
}
$config $client->configKeys();
if (
$config && $config->result) {
    foreach (
$config->result->item as $key) {
        if (!
preg_match("/^mailtransport:[0-9]+$/"$key->shortcut))
            continue;
        
$transport $key->shortcut;
        
$host $key->params->item[2];
        
$port $key->params->item[3];
        if (@
inet_pton($host) === false)
            continue;
        
// check $host:$port with $domain
        
if (!smtp_test($host$port$recipient))
            continue; 
        
// now add
        
$args = array(
                
'active' => 'yes',
                
'name' => 'Comment',
                
'domain' => $recipientdomain,
                
'incoming' => 'mailserver:1',
                
'outgoing' => $transport,
                
'flow' => 'mailflow:1',
                
'tpl' => '',
                
'rcptflow' => 'rcptflow:1'
                
);
        try {
        
$client->configKeyAdd(array('key' => 'mail_domain''params' => cfg_array2keylist($args)));
        } catch (
SoapFault $f) { /* handle errors */ }
        die(
"ok");
    }
}
die(
"error");