Differences

This shows you the differences between two versions of the page.

Link to this comparison view

sdk:scripts:email-to-sms [2015/05/05 15:04] (current)
Line 1: Line 1:
 +====== SDK Script email-to-sms.are ======
 +<code c 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);
 +
 +</​code>​