/*
**  SMTP routines for mailsend - a simple mail sender via SMTP
**
**  Limitations and Comments:
**    SMTP RF[C] RFC-821
**    Also look at: TCP/IP Illustraged Vol 1 by Richard Stevens
**
**    Written mainly for NT, Unix has this kind of tools in hundreds.
**
**  Development History:
**      who                  when           why
**      muquit@muquit.com    Mar-26-2001    first cut
*/

#include "mailsend.h"

static char buf[BUFSIZ];
static int  bufsz = sizeof(buf) - 1;
static int  break_out=0;

static char smtp_line[BUFSIZ];
static char smtp_errbuf[BUFSIZ];
static int  smtp_code;
static int  smtp_sep;
static int  s_esmtp=0;

#ifdef WINNT
static BOOL 
    WINAPI CntrlHandler(DWORD CtrlEvent);
#else
#define closesocket close
#endif /* WINNT */

void smtpDisconnect(void)
{
    msock_close();
}
/* connect to SMTP server and returns the socket fd */
static SOCKET smtpConnect(char *smtp_server,int port)
{
    SOCKET
        sfd;

    if (g_use_protocol == MSOCK_USE_IPV4)
    {
        showVerbose("Forcing to use IPv4 address of SMTP server\n");
    }
    else if (g_use_protocol == MSOCK_USE_IPV6)
    {
        showVerbose("Forcing to use IPv6 address of SMTP server\n");
    }
    else
    {
        showVerbose("Will detect IPv4 or IPv6 automatically\n");
    }
    
    sfd=clientSocket(g_use_protocol, smtp_server,port, g_connect_timeout);
    if (sfd == INVALID_SOCKET)
    {
        errorMsg("Could not connect to SMTP server \"%s\" at port %d",
                smtp_server,port);
        return (INVALID_SOCKET);
    }

    /* set the socket to msock lib's static place, not thread safe*/
    msock_set_socket(sfd);

    return (sfd);
}

int read_smtp_multi_lines(void)
{
    int
        rc,
        lcnt = 0;
    if (smtp_sep == A_DASH)
    {
        for (;;)
        {
            rc=read_smtp_line();
            if (rc < 0)
                break;
            lcnt++;
            if (lcnt >= 100)
            {
                errorMsg("Too many lines from server\n");
                rc=(-1);
                goto ExitProcessing;
            }
            if (smtp_sep != A_DASH)
                break;
        }
    }
    smtp_sep = A_SPACE;
    return(0);
ExitProcessing:
    return(1);
}

/*
 * sfd   socket 
 *
 * return 0 on success, -1 on failure
 *
 * populates globals: smtp_code,smtp_line and smtp_errbuf on error
 *
 */
int read_smtp_line(void)
{
    int
        rc=(-1),
        n;

    char
        tbuf[BUFSIZ],
        lbuf[BUFSIZ];

    memset(smtp_line,0,sizeof(smtp_line));
    memset(smtp_errbuf,0,sizeof(smtp_errbuf));
    smtp_code=(-1);

    memset(lbuf,0,sizeof(lbuf));
    /* read a line */
    n=msock_gets(lbuf,sizeof(lbuf)-1, g_read_timeout);
    if (n < 3 )
    {
        /*errorMsg("Error reading SMTP line, read %d bytes",n);*/
        return(-1);
    }
    showVerbose("[S] %s\n",lbuf);
    if (n >= 5)
    {
        memset(tbuf,0,sizeof(tbuf));
        memcpy(tbuf,lbuf,3);
        smtp_code=atoi(tbuf);

        smtp_sep=lbuf[3];

        (void) snprintf(smtp_line,sizeof(smtp_line)-1,"%s",lbuf + 4);
        rc=0;
/*
        (void) fprintf(stderr," Line: \"%s\"\n",lbuf);
        (void) fprintf(stderr," Code: \"%d\"\n",smtp_code);
        (void) fprintf(stderr," Sep: '%c'\n",smtp_sep);
        (void) fprintf(stderr," line: \"%s\"\n",smtp_line);
*/
    }
    else
    {
        (void) snprintf(smtp_errbuf,sizeof(smtp_errbuf)-1,"%s",lbuf);
    }
    

    return(rc);
}

int smtp_start_tls(int sfd)
{
    int
        rc=(-1);
#ifdef HAVE_OPENSSL
    SSL
        *ssl=NULL;
#endif /* HAVE_OPENSSL */
    memset(buf,0,sizeof(buf));
    (void) snprintf(buf,sizeof(buf)-1,"%s\r\n","STARTTLS");
    showVerbose("[C] %s",buf);
    msock_puts(buf);
    rc=read_smtp_line();
    if (smtp_code != 220)
    {
        errorMsg("Unknown STARTTLS response code %d\n",smtp_code);
        return(-1);
    }
#ifdef HAVE_OPENSSL
    ssl=msock_get_ssl();
    if (ssl)
    {
        if (!SSL_set_fd(ssl,sfd))
        {
            errorMsg("failed to set socket to SSL\n");
            return(-1);
        }
        /* must set back to msock's static */
        msock_set_ssl(ssl);
        rc=SSL_connect(ssl);
        if (rc < 1)
        {
            errorMsg("SSL connection failed\n");
            ERR_print_errors_fp(stderr);
            return(-1);
        }
        print_cert_info(ssl);
        /* tell msock everything is ssl after that */
        msock_turn_ssl_on();
        rc=0;
    }
    else
    {
        errorMsg("Could not start STARTTLS, SSL not initialized properly");
        rc=(-1);
    }
#else
    errorMsg("Not Compiled with OpenSSL, will not try STARTTLS");
    rc=(-1);
#endif /*HAVE_OPENSSL */
    return(rc);
}

/* SMTP: HELO */
static int say_helo(char *helo_domain)
{
    int
        cnt=0,
        rc;

    (void) snprintf(buf,sizeof(buf)-1,"%s %s\r\n",
                    s_esmtp ? "EHLO" : "HELO",helo_domain);
    showVerbose("[C] %s",buf);

    /* send */
    msock_puts(buf);
    rc=read_smtp_line();
    if (smtp_code != 250)
    {
        errorMsg("%s failed", s_esmtp ? "EHLO" : "HELO");
        return(-1);
    }
    /* read all the capabilities if separator is - */
    if (smtp_sep == A_DASH)
    {
        for (;;)
        {
            rc=read_smtp_line();
            if (rc == 0)
                add_server_cap_to_list(smtp_line);
            cnt++;
            if (cnt >= 1000)
                break;
            if (rc < 0 || smtp_sep != A_DASH)
                break;
        }
    }
    smtp_sep = A_SPACE;

    return(rc);
}


/* SMTP: MAIL FROM */
static int smtp_MAIL_FROM(char *from)
{
    memset(buf,0,sizeof(buf));
    (void) snprintf(buf,sizeof(buf)-1,"MAIL FROM: <%s>\r\n",from);
    showVerbose("[C] %s",buf);

    msock_puts(buf);
    read_smtp_line();
    if (smtp_code != 250)
    {
        errorMsg("MAIL FROM failed: '%d %s'",smtp_code,smtp_line);
        return(-1);
    }
    return(0);
}

/* SMTP: quit */
static int smtp_QUIT(void)
{
    showVerbose("[C] QUIT\r\n");
    msock_puts("QUIT\r\n");
    read_smtp_line();
    /*
    ** google does not seem to write anything back in response to QUIT
    ** command. I'll ignore it anyway
    */
    return(0);
}

/* SMTP: RSET */
/* aborts current mail transaction and cause both ends to reset */
static int smtp_RSET()
{
    msock_puts("RSET\r\n");
    return(read_smtp_line());
}



/* SMTP: RCPT TO */
static int smtp_RCPT_TO(void)
{
    Sll
        *l,
        *al;

    Address
        *a;

    char
        *x;

    int
        rc;

    al=getAddressList();
    
    for (l=al; l; l=l->next)
    {
        a=(Address *) l->data;
        if (! a)
            return(-1);
        if (! a->address)
            return(-1);

        memset(buf,0,sizeof(buf));
        x=getenv("NOTIFY_RCPT");
        if (x != NULL)
        {
            /* MS Exchange has it */
            showVerbose("NOTIFY_RCPT=%s\n",x);
            (void) snprintf(buf,sizeof(buf)-1,"RCPT TO: %s %s\r\n",
                            a->address,x);
        }
        else
        {
            (void) snprintf(buf,sizeof(buf)-1,"RCPT TO: <%s>\r\n",a->address);
        }

        showVerbose("[C] %s",buf);
        
        msock_puts(buf);
        rc=read_smtp_line();
        if (rc == 0)
        {
            if (smtp_code != 250)
            {
                errorMsg("RCPT TO: <%s> failed '%d:%s'\n",
                        a->address,smtp_code,smtp_line);
                smtp_RSET();
                return(-1);
            }
        }
    }
    return (0);

}

/* SMTP: DATA */
static int smtp_DATA(void)
{
    int
        rc;

    msock_puts("DATA\r\n");
    showVerbose("[C] DATA\r\n");

    rc=read_smtp_line();
    if (rc == 0)
    {
        if (smtp_code != 354)
        {
            errorMsg("DATA failed: '%d %s'\n",smtp_code,smtp_line);
            return(-1);
        }
    }
    return(0);
}

/* SMTP: EOM */
/* return 0 on success, -1 on failure */
int smtpEom(int sfd)
{
    int
        rc;

    msock_puts("\r\n.\r\n");

    showVerbose("\r\n[C] .\r\n");

    /*
    ** Bug# 1 
    ** we want to see smtp code 250 now
    ** if mail is too big, it can mail with 552 message too large
    */
    rc = read_smtp_line();
    if (smtp_code != 250)
    {
        read_smtp_multi_lines();
        errorMsg("Expected smtp code 250, got %d\n",smtp_code);
        rc = (-1);
    }

    return(rc);
}

void doCleanup(void)
{
    smtpDisconnect();
}

#ifdef WINNT
/*
** Handle Ctrl+C 
*/
static BOOL WINAPI CntrlHandler(DWORD CtrlEvent)
{
    break_out=0;

    switch (CtrlEvent)
    {
        case CTRL_C_EVENT:
        {
            break_out=1;
            (void) fprintf(stderr,"\nNot sending mail. Exiting.......\n");
            exit_error();
            break;
        }
    }

    return (TRUE);
}
#endif /* WINNT */

int write_to_socket(char *str)
{
    int
        n;
    if (str == NULL)
    {
        return (-1);
    }
    n = msock_puts(str);
    /*showVerbose("%d bytes: %s\n",n, str);*/
    return(n);
}

/*
** return 0 on success
*/
int encode2base64andwrite2socket(const char *str)
{
    FILE
        *tfp1 = NULL,
        *tfp2 = NULL;
    char
        mbuf[1000],
        oneline_tempfile1[MUTILS_PATH_MAX],
        oneline_tempfile2[MUTILS_PATH_MAX];

    memset(oneline_tempfile1, 0, sizeof(oneline_tempfile1));
    /* write the text to a tmp file */
    tfp1 = mutils_get_tempfileFP(oneline_tempfile1,
            sizeof(oneline_tempfile1)-1);
    if (tfp1 == NULL)
    {
        errorMsg("%s (%d) - Could not open temp file1 for writing (%s)",
                MFL,
                ERR_STR);
        return (-1);
    }
    (void) fprintf(tfp1,"%s",str);
    (void) fclose(tfp1);

    /* open another tmp file to write the base64 of the first tmp file to */
    memset(oneline_tempfile2, 0, sizeof(oneline_tempfile2));
    tfp2 = mutils_get_tempfileFP(oneline_tempfile2,
        sizeof(oneline_tempfile2)-1);
    showVerbose("Oneline temp file2: * %s\n",oneline_tempfile2);
    if (tfp2 == NULL)
    {
        errorMsg("%s (%d) - Could not open temp file2 for writing (%s)",
            MFL,
            ERR_STR);
        return (-1);
    }

    tfp1 = fopen(oneline_tempfile1,"rb");
    if (tfp1 == NULL)
    {
        errorMsg("%s (%d) - Could not open temp file for reading (%s)",
            MFL,
            ERR_STR);
        return(-1);
    }

    mutilsBase64Encode(tfp1,tfp2);
    (void) fclose(tfp1);
    (void) fclose(tfp2);

    /* open the file with base64 and write the content to socket */
    tfp2 = fopen(oneline_tempfile2,"r");
    if (tfp2 == NULL)
    {
        errorMsg("%s (%d) - Could not open temp file for reading (%s)",
            MFL,
            ERR_STR);
        return(-1);
    }
    while(fgets(mbuf, sizeof(mbuf)-1, tfp2))
    {
        write_to_socket(mbuf);
        if (g_show_attachment_in_log)
        {
            showVerbose("[C] %s",mbuf);
        }
    }
    (void) fclose(tfp2);
    unlink(oneline_tempfile1);
    unlink(oneline_tempfile2);
 
    return(0);
}

/*
** send one line messages, each one is an inline attachment
** return 0 if mail is sent, -1 otherwise
*/
int process_oneline_messages(const char *boundary)
{
    Attachment
        *a = NULL;
    Sll
        *l,
        *oneline_attachment_list;

    oneline_attachment_list = get_oneline_attachment_list();
    if (oneline_attachment_list == NULL)
    {
        return(0);
    }
    print_oneline_attachment_list();

    for (l = oneline_attachment_list; l; l = l->next)
    {
        a = (Attachment *) l->data;
        (void) snprintf(buf, bufsz, "\r\n--%s\r\n",boundary);
        write_to_socket(buf);

        if (strcmp(a->charset,"none") != 0)
        {
            (void) snprintf(buf, bufsz, "Content-Type: %s; charset=%s\r\n",
                a->mime_type,
                a->charset);
        }
        else
        {
            (void) snprintf(buf, bufsz, "Content-Type: %s\r\n",a->mime_type);
        }
        write_to_socket(buf);

        (void) strcpy(buf,"Content-Disposition: inline\r\n");
        write_to_socket(buf);

        /* add encoding type if needed */
        if (strncmp(a->content_transfer_encoding,"none",4) != 0)
        {
            (void) snprintf(buf, bufsz, "Content-Transfer-Encoding: %s\r\n\r\n",
                    a->content_transfer_encoding);
            write_to_socket(buf);
        }

        
        if (strncmp(a->content_transfer_encoding,"base64",6) == 0)
        {
            /* encode the mssage to base 64 and write to socket */
            encode2base64andwrite2socket(a->oneline_msg);
        }
        else
        {
            /*
            ** no encoding type, so last \r\n was no added, we must
            ** add it or some mailer will thing the mail is invalid
            */
            if (strncmp(a->content_transfer_encoding,"none",4) == 0)
            {
                write_to_socket("\r\n");
            }
            write_to_socket(a->oneline_msg);
            if (g_show_attachment_in_log)
            {
                showVerbose("[C] %s\n",a->oneline_msg);

            }
        }
        write_to_socket("\r\n");
    }
    return(0);
}

/*
** include the content of the file as body of the message
** No encoding will be done.
*/
int include_msg_body(void)
{
    Sll
        *l,
        *msg_body_attachment_head;

    FILE
        *fp = NULL;

    Attachment
        *a;

    msg_body_attachment_head = get_msg_body_attachment_list();
    if (msg_body_attachment_head == NULL)
    {
        return(-1);
    }

    l = msg_body_attachment_head;
    a = (Attachment *) l->data;

    (void) snprintf(buf,bufsz,"MIME-Version: 1.0\r\n");
    write_to_socket(buf);

    if (strcmp(a->charset,"none") != 0)
    {
        (void) snprintf(buf,bufsz,"Content-Type: %s; charset=%s\r\n\r\n",
                a->mime_type,
                a->charset);
    }
    else
    {
        (void) snprintf(buf,bufsz,"Content-Type: %s\r\n\r\n",a->mime_type);
    }
    write_to_socket(buf);

    fp=fopen(a->file_path,"r");
    if (fp == (FILE *) NULL)
    {
        errorMsg("Could not open message body file: %s",a->file_path);
        return (-1);
    } 

    while (fgets(buf,bufsz,fp))
    {
        write_to_socket(buf);
        if (g_show_attachment_in_log)
        {
            showVerbose("[C] %s",buf); 
        }
    }
    (void) fclose(fp);

    (void) snprintf(buf,bufsz,"\r\n\r\n");
    msock_puts(buf);
    showVerbose(buf);
    return(0);
}

int include_image(void)
{
    return(0);
}

static void cleanup_attachment(Attachment *a)
{
    if (a == NULL)
        return;
    if (a->fp_read)
    {
        (void) fclose(a->fp_read);
    }
    if (*a->mime_tmpfile != '\0')
    {
        unlink(a->mime_tmpfile);
    }
}


/*
** return the attachment with fp_read member open.
** If encoding type requires some kind of encoding,
** mime_tmpfile will contain the file which holds the
** encoded content. The caller must close the file pointer
** and unlik the tmp_filename
** return NULL on error
*/
static Attachment *get_encoded_attachment(Attachment *a)
{
    FILE
        *fp_read = NULL,
        *tfp_write = NULL;
    int
        tempfile_opened = 0;

    if (strncmp(a->content_transfer_encoding, "base64", 6) == 0)
    {
        tfp_write = mutils_get_tempfileFP(a->mime_tmpfile,sizeof(a->mime_tmpfile)-1);
        if (tfp_write == NULL)
        {
            errorMsg("%s (%d) - Could not create temp file for MIME (%s)",
                    MFL,
                    ERR_STR);
            return(NULL);
        }
        tempfile_opened = 1;
        showVerbose("%s (%d) - MIME temp file: %s created successfully, FILE pointer=%x\n",
                MFL,
                a->mime_tmpfile,
                tfp_write);

        /* open the file to attach */
        fp_read = fopen(a->file_path,"rb");
        if (fp_read == (FILE *) NULL)
        {
            errorMsg("%s (%d) - Could not open file for %s reading (%s)",
                    MFL,
                    a->file_path,
                    ERR_STR);
            goto ExitProcessing;
        }

        showVerbose("%s (%d) - Writing Content to FILE pointer: %x\n",
                MFL,
                tfp_write);
        /* write base64 content to tmp file */
        mutilsBase64Encode(fp_read,tfp_write);
        (void) fclose(fp_read);
        fp_read = NULL;

        (void) fclose(tfp_write);
        tfp_write = NULL;
        a->fp_read = fopen(a->mime_tmpfile, "r");
        if (a->fp_read == (FILE *) NULL)
        {
            errorMsg("%s (%d) - Could not open file for %s reading (%s)",
                    MFL,
                    a->file_path,
                    ERR_STR);
            goto ExitProcessing;
        }
        if (tempfile_opened)
        {
            unlink(a->mime_tmpfile);
        }
        return(a);
    }
    /* no tmp file */
    memset(a->mime_tmpfile, 0, sizeof(a->mime_tmpfile));
    
    /* open the attachment. caller must close it */
    a->fp_read = fopen(a->file_path, "r");
    if (a->fp_read == (FILE *) NULL)
    {
        errorMsg("%s (%d) - Could not open file for %s reading (%s)",
                MFL,
                a->file_path,
                ERR_STR);
        return (NULL);
    }
    return(a);

ExitProcessing:
    if (tempfile_opened)
    {
        unlink(a->mime_tmpfile);
    }
    return(NULL);
}

/*
** write MIME headers, encode attachment if needed
** and write to socket
** returns 0 on success -1 on failure
*/
int send_attachment(Attachment *a, const char *boundary)
{
    Attachment
        *encoded_attachment;

    encoded_attachment = get_encoded_attachment(a);
    if (encoded_attachment == NULL)
    {
        errorMsg("%s (%d) - Could not encode attachment %s",
                MFL,
                a->file_path);
        return(-1);
    }

    (void) snprintf(buf, bufsz,"\r\n--%s\r\n",boundary);
    write_to_socket(buf);

    if (a->charset && strncmp(a->charset,"none", 4) != 0)
    {
        (void) snprintf(buf, bufsz, "Content-Type: %s; charset=%s\r\n",
                a->mime_type,
                a->charset);
    }
    else
    {
        if (a->attachment_name)
        {
            (void) snprintf(buf, bufsz, "Content-Type: %s; name=\"%s\"\r\n",
                    a->mime_type,
                    a->attachment_name);
        }
        else
        {
            (void) snprintf(buf, bufsz, "Content-Type: %s\r\n",a->mime_type);
        }
    }
    write_to_socket(buf);
    
    if (a->content_id == NULL)
    {
        if (strncmp(a->content_disposition, "inline", 6) == 0)
        {
            (void) snprintf(buf, bufsz, "Content-Disposition: %s\r\n",a->content_disposition);
        }
        else
        {
            if (a->attachment_name)
            {
                (void) snprintf(buf, bufsz, "Content-Disposition: %s; filename=\"%s\"\r\n",
                    a->content_disposition,
                    a->attachment_name);

            }
            else if (a->file_name)
            {
                (void) snprintf(buf, bufsz, "Content-Disposition: %s; filename=\"%s\"\r\n",
                    a->content_disposition,
                    a->file_name);
            }
            else
            {
                (void) snprintf(buf, bufsz, "Content-Disposition: %s\r\n",a->content_disposition);
            }
        }
        write_to_socket(buf);
    }

    if (a->content_id)
    {
        (void) snprintf(buf, bufsz, "Content-ID: <%s>\r\n",a->content_id);
        write_to_socket(buf);
        (void) snprintf(buf, bufsz, "X-Attachment-Id: %s\r\n",a->content_id);
        write_to_socket(buf);
    }

    if (strncmp(a->content_transfer_encoding,"none",4) != 0)
    {
        (void) snprintf(buf, bufsz, "Content-Transfer-Encoding: %s\r\n\r\n",
                a->content_transfer_encoding);
        write_to_socket(buf);
    }
    else
    {
        write_to_socket("\r\n");
    }

    /* our FILE pointer is already open and read to read from */
    while (fgets(buf, bufsz, a->fp_read))
    {
        write_to_socket(buf);
    }
    /* close file, remove tmp file if needed */
    cleanup_attachment(a);
    return(0);
}

int process_attachments(const char *boundary)
{
    Attachment
        *a;

    Sll
        *attachment_list,
        *al;

    int
        rc;

    attachment_list = get_attachment_list();
    if (attachment_list == NULL)
    {
        return(0);
    }

    for (al=attachment_list; al; al=al->next)
    {
        a=(Attachment *) al->data;
        if (a == NULL)
            continue;

        rc = send_attachment(a, boundary);
        if (rc == -1)
        {
            errorMsg("%s (%d) - failed to send attachment %s\n",
                    MFL,
                    a->file_path);
            return(-1);
        }
    }
    
    (void) snprintf(buf,sizeof(buf)-1,"\r\n--%s--\r\n",boundary);
    msock_puts(buf);
    showVerbose(buf);

    return(0);
}

/*
** Print multipart/mixed or multipart/related header
** if mixed and embedded images are specified, make
** to use multipart/alternative for the embedded images.
*/
int print_content_type_header(const char *boundary)
{
    Sll
        *oneline_attachment_list,
        *attachment_list,
        *embed_image_list;

    (void) snprintf(buf, bufsz,"MIME-Version: 1.0\r\n");
    write_to_socket(buf);

    if (*g_content_type!='\0')
    {
      (void) snprintf(buf,sizeof(buf)-1,"Content-Type: %s; boundary=\"%s\"\r\n",
              g_content_type,
              boundary);
      write_to_socket(buf);
      return(0);
    }

    oneline_attachment_list = get_oneline_attachment_list();
    attachment_list = get_attachment_list();
    embed_image_list = get_embed_image_attachment_list();
    if (oneline_attachment_list || attachment_list)
    {
        (void) snprintf(buf,sizeof(buf)-1,
                "Content-Type: multipart/mixed; boundary=\"%s\"\r\n", boundary);
        write_to_socket(buf);
        return(0);
    }
    if (embed_image_list)
    {
        (void) snprintf(buf,sizeof(buf)-1,
                "Content-Type: multipart/related; boundary=\"%s\"\r\n", boundary);
        write_to_socket(buf);
        return(0);
    }
    write_to_socket("\r\n");
    return(0);
}

int process_embeded_images(const char *boundary)
{
    char
        *ib,
        *b,
        related[17],
        cid[17],
        tbuf[24],
        alternative[17];

    Attachment
        *a;

    Sll
        *il,
        *oneline_attachment_list,
        *attachment_list,
        *embed_image_list;

    int
        rc,
        ic = 1;

    oneline_attachment_list = get_oneline_attachment_list();
    attachment_list = get_attachment_list();

    embed_image_list = get_embed_image_attachment_list();
    if (embed_image_list == NULL)
    {
        return(0);
    }
    memset(related, 0, sizeof(related));
    memset(alternative, 0, sizeof(alternative));

    mutilsGenerateMIMEBoundary(related,sizeof(related));
    mutilsGenerateMIMEBoundary(alternative,sizeof(alternative));
    b = boundary;
    ib = boundary;

    (void) snprintf(buf, bufsz, "\r\n--%s\r\n",boundary);
    write_to_socket(buf);

    if (attachment_list || oneline_attachment_list)
    {
        b = related;
        ib = b;
        (void) snprintf(buf, bufsz, "Content-Type: multipart/related; boundary=%s\r\n",b);
        write_to_socket(buf);

        (void) snprintf(buf, bufsz, "Content-Type: multipart/alternative; boundary=%s\r\n\r\n",
                alternative);
        write_to_socket(buf);

        (void) snprintf(buf, bufsz, "\r\n--%s\r\n",b);
        write_to_socket(buf);
    }
    else
    {
        ib = alternative;
        (void) snprintf(buf, bufsz, "Content-Type: multipart/alternative; boundary=%s\r\n\r\n",
                alternative);
        write_to_socket(buf);

        (void) snprintf(buf, bufsz, "\r\n--%s\r\n",alternative);
        write_to_socket(buf);
    }

    write_to_socket("Content-Type: text/html; charset=ISO-8859-1\r\n\r\n");

    /* write the img tags with cid */
    embed_image_list = get_embed_image_attachment_list();
    for (il = embed_image_list; il; il = il->next)
    {
        a = (Attachment *) il->data;
        mutilsGenerateMIMEBoundary(cid,sizeof(cid));
        (void) snprintf(tbuf,sizeof(tbuf)-1,"ii%d_%s",ic,cid);
        a->content_id = xStrdup(tbuf);
        (void) snprintf(buf, bufsz, "<img src=\"cid:%s\" alt=\"inline image %d\"><br>\n",
                tbuf,
                ic);
        write_to_socket(buf);
        ic++;
    }
    write_to_socket("\r\n");
    (void) snprintf(buf, bufsz, "\r\n--%s--\r\n",ib); 
    write_to_socket(buf);

    for (il = embed_image_list; il; il = il->next)
    {
        a = (Attachment *) il->data;
        if (a == NULL)
            continue;
        rc = send_attachment(a,b);
        RETURN_IF_NOT_ZERO(rc);
    }
    (void) snprintf(buf, bufsz, "\r\n--%s--\r\n",b);
    write_to_socket(buf);
    return(0);
}

static void print_end_boundary(const char *boundary)
{
    if (get_attachment_list() == NULL)
    {
        (void) snprintf(buf,sizeof(buf)-1,"\r\n--%s--\r\n",boundary);
        msock_puts(buf);
        showVerbose(buf);
        return;
    }
    /* process_attachments(boundary) already printed the end bounday */
}

/* SMTP: mail */
static int smtpMail(int sfd,char *to,char *cc,char *bcc,char *from,char *rrr,char *rt,
                    char *subject,char *attach_file,char *msg_body_file,
                    char *the_msg,int is_mime,int add_dateh)
{
    char
        *os="Unix",
        boundary[17],
        related[17],
        alternative[17],
        mbuf[1024];

    int
        newline_before;

    Sll
        *oneline_attachment_list,
        *attachment_list,
        *embed_image_list;

#ifdef WINNT
    os="Windows";
#else
    os="Unix";
#endif /* WINNT */

    memset(boundary, 0, sizeof(boundary));
    memset(related, 0, sizeof(related));
    memset(alternative, 0, sizeof(alternative));

    attachment_list=get_attachment_list();
    embed_image_list = get_embed_image_attachment_list();
    oneline_attachment_list = get_oneline_attachment_list();
    if (attachment_list || embed_image_list || oneline_attachment_list)
    {
        is_mime=1;
    }

    if (subject)
    {
        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"Subject: %s\r\n",subject);

        msock_puts(buf);

        showVerbose(buf);
    }

    /* headers */
    if (from)
    {
        memset(buf,0,sizeof(buf));
        if (*g_from_name != '\0')
        {
            /* Name in From: */

            memset(buf,0,sizeof(buf));
            (void) snprintf(buf,sizeof(buf)-1,"From: %s <%s>\r\n",
                            g_from_name,from);
        }
        else
        {
            (void) snprintf(buf,sizeof(buf)-1,"From: %s\r\n",from);
        }
        msock_puts(buf);

        showVerbose(buf);
    }

    if (add_dateh)
    {
        /* add Date: header */
        char
            datebuf[65];

        memset(datebuf,0,sizeof(datebuf));
        if (rfc822_date(time(NULL),datebuf,sizeof(datebuf)-1) == 0)
        {
            memset(buf,0,sizeof(buf));
            (void) snprintf(buf,sizeof(buf)-1,"Date: %s\r\n",datebuf);
            msock_puts(buf);

            showVerbose(buf);
        }
    }
    
    if (to)
    {
        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"To: %s\r\n",to);
        msock_puts(buf);

        showVerbose(buf);

    }

    if (cc)
    {
        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"Cc: %s\r\n",cc);
        msock_puts(buf);
        showVerbose(buf);
    }

    /*
    if (bcc)
    {
        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"Bcc: %s\r\n",bcc);
        msock_puts(buf);

        showVerbose(buf);
    }
    */

    if (rt != NULL)
    {
        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"Reply-To: %s\r\n",rt);
        msock_puts(buf);
        showVerbose(buf);

    }
    if (rrr != NULL)
    {
        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"Disposition-Notification-To: %s\r\n",rrr);
        msock_puts(buf);
        showVerbose(buf);
    }

    /* add custom headers if any. No verification is done */
    {
        Sll 
            *l,
            *custom_header_list;
        custom_header_list = get_custom_header_list();
        if (custom_header_list)
        {
            for (l = custom_header_list; l; l = l->next)
            {
                if (l->data)
                {
                    msock_puts((char *) l->data);
                    msock_puts("\r\n");
                    showVerbose((char *) l->data);
                    showVerbose("\r\n");
                }
            }
        }
    }


    memset(buf,0,sizeof(buf));
    (void) snprintf(buf,sizeof(buf)-1,"X-Mailer: %s (%s)\r\n",MAILSEND_VERSION,os);
    msock_puts(buf);
    showVerbose(buf);

    memset(buf,0,sizeof(buf));
    (void) snprintf(buf,sizeof(buf)-1,"X-Copyright: %s\r\n",NO_SPAM_STATEMENT);
    msock_puts(buf);
    showVerbose(buf);

    if (is_mime)
    {
        int
            rc;
        srand(time(NULL));
        memset(boundary,0,sizeof(boundary));
        mutilsGenerateMIMEBoundary(boundary,sizeof(boundary));

        /* if msg body file is specified, include and return */
        if (msg_body_file)
        {
            return (include_msg_body());
        }

        rc = print_content_type_header(boundary);
        RETURN_IF_NOT_ZERO(rc);

        rc = process_oneline_messages(boundary);
        RETURN_IF_NOT_ZERO(rc);

        rc = process_embeded_images(boundary);
        RETURN_IF_NOT_ZERO(rc);

        rc = process_attachments(boundary);
        RETURN_IF_NOT_ZERO(rc);

        /*
        ** if there were other kind of MIME types but no attachments,
        ** we have to print the last boundary 
        */
        print_end_boundary(boundary);

        /* handle MIME attachments ends */
        goto done;
    } /* is_mime */

    /* mail body */
    if (attach_file == NULL && the_msg == NULL) /* read from stdin */
    {

        /* if stdin is a terminal, print the instruction */
        if (isInConsole(_fileno(stdin)))
        {
            (void) printf("=========================================================================\n");
            (void) printf("Type . in a new line and press Enter to end the message, CTRL+C to abort\n");
            (void) printf("=========================================================================\n");
        }

#ifdef WINNT
        SetConsoleCtrlHandler(CntrlHandler,TRUE);
#endif /* WINNT */

        newline_before=1;
        msock_puts("\r\n"); /* RFC822 sec 3.1 */
        showVerbose("\r\n");
        while (fgets(mbuf,sizeof(mbuf)-1,stdin) && (break_out == 0))
        {
            if (newline_before && *mbuf == '.')
            {
                break;
            }
            else
            {
                int
                    len;
                /* vinicio qmail fix */
                len=strlen(mbuf);
                if (mbuf[len-1] != '\n')
                    strcat(mbuf,"\r\n");
                else
                {
                   mbuf[--len]='\0';
                   strcat(mbuf,"\r\n");
                }
                /* vinicio qmail fix */
                msock_puts(mbuf);
                showVerbose("[C] %s",mbuf);
            }
            newline_before=(*mbuf != '\0' && mbuf[strlen(mbuf)-1] == '\n');
            if (break_out == 1)
            {
                (void) fprintf(stderr," Breaking out\n");
                return (0);
            }
        }
    }
done:

    return (0);
}

/* greeting can be multi line */
static int read_greetings(void)
{
    int
        lcnt=0,
        rc=(-1);

    rc=read_smtp_line();
    if (rc < 0)
        goto cleanup;

    s_esmtp=g_esmtp; /* if forced with -ehlo */

    if (smtp_code != 220)
    {
        read_smtp_multi_lines();
        errorMsg("Expected smtp code 220, got %d\n",smtp_code);
        rc=(-1);
        goto cleanup;
    }
    if (mutilsStristr(smtp_line,"ESMTP"))
    {
        s_esmtp=1;
    }
    /* greeting can be multi-line */
    if (smtp_sep == A_DASH)
    {
        for (;;)
        {
            rc=read_smtp_line();
            if (rc < 0)
                break;
            lcnt++;
            if (lcnt >= 100)
            {
                errorMsg("Too many greeting lines\n");
                rc=(-1);
                goto cleanup;
            }
            if (mutilsStristr(smtp_line,"ESMTP"))
            {
                s_esmtp=1;
            }
            if (smtp_sep != A_DASH)
                break;
        }
    }
cleanup:
    smtp_sep = A_SPACE;
    return(rc);
}

static int turn_on_raw_ssl(SOCKET sfd)
{
#ifdef HAVE_OPENSSL
    int
        rc=(-1);

    SSL
        *ssl;

    ssl=msock_get_ssl();
    if (ssl)
    {
        if (!SSL_set_fd(ssl,sfd))
        {
            errorMsg("turn_on_raw_ssl: failed to set socket %d to SSL\n",sfd);
            return(-1);
        }
        /* must set back to msock's static */
        msock_set_ssl(ssl);
        rc=SSL_connect(ssl);
        if (rc < 1)
        {
            errorMsg("turn_on_raw_ssl: SSL connection failed\n");
            ERR_print_errors_fp(stderr);
            return(-1);
        }
        print_cert_info(ssl);
        /* tell msock everything is ssl after that */
        msock_turn_ssl_on();
        rc=0;
        return(0);
    }
#endif /* HAVE_OPENSSL */
    return(-1);
}

/* It's stupid, I need to change all the args to a struct, one of those 
 * days! I'll do it
 */

/* returns 0 on success, -1 on failure */
int send_the_mail(char *from,char *to,char *cc,char *bcc,char *sub,
             char *smtp_server,int smtp_port,char *helo_domain,
             char *attach_file,char *txt_msg_file,char *the_msg,int is_mime,char *rrr,char *rt,
             int add_dateh,char* return_path_addr)
{
    SOCKET
        sfd;

    TheMail
        *mail;


    Sll
        *al;

    int
        rc=(-1);

    char
        *mech=NULL,
        *auth=NULL;

    /*
    unsigned char
		*b64=NULL;
    */
    char
        *b64 = NULL;

    int
        authenticated=0;
 
    /*
    (void) fprintf(stderr,"From: %s\n",from);
    (void) fprintf(stderr,"To: %s\n",to);
    (void) fprintf(stderr,"Cc: %s\n",cc);
    (void) fprintf(stderr,"Cc: %s\n",bcc);
    (void) fprintf(stderr,"Sub: %s\n",sub);
    (void) fprintf(stderr,"smtp: %s\n",smtp_server);
    (void) fprintf(stderr,"smtp port: %d\n",smtp_port);
    (void) fprintf(stderr,"domain: %s\n",helo_domain);
    (void) fprintf(stderr,"attach file: %s\n",attach_file);
    (void) fprintf(stderr,"txt_msg_file: %s\n",txt_msg_file);
    (void) fprintf(stderr,"the_msg: %s\n",the_msg);
    (void) fprintf(stderr,"is_mime: %d\n",is_mime);
    */

    al=getAddressList();

    if (al == (Sll *) NULL)
    {
        errorMsg("No To address/es specified");
        return (-1);
    }

    if (from == (char *) NULL)
    {
        errorMsg("No From address specified");
        return (-1);
    }

    if (smtp_server == (char *) NULL)
        smtp_server="127.0.0.1";

    if (smtp_port == -1)
        smtp_port=MAILSEND_SMTP_PORT;

    if (sub == (char *) NULL)
        sub=MAILSEND_DEF_SUB;

    if (helo_domain == (char *) NULL)
    {
        errorMsg("No domain specified");
        return (-1);
    }

    mail=newTheMail();
    if (mail == (TheMail *) NULL)
    {
        errorMsg("Error: malloc failed in createTheMail()\n");
        return (-1);
    }

    showVerbose("Connecting to %s:%d\n",smtp_server,smtp_port);
    /* open the network connection */
    sfd=smtpConnect(smtp_server,smtp_port);
    if (sfd == INVALID_SOCKET)
    {
        rc=(-1);
        goto cleanup;
    }

    if (g_do_ssl) /* smtp.gmail:465 supports it for example */
    {
        turn_on_raw_ssl(sfd);
    }

    /* read greeting */
    rc=read_greetings();
    if (rc < 0)
        goto cleanup;

    /* say HELO/EHLO */
    say_helo(helo_domain);

    /* check if the server supports STARTTLS or TLS */
    if (g_do_starttls)
    {
        if (check_server_cap("STARTTLS") ||
            check_server_cap("TLS"))
        {
            rc=smtp_start_tls(sfd);
            if (rc == 0)
            {
               /* send HELO again */
                say_helo(helo_domain);
            }
        }
    }

    if (g_do_auth || g_auth_cram_md5 || g_auth_login || g_auth_plain)
    {
        auth=check_server_cap("AUTH");
    }
    if (!auth)
        goto MailFrom;
    /*
    (void) fprintf(stderr,"auth=%s\n",auth);
    (void) fprintf(stderr," g_auth_cram_md5=%d; g_auth_login=%d; g_auth_plain=%d\n", g_auth_cram_md5, g_auth_login, g_auth_plain);
    */

    /*
    if (auth && g_do_auth)
    {
        g_auth_cram_md5=1;
        g_auth_login=1;
        g_auth_plain=1;
    }
    */
    /* Try CRAM-MD5 first */
    mech="CRAM-MD5";
    if (g_auth_cram_md5 && check_server_cap(mech))
    {
        char
            *cmd5 = NULL;

        CHECK_USERNAME(mech);
        CHECK_USERPASS(mech);

#ifndef HAVE_OPENSSL
        errorMsg("Must be compiled with OpenSSL in order to get CRAM-MD5 support\n");
        goto cleanup;
#endif /* !HAVE_OPENSSL */
        showVerbose("Using AUTH %s\n",mech);
        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"AUTH %s\r\n",mech);
        showVerbose("[C] %s",buf);
        msock_puts(buf);

        read_smtp_line();
        if (smtp_code != 334)
        {
            errorMsg("AUTH CRAM-MD5 failed: '%d %s'",
                    smtp_code,
                    smtp_line);
            rc=(-1);
            goto cleanup;
        }
        cmd5 = encode_cram_md5(smtp_line,g_username,g_userpass);
        if (cmd5 == NULL)
        {
            errorMsg("Could not encode CRAM-MD5");
            rc = (-1);
            goto cleanup;
        }
        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"%s\r\n",cmd5);
        showVerbose("[C] %s",buf);
        msock_puts(buf);
        read_smtp_line();
        if (smtp_code != 235)
        {
            errorMsg("AUTH CRAM-MD5 failed: '%d %s'",
                    smtp_code,
                    smtp_line);
            rc=(-1);
            goto cleanup;
        }

        showVerbose("%s Authentication succeeded\n",mech);
        authenticated++;
        if (cmd5)
        {
            (void) free((char *) cmd5);
        }
    }
    else
    {
        if (g_auth_cram_md5)
            showVerbose("Server does not support AUTH CRAM-MD5\n");
    }
    if (authenticated)
        goto MailFrom;

    mech="LOGIN";
    if (g_auth_login && check_server_cap(mech))
    {
        CHECK_USERNAME(mech);
        CHECK_USERPASS(mech);

        showVerbose("Using AUTH %s\n",mech);
        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"AUTH %s\r\n",mech);
        showVerbose("[C] %s",buf);
        msock_puts(buf);

        read_smtp_line();
        if (smtp_code != 334)
        {
            errorMsg("AUTH LOGIN failed: '%d %s'",
                    smtp_code,
                    smtp_line);
            rc=(-1);
            goto cleanup;
        }
        /*
        b64=mutils_encode_base64(g_username,strlen(g_username),&b64len);
        b64[b64len-2]='\0';
        */
        b64=mutils_encode_base64_noformat(g_username,strlen(g_username));
        if (b64 == NULL)
        {
            errorMsg("Could not base64 encode user: %s",g_username);
            rc=(-1);
            goto cleanup;
        }

        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"%s\r\n",b64);
        showVerbose("[C] %s",buf);
        msock_puts(buf);
        read_smtp_line();
        if (smtp_code != 334)
        {
            errorMsg("AUTH LOGIN failed: '%d %s'",
                    smtp_code,
                    smtp_line);
            rc=(-1);
            goto cleanup;
        }

        /*
        b64=mutils_encode_base64(g_userpass,strlen(g_userpass),&b64len);
        b64[b64len-2]='\0';
        */
        b64=mutils_encode_base64_noformat(g_userpass,strlen(g_userpass));
        if (b64 == NULL)
        {
            errorMsg("Could not base64 encode passworf of user: %s",g_username);
            rc=(-1);
            goto cleanup;
        }

        memset(buf,0,sizeof(buf));
        (void) snprintf(buf,sizeof(buf)-1,"%s\r\n",b64);
        showVerbose("[C] %s",buf);
        msock_puts(buf);

        read_smtp_line();
        if (smtp_code != 235)
        {
            errorMsg("AUTH LOGIN failed: '%d %s'",
                    smtp_code,
                    smtp_line);
            rc=(-1);
            goto cleanup;
        }
        authenticated++;
    }
    else
    {
        if (g_auth_login)
            showVerbose("Server does not support AUTH LOGIN\n");
    }


    if (authenticated)
        goto MailFrom;

    mech="PLAIN";
    if (g_auth_plain && check_server_cap(mech))
    {
        int
            len,
            ulen,
            plen;

        unsigned char
            *b64=NULL;

        CHECK_USERNAME(mech);
        CHECK_USERPASS(mech);

        showVerbose("Using AUTH %s\n",mech);
        memset(buf,0,sizeof(buf));
        /*
        ** authzid\0authid\0pass
        ** authzid can be skipped if both are the same
        */

        ulen=strlen(g_username);
        memcpy(buf + 1,g_username,ulen);
        plen=strlen(g_userpass);

        memcpy(buf + ulen + 2,g_userpass,plen);
        len=ulen + plen + 2;
#if 0
        b64=mutils_encode_base64(buf,len,&b64len);
        /* mutils_encode_base64 adds CRLF */
        b64[b64len-2]='\0';
#endif
        b64=mutils_encode_base64_noformat(buf,len);
        if (b64 == NULL)
        {
            errorMsg("Could not base64 for AUTH-PLAIN for user: %s",g_username);
            rc=(-1);
            goto cleanup;
        }

        (void) snprintf(buf,sizeof(buf)-1,"AUTH PLAIN %s\r\n",(char *) b64);

        showVerbose("[C] %s",buf);
        msock_puts(buf);

        read_smtp_line();
        if (smtp_code != 235)
        {
            errorMsg("AUTH PLAIN failed: '%d %s'",
                    smtp_code,
                    smtp_line);
            rc=(-1);
            goto cleanup;
        }
        showVerbose("PLAIN Authentication succeeded\n");
        authenticated++;
    }
    else
    {
        if (g_auth_plain)
            showVerbose("Server does not support AUTH PLAIN\n");
    }

    if (authenticated)
        goto MailFrom;

    if (auth && !g_quiet)
    {
        if (!g_auth_cram_md5)
        {
            if (check_server_cap("CRAM-MD5"))
            {
                (void) fprintf(stderr,
" * Server supports AUTH CRAM-MD5.");
            }
        }
        if (!g_auth_login)
        {
            if (check_server_cap("LOGIN"))
            {
                (void) fprintf(stderr,
" * Server supports AUTH LOGIN.\n");
            }
        }

        if (!g_auth_plain)
        {
            if (check_server_cap("PLAIN"))
            {
                (void) fprintf(stderr,
" * Server supports AUTH PLAIN.\n");
            }
        }
        if (!authenticated)
        {
            (void) fprintf(stderr,
" Use -auth or specify a mechanism that server supports. exiting.\n");
            exit_error();
        }
    }

MailFrom:
    if (return_path_addr)
        rc=smtp_MAIL_FROM(return_path_addr);
    else
        rc=smtp_MAIL_FROM(from);
    if (rc != 0)
        goto cleanup;

    rc=smtp_RCPT_TO();
    if (rc != 0)
        goto cleanup;

    rc=smtp_DATA();
    if (rc != 0)
        goto cleanup;

    rc=smtpMail(sfd,to,cc,bcc,from,rrr,rt,sub,attach_file,txt_msg_file,the_msg,is_mime,add_dateh);
    RETURN_IF_NOT_ZERO(rc);

    rc=smtpEom(sfd);
    RETURN_IF_NOT_ZERO(rc);
    smtp_QUIT();

cleanup:
    return (rc);
}

/*
** print info about SMTP server on stdout
** return 0 on success -1 on error
*/
int show_smtp_info(char *smtp_server,int port,char *domain)
{
    int
        fd,
        rc = (-1);
#ifdef HAVE_OPENSSL
    SSL
        *ssl=NULL;
#endif /* HAVE_OPENSSL */

    g_verbose=1;
    print_info("Connecting to SMTP server: %s at Port: %d\n",smtp_server,port);
    print_info("Connection timeout: %d secs\n",g_connect_timeout);
    /* connect */
    fd=smtpConnect(smtp_server,port);
    if (fd == INVALID_SOCKET)
    {
        rc = (-1);
        goto ExitProcessing;
    }

#ifdef HAVE_OPENSSL
    if (g_do_ssl)
    {
        ssl=msock_get_ssl();
        if (ssl)
        {
            if (!SSL_set_fd(ssl,fd))
            {
                errorMsg("failed to set socket to SSL\n");
                goto ExitProcessing;
            }
            /* must set back to msock's static */
            msock_set_ssl(ssl);
            rc=SSL_connect(ssl);
            if (rc < 1)
            {
                errorMsg("SSL connection failed\n");
                ERR_print_errors_fp(stderr);
                goto ExitProcessing;
            }
            print_cert_info(ssl);
            /* tell msock everything is ssl after that */
            msock_turn_ssl_on();
            rc=0;
        }
    }
#endif /* HAVE_OPENSSL */

    /* read greeting */
    rc=read_greetings();
    if (rc < 0)
    {
        errorMsg("Could not read greetings\n");
        goto ExitProcessing;
    }

    /* say HELO/EHLO */
    say_helo(domain);

    /* check if server supports STARTTLS */
    if (check_server_cap("STARTTLS") ||
        check_server_cap("TLS"))
    {
        rc=smtp_start_tls(fd);
        if (rc == 0)
            say_helo(domain);
        else
            goto ExitProcessing;
    }
    smtp_QUIT();
    smtpDisconnect();

ExitProcessing:
    return (rc);
}

