SDK Script email-to-sms.are

email-to-sms.are
/* DESC: This script implements a lightweight SMTP server which is able to receive mail and forward them as SMS to a phone number.
 * Copyright (C) 2012 NetModule AG, Switzerland
 */
 
MAX_CONECTIONS  = 1;                                    /* maximum of concurrent connections */
TIMEOUT         = 10;                                   /* receive timeout in seconds */
MAX_ERRORS      = 5;                                    /* max. number of error per session */
MAX_MESSAGES    = 32;                                   /* max. number of messages per session */
MAX_HEADER_LEN  = 1024;                                 /* max. header length */
MAX_BODY_LEN    = 160;                                  /* max. body length */
DOMAIN          = "domain.net";                         /* recipient domain */
AUTHENTICATE    = 1;                                    /* force authentication */
 
ALLOWED_SENDERS = mkarray("sender1@domain.local",        
			  "sender2@domain.local");
 
ALLOWED_NUMBERS = mkarray("+123456789",                
		          "+987654321");
 
string extract_addr (string str)
{
    addr = trim(str);
 
    /* check for @ */
    pos = strchr(addr, "@");
    if (is_void(pos) || pos == 0) {
        nb_syslog("malformed address");
        return "";
    }
 
    pos = strchr(addr, "<");
    if (!is_void(pos)) {
        addr = substr(addr, pos + 1);
        pos = strchr(addr, ">");
        if (is_void(pos) || pos < 1) {
            nb_syslog("malformed address");
            return "";
        }
        addr = left(addr, pos);
    }
 
    return addr;
}
 
string validate_sender (string from) 
{
    /* extract mail addr */
    sender = extract_addr(from);
    if (strlen(sender) == 0) return "";
 
    /* check if sender is allowed */
    for (i = 0; i < length(ALLOWED_SENDERS); i++) {
        s = ALLOWED_SENDERS[i];
        if (left(sender, strlen(s)) == s) {
            nb_syslog("sender %s can pass", sender);
            return sender;
        }
    }
    nb_syslog("sender %s is now allowed to send something", sender);
    return "";
}
 
string validate_number (string rcpt)
{
    /* extract mail addr */
    addr = extract_addr(rcpt);
    if (strlen(addr) == 0) return "";
 
    pos = strchr(addr, "@");
    if (is_void(pos) || pos == 0) {
        nb_syslog("malformed address");
        return "";
    }
 
    number = left(addr, pos);
    domain = substr(addr, pos + 1);
 
    if (domain != DOMAIN) {
        nb_syslog("invalid domain");
        return "";
    }
    if (left(number, 1) != "+") {
        nb_syslog("need international number format");
        return "";
    }
 
    for (i = 0; i < length(ALLOWED_NUMBERS); i++) {
        n = ALLOWED_NUMBERS[i];
        if (left(number, strlen(n)) == n) {
            nb_syslog("number %s is allowed", number);
            return number;
        }
    }
    nb_syslog("number %s is not allowed", number);
    return number;
}
 
string add_to_header (string header, string msg)
{
    if ((strlen(header) + strlen(msg) + 1) > MAX_HEADER_LEN) {
        nb_syslog("header length exceeded");
        return header;
    }
    return strcat(header, msg, "\n");
}
 
string add_to_body (string body, string msg)
{
    len = strlen(body) + strlen(msg) + 1;
 
    if (len > MAX_BODY_LEN) {
        nb_syslog("body length exceeded (%d bytes, max %d)", len, MAX_BODY_LEN);
        return body;
    }
    return strcat(body, msg, "\n");
}
 
void usage()
{
    nb_syslog("usage: email-to-sms.are <port>");
    exit(1);
}
 
/* main */
 
if (argc < 2) {
    usage();
}
 
PORT = (int) argv[1];
HOSTNAME = nb_config_get("network.hostname");
if (strlen(hostname) == 0) {
    HOSTNAME = "host";
}
if (AUTHENTICATE) {
    PASSWORD = nb_config_get("admin.password");
    if (strlen(PASSWORD) == 0) {
        PASSWORD = "admin01";
    }
}
 
st = nb_config_get("smsd.status");
if (st != "1") {
    nb_syslog("SMS daemon is not enabled");
    exit(1);
}
 
if (PORT < 1024 || PORT >= 65536) {                
    nb_syslog("invalid port %d specified (must be 1024..65535)", PORT);
    exit(1);                                                           
}     
 
/* raise socket */
 
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
    nb_syslog("unable to open socket");
    exit(1);
}
 
ret = bind(sock, PORT, "");
if (ret == -1) {
    nb_syslog("unable to bind port %d", PORT);
    close(sock);
    exit(1);
}
 
ret = listen(sock, MAX_CONECTIONS);
if (ret == -1) {
    nb_syslog("unable to listen");
    close(sock);
    exit(1);
}
 
/* listen for clients */
nb_syslog("listening for connections on port %d", PORT);
state = "disconnected";
 
while (1) {
    client = accept(sock);
    if (client < 0) {
        printf("accept failed\n");
        sleep(1);
        continue;
    }   
 
    nb_syslog("new client has connected");
    user = pass = number = rcpt = sender = header = body = subject = "";
    errors = messages = 0;
 
    state = "connected";
    send(client, "220 %s.%s ESTMP arena\r\n", HOSTNAME, DOMAIN);    
 
    while (1) {
        /* wait for socket data */
        rv = select(client, TIMEOUT);
        if (rv == -1) {
            nb_syslog("select failed, client endpoint stalled");
            break;
        } else if (rv == 0) {
            nb_syslog("select timed out"); 
            break;
        } else {
            /* data available */
            msg = recv(client);
            if (strlen(msg) == 0) {
                nb_syslog("empty message received, stopping transaction");
                break;
            }
            messages++;
 
            if (errors > MAX_ERRORS || messages > MAX_MESSAGES) {     
                nb_syslog("discarding client because of too many errors");
                send(client,"554 Transaction failed\r\n");
                break;
            }
 
            pos1 = strchr(msg, "\r");
            pos2 = strchr(msg, "\n");
            if (pos1 >= 0 && pos1 < pos2) {
                pos = pos1;
            } else {
                pos = pos2;
            }
            if (!is_void(pos)) {
                line = substr(msg, 0, pos); 
                cmd = tolower(line);
            } else {
                /* not a valid command */
                line = cmd = "";
            }
 
            /*
                dump(msg);
                nb_syslog("state is '%s', got cmd '%s'", state, cmd);
             */
 
            if (left(cmd, 4) == "quit" || left(cmd, 3) == "bye") {
                send(client,"221 Bye\r\n");
                state = "disconnected";                
                break;
            } 
 
            if (state == "connected") {
                /* expect hello */
                if (left(cmd, 4) == "helo") {
                    state = "heloSent";
                    send(client, "250 %s.%s Hello\r\n", HOSTNAME, DOMAIN);
                } else if (left(cmd, 4) == "ehlo") {
                    if (AUTHENTICATE) {
                        state = "heloSent";
                        send(client, 
                             "250-%s.%s at your service\r\n250-8BITMIME\r\n250-AUTH LOGIN PLAIN\r\n250 ENHANCEDSTATUSCODES\r\n",
                             HOSTNAME, DOMAIN);
                    } else {
                        state = "heloSent";
                        send(client, "220 %s.%s Hello\r\n", HOSTNAME, DOMAIN);
                    }
                } else {
                    send(client, "500 polite people say hello first\r\n");
                    errors++;
                }
            } else if (state == "heloSent" || state == "authenticated") {
                if (AUTHENTICATE && state != "authenticated") {
                    if (left(cmd, 10) == "auth login") {
                        /* prompt for username */
                        send(client, "334 %s\r\n", base64_encode("Username:"));
                        state = "userPrompted";
                    } else {
                        send(client, "500 not authenticated yet\r\n");
                        errors++;
                    }
                } else {
                    /* expect mail from */
                    if (left(cmd, 10) == "mail from:") {
                        /* rewind mail settings */
                        user = pass = number = rcpt = sender = header = body = subject = "";
 
                        sender = validate_sender(substr(cmd, 10, 64));
                        if (strlen(sender) > 0) {
                            state = "fromReceived";
                            send(client, "250 sender ok\r\n");
                        } else {
                            send(client, "500 invalid sender\r\n");
                            errors++;
                        }
                    } else {
                        send(client, "500 need mail from\r\n");
                        errors++;
                    }
                }
            } else if (state == "userPrompted") {
                /* read username */
                user = base64_decode(trim(line));
                if (user != "admin") {
                    nb_syslog("invalid user specified for authentication");
                    send(client, "500 invalid user specified\r\n");
                    errors++;
                } else {
                    send(client, "334 %s\r\n", base64_encode("Password:"));
                    state = "passwordPrompted";
                }
            } else if (state == "passwordPrompted") {
                /* read username */
                pass = base64_decode(trim(line));
                if (pass != PASSWORD) {
                    nb_syslog("authentication for '%s' failed", user);
                    send(client, "500 authentication failed\r\n");
                    errors++;
                } else {
                    nb_syslog("authentication succeeded for user %s", user);
                    send(client, "235 OK\r\n");
                    state = "authenticated";
                }
            } else if (state == "fromReceived") {
                /* expect rcpt to */
                if (left(cmd, 8) == "rcpt to:") {
                    number = validate_number(substr(cmd, 8, 64));
                    if (strlen(number) > 0) {
                        state = "rcptReceived";
                        send(client, "250 rcpt ok\r\n");
                    } else {
                        send(client, "500 invalid rcpt\r\n");
                        errors++;
                    }
                } else {
                    send(client, "500 need rcpt to\r\n");
                    errors++;
                }
            } else if (state == "rcptReceived") {
                /* expect DATA */
                if (left(cmd, 4) == "data") {
                    state = "dataReady";
                    send(client, "354 End data with <CR><LF>.<CR><LF>\r\n");
                } else {
                    send(client, "500 need data\r\n");
                    errors++;
                }
            } else if (state == "dataReady" || state == "headerReceived") {
                gotEOF = 0;
                pos = strstr(msg, "\r\n.\r\n");
                if (!is_void(pos)) {
                    gotEOF = 1;
                    msg = left(msg, pos);
                }
 
                if (state != "headerReceived") {
                    pos = strstr(msg, "\n\n");
                    if (is_void(pos)) pos = strstr(msg, "\r\n\r\n");
 
                    if (is_void(pos)) {
                        header = add_to_header(header, msg);
                    } else {
                        state = "headerReceived";
                        header = add_to_header(header, substr(msg, 0, pos));
                        body = add_to_body(body, ltrim(substr(msg, pos+1)));
                    }
                } else {
                    body = add_to_body(body, msg, "\n");
                }
 
                if (gotEOF) {
                    if (state != "headerReceived") {
                        nb_syslog("no body message data has been received");
                        send(client,"554 Transaction failed (no body data)\r\n");
                        errors++;
                    } else {
                        body = trim(body);
                        if (strlen(body) > 0 && strlen(number) > 0) {
                            /* dump(body); */
 
                            nb_syslog("sending message to %s (%d bytes)", number, strlen(body));
                            id = nb_sms_send(number, body);                
                            if (id) {
                                nb_syslog("spooled message to %s (ID %s)", number, id);
                                send(client,"250 OK message queued as %s\r\n", id);
                                errors = 0;
                            } else {
                                nb_syslog("unable to send message to %s", number);
                                send(client,"554 Transaction failed\r\n");
                                errors++;
                            }
                        } else {
                            nb_syslog("no message data");
                            send(client,"554 Transaction failed (no message data)\r\n");
                            errors++;
                        }
                    }
                    state = "heloSent";                    
                }
            } else {
                nb_syslog("rejecting client due to invalid state");
                break;
            }
        }
    }
    nb_syslog("disconnecting client");
    close(client);
}
 
nb_syslog("terminating");
close(sock);
 
exit(0);