/* 
   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 { conn.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_MAIL,              /* after response to mail from */
        SMTP_RCPT,              /* after response to rcpt to */
        SMTP_DATA,              /* after response to data */

        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
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE DATA
 ****************************************************************************/

static char *auth_str[4] = {
        NULL,
        "CRAM-MD5",
        "PLAIN",
        "LOGIN",
};

static struct {
        enum state state;       /* connection state */

        int nd;                 /* networking descriptor */

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

static char hostname[HOST_NAME_MAX + 1];

/****************************************************************************
 *    INTERFACE DATA
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
 ****************************************************************************/

static void pick_recepient (void);
static void pick_message (void);
static void pick_server (void);

static void send_ehlo (void);
static void send_helo (void);
static void send_auth(void);
static int  send_mail_from (void);
static void send_rcpt_to (void);
static void send_data (void);
static void send_quit (void);

/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTIONS
 ****************************************************************************/

static void
cleanup (int n)
{
        if (conn.state != SMTP_QUIT || n == -1){
                if (conn.marray)
                        mail_array_destroy (conn.marray);
                if (conn.ask)
                        ask_destroy (conn.ask);
                if (conn.send_buf)
                        str_destroy (conn.send_buf);

                conn.mail          = NULL;
                conn.marray        = NULL;
                conn.ask           = NULL;
                conn.send_buf      = NULL;
        }

        if (conn.error_message)
                str_destroy (conn.error_message);

        conn.state         = SMTP_READY;
        conn.nd            = -1;
        conn.sent          = 0;
        conn.tried         = 0;
        conn.accepted      = 0;
        conn.recepient     = NULL;
        conn.error_message = NULL;

        conn.auth          = AUTH_NONE;
        conn.ext_size      = 0;
        conn.ext_pipeline  = 0;
        conn.ext_8bitmime  = 0;
        conn.max_size      = 0;
}


static address_t *
next_recepient (void)
{
        int     n    = conn.tried;
        mail_t *mail = conn.mail;
        
        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;
}


static void
after_newline (char **buf, int *header)
{
        switch (**buf){

                case '.':
                        str_put_string_len (conn.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;
        }
}




static void
copy_buf (char *buf)
{
        int   header = 1;
        char *s      = buf;
        
        str_clear (conn.send_buf);
        
        while (*s){

                switch (*s){

                        case '\n':
                                str_put_string_len (conn.send_buf, "\r\n", 2);
                                s++;
                                after_newline (& s, & header);
                                break;
                        
                        default:
                                str_put_char (conn.send_buf, *s);
                                s++;
                                break;
                }
        }
        str_put_string_len (conn.send_buf, "\r\n.\r\n", 5);
}



static int
load_file (const char *fname)
{
        char *buf;
        int   size;
        FILE *fp = fopen (fname, "r");

        if (fp == NULL){
                error_ (errno, "%s", fname);
                return 1;
        }

        if (file_whole (fp, & buf, & size)){
                fclose (fp);
                error_ (errno, "%s", fname);
                return 1;
        }

        if (conn.send_buf->size < size * 2){
                str_destroy (conn.send_buf);
                conn.send_buf = str_create_size (size * 2 + 5);
        }

        copy_buf (buf);
        xfree (buf);
        return 0;
}



static void
parse_extension (char *s)
{
        char *seek;
        
        if (strstr (s, "SIZE") == s || strstr (s, "size") == s){
                conn.ext_size = 1;
                seek = s + 4;
                while (*seek && isspace (*seek))
                        seek++;
                if (isdigit (*seek))
                        conn.max_size = atoi (seek);
                return;
        }

        if (strcmp (s, "PIPELINING") == 0 || strcmp (s, "pipelining") == 0){
                conn.ext_pipeline = 1;
                return;
        }

        if (strcmp (s, "8BITMIME") == 0 || strcmp (s, "8bitmime") == 0){
                conn.ext_8bitmime = 1;
                return;
        }

        if (strstr (s, "AUTH") == s || strstr (s, "auth") == s){
                conn.ext_auth = 1;

                if (strstr (s, "CRAM-MD5") || strstr (s, "cram-md5")){
                        conn.auth = AUTH_CRAM_MD5;
                }
                else if (strstr (s, "LOGIN") || strstr (s, "login")){
                        conn.auth = AUTH_LOGIN;
                }
                else if (strstr (s, "PLAIN") || strstr (s, "plain")){
                        conn.auth = AUTH_PLAIN;
                }
                else {
                        conn.auth = AUTH_NONE;
                }
                return;
        }
}



static void
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);
}



static int
parse_answer (char *msg, int d)
{
        char *seek;
        
        if (*msg == d)
                return 0;

        if (conn.error_message)
                str_destroy (conn.error_message);

        seek = strchr (msg, '\r');
        if (seek)
                *seek = '\0';
        
        conn.error_message = str_dup (msg + 4);

        if (seek)
                *seek = '\r';
        return 1;
}


static void
report_error (const char *str)
{
        if (conn.error_message){
                error_ (0, "%s (%s)", str, conn.error_message->str);
        }
        else {
                error_ (0, "%s", str);
        }
}



static void
s_sent (char *msg, int len)
{
        char *sent = mybox_sent ();
        
        if (parse_answer (msg, '2')){
                report_error (_("couldn't transmit message"));
        }

        conn.mail->flags &= ~ FLAG_READ;
        if (wrapbox_move_mail_to (conn.mail, sent)){
                error_ (0, _("couldn't move mail to %s"), sent);
        }

        xfree (sent);
        
        PICK_NEXT ();
}



static void
s_data (char *msg, int len)
{
        char *fname;
        
        if (conn.state != SMTP_DATA){
                debug_msg (DEBUG_ERROR, "invalid state in s_data");
                return;
        }

        if (parse_answer (msg, '3')){
                report_error (_("couldn't transmit message"));
                PICK_NEXT ();
        }

        fname = wrapbox_fetch_single (conn.mail);
        if (load_file (fname)){
                xfree (fname);
                PICK_NEXT ();
        }

        xfree (fname);
        net_send_progress (conn.nd, _("sending message %d"), conn.sent + 1);
        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   TERMINATOR_RE, s_sent);
}


static void
s_rcpt_to (char *msg, int len)
{
        if (conn.state != SMTP_RCPT){
                debug_msg (DEBUG_ERROR, "invalid state in s_rcpt_to");
                return;
        }

        if (parse_answer (msg, '2')){
                report_error (_("recepient address was rejected"));
        }
        else {
                conn.accepted++;
        }

        conn.tried++;
        pick_recepient ();
}


static void
s_mail_from (char *msg, int len)
{
        if (conn.state != SMTP_MAIL){
                debug_msg (DEBUG_ERROR, "invalid state in s_mail_from");
                return;
        }
        
        if (parse_answer (msg, '2')){
                report_error (_("your email address was rejected"));
                PICK_NEXT ();
        }

        pick_recepient ();
}


static void
s_ehlo (char *msg, int len)
{
        char *user;
        char *pass;
        
        if (conn.state != SMTP_EHLO){
                debug_msg (DEBUG_ERROR, "invalid state in s_ehlo");
                return;
        }
        
        if (parse_answer (msg, '2')){
                send_helo ();
                return;
        }

        parse_extensions (msg);

        if (conn.ext_auth && conn.auth != AUTH_NONE){
                user = ask_get_field (conn.ask, "username");
                pass = ask_get_field (conn.ask, "password");

                if (user && pass){
                        send_auth ();
                        return;
                }
        }

        send_mail_from ();
}


static void
s_helo (char *msg, int len)
{
        if (conn.state != SMTP_HELO){
                debug_msg (DEBUG_ERROR, "invalid state in s_helo");
                return;
        }
        
        if (parse_answer (msg, '2')){
                PICK_NEXT ();
        }

        send_mail_from ();
}


static void
s_auth_final (char *msg, int len)
{
        if (parse_answer (msg, '2')){
                report_error (_("authorization failed"));
                PICK_NEXT ();
        }

        send_mail_from ();
}



static void
s_auth_cram_md5 (char *msg, int len)
{
        int     tmp_len;
        char   *tmp;
        char   *user;
        char   *pass;
        char    digest[16];
        char   *seek;
        str_t  *challenge;
        str_t  *answer;
        str_t  *result;
        mime_t  mime;
        int     pass_len;
        int     user_len;
        int     i;
        
        seek = strchr (msg, ' ');
        if (seek == NULL){
                error_ (0, "%s", _("invalid challenge from server"));
                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 (conn.ask, "username");
        pass      = ask_get_field (conn.ask, "password");
        pass_len  = strlen (pass);
        user_len  = strlen (user);

        hmac_md5 (challenge->str, challenge->len, 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 (conn.send_buf);
        str_sprintf (conn.send_buf, "%s\r\n", result->str);

        str_destroy (result);
        str_destroy (challenge);
        
        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   TERMINATOR_RE, s_auth_final);
}



static void
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 (conn.ask, "username");
        pass     = ask_get_field (conn.ask, "password");
        user_len = strlen (user);
        pass_len = strlen (pass);
        s_len    = 1 + user_len + 1 + pass_len + 1;
        
        answer = 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 (conn.send_buf);
        str_sprintf (conn.send_buf, "%s\r\n", encoded->str);

        str_destroy (encoded);

        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   TERMINATOR_RE, s_auth_final);
}



static void
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 failed"));
                PICK_NEXT ();
        }
        
        pass          = xstrdup (ask_get_field (conn.ask, "password"));
        pass_len      = strlen (pass);
        mime.encoding = MENC_BASE64;

        encoded = mime_encode (& mime, pass, pass_len);

        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "%s\r\n", encoded->str);

        str_destroy (encoded);

        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   TERMINATOR_RE, s_auth_final);
}



static void
s_auth_login (char *msg, int len)
{
        char   *user;
        str_t  *encoded;
        mime_t  mime;
        int     user_len;

        user          = xstrdup (ask_get_field (conn.ask, "username"));
        user_len      = strlen (user);
        mime.encoding = MENC_BASE64;

        encoded = mime_encode (& mime, user, user_len);

        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "%s\r\n", encoded->str);

        str_destroy (encoded);

        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   TERMINATOR_RE, s_auth_login_2);
}



static void
s_auth (char *msg, int len)
{
        if (parse_answer (msg, '3')){
                report_error (_("authorization request failed"));
                PICK_NEXT ();
        }

        switch (conn.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;
        }
}


static void
s_greeting (char *msg, int len)
{
        if (conn.state != SMTP_GREETING){
                debug_msg (DEBUG_ERROR, "invalid state in s_greeting");
                return;
        }
        
        if (parse_answer (msg, '2')){
                report_error (_("connection rejected"));
                PICK_NEXT ();
        }

        send_ehlo ();
}


static void
s_disconnected (void)
{
        int   secure;
        int   port;
        char *host;
        
        if (conn.state != SMTP_DISCONNECTED){
                debug_msg (DEBUG_ERROR, "invalid state in s_disconnected");
                return;
        }
        
        if (conn.mail == NULL){
                conn.state = SMTP_READY;
                return;
        }

        host   = ask_get_field (conn.ask, "server");
        secure = ask_get_field_int_default (conn.ask, "ssl", 0);
        port   = ask_get_field_int_default (conn.ask, "port",
                                            (secure) ? 465 : 25);

        if (host == NULL){
                error_ (0, "%s", _("server field in smtp_acc not defined"));
                PICK_NEXT ();
        }

        conn.nd = net_open (host, port, secure, cleanup);
        if (conn.nd == -1){
                PICK_NEXT ();
        }

        conn.state = SMTP_GREETING;
        net_recv_data (conn.nd, TERMINATOR_RE, s_greeting);
}


static void
s_quit (char *msg, int len)
{
        if (conn.state != SMTP_QUIT){
                debug_msg (DEBUG_ERROR, "invalid state in s_quit");
                return;
        }
        
        if (parse_answer (msg, '2')){
                report_error (_("does your server love you so much?"));
        }

        net_close (conn.nd);
        conn.state = SMTP_DISCONNECTED;

        s_disconnected ();
}


static void
pick_recepient (void)
{
        conn.recepient = next_recepient ();

        if (conn.recepient)
                send_rcpt_to ();
        else if (conn.accepted > 0)
                send_data ();
        else
                PICK_NEXT ();
}


static void
pick_server (void)
{
        char *name;
        
        if (conn.ask == NULL){
                error_ (0, "%s", _("no smtp_acc defined"));
                return;
        }

        name = ask_get_field (conn.ask, "name");
        if (name == NULL){
                error_ (0, "%s", _("name field in smtp_acc not defined"));
                return;
        }
        if (conn.mail->smtp == NULL){
                if (conn.mail->subject)
                        error_ (0, _("no smtp for message %d (subject: %s)"),
                                conn.sent + 1, conn.mail->subject);
                else
                        error_ (0, _("no smtp for message %d"),
                                conn.sent + 1);
                PICK_NEXT ();
        }
        
        if (conn.nd == -1){
                ask_change_where (conn.ask, "name", conn.mail->smtp);
                conn.state = SMTP_DISCONNECTED;
                s_disconnected ();
                return;
        }

        if (strcmp (conn.mail->smtp, name) == 0){
                if (send_mail_from ()){
                        PICK_NEXT ();
                }
                return;
        }

        send_quit ();
}


static void
pick_message (void)
{
        conn.tried     = 0;
        conn.accepted  = 0;
        conn.recepient = NULL;
        conn.mail      = mail_array_get (conn.marray, conn.sent);

        if (conn.mail == NULL && conn.nd == -1)
                return;

        if (conn.mail == NULL){
                send_quit ();
                return;
        }

        pick_server ();
}


static void
send_ehlo (void)
{
        conn.state = SMTP_EHLO;
        
        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "EHLO %s\r\n", hostname);
        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   TERMINATOR_RE, s_ehlo);
}


static void
send_helo (void)
{
        conn.state = SMTP_HELO;
        
        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "HELO %s\r\n", hostname);
        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   TERMINATOR_RE, s_helo);
}


static void
send_auth (void)
{
        conn.state = SMTP_AUTH;

        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "AUTH %s\r\n", auth_str[conn.auth]);
        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   TERMINATOR_RE, s_auth);
}


static int
send_mail_from (void)
{
        char *email;
        
        email = ask_get_field (conn.ask, "email");
        if (email == NULL){
                error_ (0, "%s", _("email field in smtp_acc not defined"));
                return 1;
        }

        conn.state = SMTP_MAIL;

        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "MAIL FROM:<%s>\r\n", email);
        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   TERMINATOR_RE, s_mail_from);
        return 0;
}


static void
send_rcpt_to (void)
{
        conn.state = SMTP_RCPT;

        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "RCPT TO:<%s>\r\n",
                     conn.recepient->email);
        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   TERMINATOR_RE, s_rcpt_to);
}


static void
send_data (void)
{
        conn.state = SMTP_DATA;
        net_combo (conn.nd, "DATA\r\n", 6, TERMINATOR_RE, s_data);
}


static void
send_quit (void)
{
        conn.state = SMTP_QUIT;
        net_combo (conn.nd, "QUIT\r\n", 6, TERMINATOR_RE, s_quit);
}

/****************************************************************************
 *    INTERFACE FUNCTIONS
 ****************************************************************************/

void
smtp_init (void)
{
        conn.state         = SMTP_READY;
        conn.nd            = -1;
        conn.marray        = NULL;
        conn.sent          = 0;
        conn.tried         = 0;
        conn.accepted      = 0;
        conn.mail          = NULL;
        conn.recepient     = NULL;
        conn.ask           = NULL;
        conn.send_buf      = NULL;
        conn.error_message = NULL;

        conn.ext_size      = 0;
        conn.ext_pipeline  = 0;
        conn.ext_8bitmime  = 0;
        conn.max_size      = 0;

        gethostname (hostname, HOST_NAME_MAX);
        hostname[HOST_NAME_MAX] = '\0';
}



void
smtp_free_resources (void)
{
        if (conn.nd != -1)
                net_close (conn.nd);
        else
                cleanup (-1);
}


void
smtp_flush_outbox (void)
{
        char *outbox;
        
        if (conn.state != SMTP_READY || conn.nd != -1){
                error_ (0, "%s", _("connection in progress"));
                return;
        }

        outbox = mybox_outbox ();

        if (conn.marray)
                mail_array_destroy (conn.marray);

        conn.marray = wrapbox_open_box (outbox);
        if (conn.marray == NULL){
                error_ (0, _("couldn't open %s"), outbox);
                xfree (outbox);
                return;
        }
        xfree (outbox);

        mail_array_sort_smtp (conn.marray);

        if (conn.ask)
                ask_destroy (conn.ask);
        
        conn.ask      = ask_select_default ("smtp_acc");
        conn.mail     = NULL;
        conn.sent     = 0;
        conn.send_buf = str_create ();

        pick_message ();
}

/****************************************************************************
 *    INTERFACE CLASS BODIES
 ****************************************************************************/
/****************************************************************************
 *
 *    END MODULE smtp.c
 *
 ****************************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1