/*
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 <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#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
*
****************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1