diff options
Diffstat (limited to 'protocols/smtp.cc')
-rw-r--r-- | protocols/smtp.cc | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/protocols/smtp.cc b/protocols/smtp.cc new file mode 100644 index 0000000..706f757 --- /dev/null +++ b/protocols/smtp.cc @@ -0,0 +1,254 @@ +// nullmailer -- a simple relay-only MTA +// Copyright (C) 2017 Bruce Guenter <bruce@untroubled.org> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// You can contact me at <bruce@untroubled.org>. There is also a mailing list +// available to discuss this package. To subscribe, send an email to +// <nullmailer-subscribe@lists.untroubled.org>. + +#include "config.h" +#include <stdlib.h> +#include <unistd.h> +#include "base64.h" +#include "connect.h" +#include "errcodes.h" +#include "fdbuf/fdbuf.h" +#include "itoa.h" +#include "mystring/mystring.h" +#include "protocol.h" + +const int default_port = 25; +const int default_tls_port = 465; +const char* cli_program = "smtp"; +const char* cli_help_prefix = "Send an email message via SMTP\n"; + +class smtp +{ + fdibuf& in; + fdobuf& out; + mystring caps; +public: + smtp(fdibuf& netin, fdobuf& netout); + ~smtp(); + int get(mystring& str); + int put(mystring cmd, mystring& result); + void docmd(mystring cmd, int range, mystring& result); + void docmd(mystring cmd, int range); + void dohelo(bool ehlo); + bool hascap(const char* name, const char* word = NULL); + void auth_login(void); + void auth_plain(void); + void send_data(fdibuf& msg); + void send_envelope(fdibuf& msg); + void send(fdibuf& msg); +}; + +smtp::smtp(fdibuf& netin, fdobuf& netout) + : in(netin), out(netout) +{ +} + +smtp::~smtp() +{ +} + +int smtp::get(mystring& str) +{ + mystring tmp; + str = ""; + int code = -1; + while(in.getline(tmp)) { + if(tmp[tmp.length()-1] == '\r') + tmp = tmp.left(tmp.length()-1); + code = atoi(tmp.c_str()); + if(!!str) + str += "\n"; + str += tmp; + if(tmp[3] != '-') + break; + } + return code; +} + +int smtp::put(mystring cmd, mystring& result) +{ + out << cmd << "\r\n"; + if(!out.flush()) + return -1; + return get(result); +} + +void smtp::docmd(mystring cmd, int range, mystring& result) +{ + int code; + if(!cmd) + code = get(result); + else + code = put(cmd, result); + if(code < range || code >= (range+100)) { + int e; + if(code >= 500) + e = ERR_MSG_PERMFAIL; + else if(code >= 400) + e = ERR_MSG_TEMPFAIL; + else + e = ERR_PROTO; + out << "QUIT\r\n"; + out.flush(); + protocol_fail(e, result.c_str()); + } +} + +void smtp::docmd(mystring cmd, int range) +{ + mystring msg; + docmd(cmd, range, msg); +} + +void smtp::dohelo(bool ehlo) +{ + mystring hh = getenv("HELOHOST"); + if (!hh) protocol_fail(1, "$HELOHOST is not set"); + docmd((ehlo ? "EHLO " : "HELO ") + hh, 200, caps); +} + +static int issep(char ch) +{ + return ch == ' ' || ch == '\n' || ch == '\0'; +} + +bool smtp::hascap(const char* name, const char* word) +{ + const size_t namelen = strlen(name); + int i = -1; + do { + const char* s = caps.c_str() + i + 5; + if (strncasecmp(s, name, namelen) == 0) { + if (s[namelen] == '\n') + return word == NULL; + else if (s[namelen] == ' ') { + if (word == NULL) + return true; + s += namelen + 1; + const size_t wordlen = strlen(word); + do { + if (strncasecmp(s, word, wordlen) == 0 && issep(s[wordlen])) + return true; + while (!issep(*s)) + ++s; + } while (*s++ == ' '); + return false; + } + } + i = caps.find_first('\n', i+1); + } while (i > 0); + return false; +} + +void smtp::auth_login(void) +{ + mystring encoded; + base64_encode(user, encoded); + docmd("AUTH LOGIN " + encoded, 300); + encoded = ""; + base64_encode(pass, encoded); + docmd(encoded, 200); +} + +void smtp::auth_plain(void) +{ + mystring plain(user); + plain += '\0'; + plain += user; + plain += '\0'; + plain += pass; + mystring encoded = "AUTH PLAIN "; + base64_encode(plain, encoded); + docmd(encoded, 200); +} + +void smtp::send_envelope(fdibuf& msg) +{ + mystring tmp; + msg.getline(tmp); + docmd("MAIL FROM:<" + tmp + ">", 200); + while(msg.getline(tmp) && !!tmp) + docmd("RCPT TO:<" + tmp + ">", 200); +} + +void smtp::send_data(fdibuf& msg) +{ + docmd("DATA", 300); + mystring tmp; + while(msg.getline(tmp)) { + if((tmp[0] == '.' && !(out << ".")) || + !(out << tmp << "\r\n")) + protocol_fail(ERR_MSG_WRITE, "Error sending message to remote"); + } + docmd(".", 200, tmp); + out << "QUIT\r\n"; + out.flush(); + protocol_succ(tmp.c_str()); +} + +void smtp::send(fdibuf& msg) +{ + send_envelope(msg); + send_data(msg); +} + +void protocol_prep(fdibuf&) +{ +} + +static int did_starttls = 0; + +void protocol_starttls(fdibuf& netin, fdobuf& netout) +{ + smtp conn(netin, netout); + conn.docmd("", 200); + conn.dohelo(true); + conn.docmd("STARTTLS", 200); + did_starttls = 1; +} + +void protocol_send(fdibuf& in, fdibuf& netin, fdobuf& netout) +{ + smtp conn(netin, netout); + if (!did_starttls) + conn.docmd("", 200); + + if (user != 0 && pass != 0) { + conn.dohelo(true); + if (auth_method == AUTH_LOGIN) + conn.auth_login(); + else if (auth_method == AUTH_PLAIN) + conn.auth_plain(); + else { + // Detect method + if (conn.hascap("AUTH", "PLAIN")) + conn.auth_plain(); + else if (conn.hascap("AUTH", "LOGIN")) + conn.auth_login(); + else + protocol_fail(ERR_MSG_TEMPFAIL, "Server does not advertise any supported authentication methods"); + } + } + else + conn.dohelo(false); + + conn.send(in); +} |