/* elmo - ELectronic Mail Operator Copyright (C) 2002, 2003, 2004 rzyjontko 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; version 2. 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. ---------------------------------------------------------------------- This module implements RFC2821 and RFC821 (e)smtp client side protocol. Its implementation is based on the project stored in the diagram in smtp.dia. */ /**************************************************************************** * IMPLEMENTATION HEADERS ****************************************************************************/ #include #include #include #include #include "smtp.h" #include "networking.h" #include "error.h" #include "wrapbox.h" #include "mail.h" #include "mybox.h" #include "xmalloc.h" #include "ask.h" #include "gettext.h" #include "str.h" #include "debug.h" #include "file.h" #include "hmac-md5.h" /**************************************************************************** * IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS ****************************************************************************/ #ifndef HOST_NAME_MAX # define HOST_NAME_MAX 255 #endif #define TERMINATOR_RE "[0-9]{3,3} [^\r\n]*\r\n$" #define PICK_NEXT() do { sent++; pick_message (); return; } while (0) enum state { SMTP_READY, /* ready to flush - initial / final state */ SMTP_DISCONNECTED, /* no connection */ SMTP_GREETING, /* after welcome message (greeting) */ SMTP_EHLO, /* after response to ehlo */ SMTP_HELO, /* after response to helo */ SMTP_AUTH, /* after response to auth */ SMTP_AUTH_INTER, /* after response to sending a login */ SMTP_AUTH_FINAL, /* after response to sending a password or other credentials */ SMTP_MAIL, /* after response to mail from */ SMTP_RCPT, /* after response to rcpt to */ SMTP_DATA, /* after response to data */ SMTP_SENT, /* after response to message */ SMTP_QUIT /* after response to quit */ }; enum auth { AUTH_NONE, /* authentication not supported */ AUTH_CRAM_MD5, /* CRAM-MD5 */ AUTH_PLAIN, /* PLAIN */ AUTH_LOGIN /* LOGIN */ }; /**************************************************************************** * IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES ****************************************************************************/ /**************************************************************************** * IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES ****************************************************************************/ class Smtp : public Net { private: enum state state; /* connection state */ mail_array_t *marray; /* list of messages in outbox */ int sent; /* number of messages already sent */ int tried; /* number of recepients tried */ int accepted; /* number of recepients accepted */ mail_t *mail; /* a message to be sent */ address_t *recepient; /* message's recepient */ ask_t *ask; /* smtp_acc data */ str_t *send_buf; /* data sent to server */ str_t *error_message; /* error message displayed to the user */ enum auth auth; /* authentication method supported */ int ext_size; /* if server supports size extension */ int ext_pipeline; /* if server supports pipelining */ int ext_8bitmime; /* if server supports BODY=... */ int ext_auth; /* if server supports AUTH */ int max_size; /* maximal message size */ address_t *next_recepient (void); void after_newline (char **buf, int *header); void copy_buf (char *buf); bool load_file (const char *fname); void parse_extension (char *s); void parse_extensions (char *msg); bool parse_answer (char *msg, int len); void report_error (const char *str); void s_sent (char *msg, int len); void s_data (char *msg, int len); void s_rcpt_to (char *msg, int len); void s_mail_from (char *msg, int len); void s_ehlo (char *msg, int len); void s_helo (char *msg, int len); void s_auth_final (char *msg, int len); void s_auth_cram_md5 (char *msg, int len); void s_auth_plain (char *msg, int len); void s_auth_login_2 (char *msg, int len); void s_auth_login (char *msg, int len); void s_auth (char *msg, int len); void s_greeting (char *msg, int len); void s_disconnected (void); void s_quit (char *msg, int len); void init (void); void cleanup (void); void pick_recepient (void); void pick_message (void); void pick_server (void); void send_ehlo (void); void send_helo (void); void send_auth(void); void send_mail_from (void); void send_rcpt_to (void); void send_data (void); void send_quit (void); protected: void recv_fun (char *buf, int size); void on_shutdown (void); public: Smtp (void); ~Smtp (void); void run (void); void cancel (void); bool ready (void); }; /**************************************************************************** * IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID) ****************************************************************************/ /**************************************************************************** * IMPLEMENTATION PRIVATE DATA ****************************************************************************/ static char *auth_str[4] = { NULL, "CRAM-MD5", "PLAIN", "LOGIN", }; /* Here we store the result of gethostname(2). */ static char hostname[HOST_NAME_MAX + 1]; static Smtp *smtp = NULL; /**************************************************************************** * INTERFACE DATA ****************************************************************************/ /**************************************************************************** * IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES ****************************************************************************/ /**************************************************************************** * IMPLEMENTATION PRIVATE FUNCTIONS ****************************************************************************/ address_t * Smtp::next_recepient (void) { int n = tried; if (mail == NULL) return NULL; if (mail->to && n < mail->to->count){ return mail->to->array[n]; } else if (mail->to){ n -= mail->to->count; } if (mail->cc && n < mail->cc->count){ return mail->cc->array[n]; } else if (mail->cc){ n -= mail->cc->count; } if (mail->bcc && n < mail->bcc->count){ return mail->bcc->array[n]; } return NULL; } void Smtp::after_newline (char **buf, int *header) { switch (**buf){ case '.': str_put_string_len (send_buf, "..", 2); break; case '\n': *header = 0; break; case 'B': if (*header){ if (strstr (*buf, "Bcc") == *buf){ while (**buf != '\n' && **buf) ++*buf; } if (**buf) ++*buf; } break; case 'X': if (*header){ if (strstr (*buf, "X-Elmo-SMTP") == *buf){ while (**buf != '\n' && **buf) ++*buf; } if (**buf) ++*buf; } break; } } void Smtp::copy_buf (char *buf) { int header = 1; char *s = buf; str_clear (send_buf); while (*s){ switch (*s){ case '\n': str_put_string_len (send_buf, "\r\n", 2); s++; after_newline (& s, & header); break; default: str_put_char (send_buf, *s); s++; break; } } str_put_string_len (send_buf, "\r\n.\r\n", 5); } bool Smtp::load_file (const char *fname) { char *buf; size_t size; FILE *fp = fopen (fname, "r"); if (fp == NULL){ error_ (errno, "%s", fname); return false; } if (file_whole (fp, & buf, & size)){ fclose (fp); error_ (errno, "%s", fname); return false; } if (send_buf->size < size * 2){ str_destroy (send_buf); send_buf = str_create_size (size * 2 + 5); } copy_buf (buf); xfree (buf); return true; } void Smtp::parse_extension (char *s) { char *seek; if (strstr (s, "SIZE") == s || strstr (s, "size") == s){ ext_size = 1; seek = s + 4; while (*seek && isspace (*seek)) seek++; if (isdigit (*seek)) max_size = atoi (seek); return; } if (strcmp (s, "PIPELINING") == 0 || strcmp (s, "pipelining") == 0){ ext_pipeline = 1; return; } if (strcmp (s, "8BITMIME") == 0 || strcmp (s, "8bitmime") == 0){ ext_8bitmime = 1; return; } if (strstr (s, "AUTH") == s || strstr (s, "auth") == s){ ext_auth = 1; if (strstr (s, "CRAM-MD5") || strstr (s, "cram-md5")){ auth = AUTH_CRAM_MD5; } else if (strstr (s, "LOGIN") || strstr (s, "login")){ auth = AUTH_LOGIN; } else if (strstr (s, "PLAIN") || strstr (s, "plain")){ auth = AUTH_PLAIN; } else { auth = AUTH_NONE; } return; } } void Smtp::parse_extensions (char *msg) { int i; rstring_t *exts; exts = rstring_split_re (msg, "\r\n([0-9]{3,3}[ \\-])?"); if (exts == NULL) return; for (i = 1; i < exts->count; i++){ parse_extension (exts->array[i]); } rstring_delete (exts); } bool Smtp::parse_answer (char *msg, int d) { char *seek; if (*msg == d) return true; if (error_message) str_destroy (error_message); seek = strchr (msg, '\r'); if (seek) *seek = '\0'; error_message = str_dup (msg + 4); if (seek) *seek = '\r'; return false; } void Smtp::report_error (const char *str) { if (error_message){ error_ (0, "%s (%s)", str, error_message->str); } else { error_ (0, "%s", str); } } void Smtp::s_sent (char *msg, int len) { str_t *str; char *box_sent = mybox_sent (); if (! parse_answer (msg, '2')){ str = str_create (); str_sprintf (str, "%s (%s)", _("Couldn't transmit message."), mail->subject); report_error (str->str); str_destroy (str); } mail->flags &= ~ FLAG_READ; if (wrapbox_move_mail_to (mail, box_sent)){ str = str_create (); str_sprintf (str, _("Couldn't move message to %s. (%s)"), box_sent, mail->subject); error_ (errno, str->str); str_destroy (str); } xfree (box_sent); PICK_NEXT (); } void Smtp::s_data (char *msg, int len) { char *fname; str_t *str; if (! parse_answer (msg, '3')){ str = str_create (); str_sprintf (str, "%s (%s)", _("Couldn't transmit message"), mail->subject); report_error (str->str); str_destroy (str); PICK_NEXT (); } fname = wrapbox_fetch_single (mail); if (! load_file (fname)){ xfree (fname); PICK_NEXT (); } xfree (fname); state = SMTP_SENT; send_progress (_("sending message %d"), sent + 1); combo (send_buf->str, send_buf->len, TERMINATOR_RE); } void Smtp::s_rcpt_to (char *msg, int len) { str_t *str; if (! parse_answer (msg, '2')){ str = str_create (); str_sprintf (str, _("This address has been rejected " "by server: %s."), recepient->email); report_error (str->str); str_destroy (str); } else { accepted++; } tried++; pick_recepient (); } void Smtp::s_mail_from (char *msg, int len) { if (! parse_answer (msg, '2')){ report_error (_("Your email address has been rejected. " "Message not sent.")); PICK_NEXT (); } pick_recepient (); } void Smtp::s_ehlo (char *msg, int len) { char *user; char *pass; if (! parse_answer (msg, '2')){ send_helo (); return; } parse_extensions (msg); if (ext_auth && auth != AUTH_NONE){ user = ask_get_field (ask, "username"); pass = ask_get_field (ask, "password"); if (user && pass){ send_auth (); return; } } send_mail_from (); } void Smtp::s_helo (char *msg, int len) { if (! parse_answer (msg, '2')){ PICK_NEXT (); } send_mail_from (); } void Smtp::s_auth_final (char *msg, int len) { if (! parse_answer (msg, '2')){ report_error (_("Authorization has failed.")); PICK_NEXT (); } send_mail_from (); } void Smtp::s_auth_cram_md5 (char *msg, int len) { int tmp_len; char *tmp; char *user; char *pass; char *seek; str_t *challenge; str_t *answer; str_t *result; mime_t mime; int pass_len; int user_len; int i; unsigned char digest[16]; seek = strchr (msg, ' '); if (seek == NULL){ error_ (0, "%s", _("Server has sent an invalid " "authorization challenge.")); PICK_NEXT (); } else seek++; mime.off_start = 0; mime.off_end = len - (seek - msg); mime.encoding = MENC_BASE64; mime.charset = NULL; challenge = mime_decode (& mime, seek, 0); user = ask_get_field (ask, "username"); pass = ask_get_field (ask, "password"); pass_len = strlen (pass); user_len = strlen (user); hmac_md5 ((unsigned char *) challenge->str, challenge->len, (unsigned char *) pass, pass_len, digest); answer = str_create_size (user_len + 1 + 32 + 1); str_sprintf (answer, "%s ", user); for (i = 0; i < 16; i++) str_sprintf (answer, "%02x", (unsigned char) digest[i]); tmp_len = answer->len; tmp = str_finished (answer); result = mime_encode (& mime, tmp, tmp_len); str_clear (send_buf); str_sprintf (send_buf, "%s\r\n", result->str); str_destroy (result); str_destroy (challenge); state = SMTP_AUTH_FINAL; combo (send_buf->str, send_buf->len, TERMINATOR_RE); } void Smtp::s_auth_plain (char *msg, int len) { char *user; char *pass; char *answer; str_t *encoded; mime_t mime; int pass_len; int user_len; int s_len; user = ask_get_field (ask, "username"); pass = ask_get_field (ask, "password"); user_len = strlen (user); pass_len = strlen (pass); s_len = 1 + user_len + 1 + pass_len + 1; answer = (char *) xmalloc (s_len); sprintf (answer, "%c%s%c%s", '\0', user, '\0', pass); mime.encoding = MENC_BASE64; encoded = mime_encode (& mime, answer, s_len - 1); str_clear (send_buf); str_sprintf (send_buf, "%s\r\n", encoded->str); str_destroy (encoded); state = SMTP_AUTH_FINAL; combo (send_buf->str, send_buf->len, TERMINATOR_RE); } void Smtp::s_auth_login_2 (char *msg, int len) { char *pass; str_t *encoded; mime_t mime; int pass_len; if (! parse_answer (msg, '3')){ report_error (_("Authorization has failed.")); PICK_NEXT (); } pass = xstrdup (ask_get_field (ask, "password")); pass_len = strlen (pass); mime.encoding = MENC_BASE64; encoded = mime_encode (& mime, pass, pass_len); str_clear (send_buf); str_sprintf (send_buf, "%s\r\n", encoded->str); str_destroy (encoded); state = SMTP_AUTH_FINAL; combo (send_buf->str, send_buf->len, TERMINATOR_RE); } void Smtp::s_auth_login (char *msg, int len) { char *user; str_t *encoded; mime_t mime; int user_len; user = xstrdup (ask_get_field (ask, "username")); user_len = strlen (user); mime.encoding = MENC_BASE64; encoded = mime_encode (& mime, user, user_len); str_clear (send_buf); str_sprintf (send_buf, "%s\r\n", encoded->str); str_destroy (encoded); state = SMTP_AUTH_INTER; combo (send_buf->str, send_buf->len, TERMINATOR_RE); } void Smtp::s_auth (char *msg, int len) { if (! parse_answer (msg, '3')){ report_error (_("Authorization has failed.")); PICK_NEXT (); } switch (auth){ case AUTH_NONE: break; case AUTH_CRAM_MD5: s_auth_cram_md5 (msg, len); break; case AUTH_PLAIN: s_auth_plain (msg, len); break; case AUTH_LOGIN: s_auth_login (msg, len); break; } } void Smtp::s_greeting (char *msg, int len) { if (! parse_answer (msg, '2')){ report_error (_("Connection has been rejected.")); PICK_NEXT (); } send_ehlo (); } void Smtp::s_disconnected (void) { int secure; int port; char *host; if (mail == NULL){ state = SMTP_READY; return; } host = ask_get_field (ask, "server"); secure = ask_get_field_int_default (ask, "ssl", 0); port = ask_get_field_int_default (ask, "port", (secure) ? 465 : 25); if (host == NULL){ error_ (0, "%s", _("Server field in smtp_acc is " "not defined.")); PICK_NEXT (); } if (! open (host, port, secure)){ PICK_NEXT (); } state = SMTP_GREETING; net_recv (TERMINATOR_RE); } void Smtp::s_quit (char *msg, int len) { if (! parse_answer (msg, '2')){ report_error (_("Does your server love you so much?")); } close (); } void Smtp::pick_recepient (void) { recepient = next_recepient (); if (recepient) send_rcpt_to (); else if (accepted > 0) send_data (); else PICK_NEXT (); } void Smtp::pick_server (void) { char *name; if (ask == NULL){ error_ (0, "%s", _("No SMTP account has been defined.")); cleanup (); return; } name = ask_get_field (ask, "name"); if (name == NULL){ error_ (0, "%s", _("The name field in smtp_acc is " "not defined.")); PICK_NEXT (); } if (mail->smtp == NULL){ if (mail->subject) error_ (0, _("No smtp for message %d (subject: %s)."), sent + 1, mail->subject); else error_ (0, _("No smtp for message %d."), sent + 1); PICK_NEXT (); } if (! is_connected ()){ ask_change_where (ask, "name", mail->smtp); state = SMTP_DISCONNECTED; s_disconnected (); return; } if (strcmp (mail->smtp, name) == 0){ send_mail_from (); return; } send_quit (); } void Smtp::pick_message (void) { tried = 0; accepted = 0; recepient = NULL; mail = mail_array_get (marray, sent); if (mail == NULL && ! is_connected ()){ cleanup (); return; } if (mail == NULL){ send_quit (); return; } pick_server (); } void Smtp::send_ehlo (void) { state = SMTP_EHLO; str_clear (send_buf); str_sprintf (send_buf, "EHLO %s\r\n", hostname); combo (send_buf->str, send_buf->len, TERMINATOR_RE); } void Smtp::send_helo (void) { state = SMTP_HELO; str_clear (send_buf); str_sprintf (send_buf, "HELO %s\r\n", hostname); combo (send_buf->str, send_buf->len, TERMINATOR_RE); } void Smtp::send_auth (void) { state = SMTP_AUTH; str_clear (send_buf); str_sprintf (send_buf, "AUTH %s\r\n", auth_str [auth]); combo (send_buf->str, send_buf->len, TERMINATOR_RE); } void Smtp::send_mail_from (void) { char *email; email = ask_get_field (ask, "email"); if (email == NULL){ error_ (0, "%s", _("The email field in smtp_acc is" "not defined.")); send_quit (); return; } state = SMTP_MAIL; str_clear (send_buf); str_sprintf (send_buf, "MAIL FROM:<%s>\r\n", email); combo (send_buf->str, send_buf->len, TERMINATOR_RE); } void Smtp::send_rcpt_to (void) { state = SMTP_RCPT; str_clear (send_buf); str_sprintf (send_buf, "RCPT TO:<%s>\r\n", recepient->email); combo (send_buf->str, send_buf->len, TERMINATOR_RE); } void Smtp::send_data (void) { state = SMTP_DATA; combo ("DATA\r\n", 6, TERMINATOR_RE); } void Smtp::send_quit (void) { state = SMTP_QUIT; combo ("QUIT\r\n", 6, TERMINATOR_RE); } void Smtp::recv_fun (char *msg, int len) { switch (state){ case SMTP_QUIT: s_quit (msg, len); break; case SMTP_SENT: s_sent (msg, len); break; case SMTP_DATA: s_data (msg, len); break; case SMTP_RCPT: s_rcpt_to (msg, len); break; case SMTP_MAIL: s_mail_from (msg, len); break; case SMTP_AUTH: s_auth (msg, len); break; case SMTP_AUTH_INTER: s_auth_login_2 (msg, len); break; case SMTP_AUTH_FINAL: s_auth_final (msg, len); break; case SMTP_HELO: s_helo (msg, len); break; case SMTP_EHLO: s_ehlo (msg, len); break; case SMTP_DISCONNECTED: s_disconnected (); break; case SMTP_GREETING: s_greeting (msg, len); break; } } void Smtp::cleanup (void) { if (ask) ask_destroy (ask); ask = NULL; if (send_buf) str_destroy (send_buf); send_buf = NULL; if (error_message) str_destroy (error_message); error_message = NULL; if (marray) mail_array_destroy (marray); marray = NULL; } void Smtp::init (void) { state = SMTP_READY; marray = NULL; sent = 0; tried = 0; accepted = 0; mail = NULL; recepient = NULL; ask = NULL; send_buf = str_create (); error_message = NULL; auth = AUTH_NONE; ext_auth = 0; ext_size = 0; ext_pipeline = 0; ext_8bitmime = 0; max_size = 0; } void Smtp::on_shutdown (void) { state = SMTP_DISCONNECTED; s_disconnected (); } /**************************************************************************** * INTERFACE FUNCTIONS ****************************************************************************/ void smtp_init (void) { gethostname (hostname, HOST_NAME_MAX); hostname[HOST_NAME_MAX] = '\0'; smtp = new Smtp (); } void smtp_free_resources (void) { if (smtp) delete (smtp); smtp = NULL; } void smtp_flush_outbox (void) { if (smtp == NULL) return; if (! smtp->ready ()){ error_ (0, "%s", _("Connection in progress.")); return; } smtp->run (); } void smtp_cancel (void) { if (smtp == NULL || smtp->ready ()) return; smtp->cancel (); } /**************************************************************************** * INTERFACE CLASS BODIES ****************************************************************************/ Smtp::Smtp (void) : Net () { init (); } Smtp::~Smtp (void) { cleanup (); } void Smtp::run (void) { char *outbox; cleanup (); init (); ask = ask_select_default ("smtp_acc"); outbox = mybox_outbox (); if (outbox == NULL) return; marray = wrapbox_open_box (outbox); if (marray == NULL){ error_ (0, _("Couldn't open folder %s."), outbox); xfree (outbox); return; } xfree (outbox); mail_array_sort_smtp (marray); pick_message (); } void Smtp::cancel (void) { close (); state = SMTP_READY; } bool Smtp::ready (void) { return state == SMTP_READY; } /**************************************************************************** * * END MODULE smtp.c * ****************************************************************************/