/* 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 ""; } 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);