jens-maus/yam

View on GitHub
src/tcp/smtp.c

Summary

Maintainability
Test Coverage
/***************************************************************************

 YAM - Yet Another Mailer
 Copyright (C) 1995-2000 Marcel Beck
 Copyright (C) 2000-2022 YAM Open Source Team

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 YAM Official Support Site :  http://www.yam.ch
 YAM OpenSource project    :  http://sourceforge.net/projects/yamos/

 $Id$

***************************************************************************/

#include <ctype.h>
#include <string.h>

#include <clib/alib_protos.h>
#include <proto/exec.h>

#include "extrasrc.h"

#include "YAM.h"
#include "YAM_error.h"
#include "YAM_find.h"
#include "YAM_mainFolder.h"

#include "mime/base64.h"
#include "mime/md5.h"
#include "mui/ClassesExtra.h"
#include "mui/TransferControlGroup.h"
#include "mui/YAMApplication.h"
#include "tcp/Connection.h"
#include "tcp/ssl.h"

#include "Busy.h"
#include "Config.h"
#include "FolderList.h"
#include "Locale.h"
#include "Logfile.h"
#include "MailList.h"
#include "MailServers.h"
#include "MailTransferList.h"
#include "MethodStack.h"
#include "MUIObjects.h"
#include "Threads.h"
#include "UserIdentity.h"

#include "Debug.h"

/**************************************************************************/
// SMTP commands (RFC 821) and extended ESMTP.
// the order of the following enum & pointer array is important and have to
// match each other or weird things will happen.
enum SMTPCommand
{
  // SMTP commands
  SMTP_HELO, SMTP_MAIL, SMTP_RCPT, SMTP_DATA, SMTP_SEND, SMTP_SOML, SMTP_SAML, SMTP_RSET,
  SMTP_VRFY, SMTP_EXPN, SMTP_HELP, SMTP_NOOP, SMTP_QUIT, SMTP_TURN, SMTP_FINISH, SMTP_CONNECT,

  // ESMTP commands
  ESMTP_EHLO, ESMTP_STARTTLS, ESMTP_AUTH_CRAM_MD5, ESMTP_AUTH_DIGEST_MD5, ESMTP_AUTH_LOGIN,
  ESMTP_AUTH_PLAIN
};

static const char *const SMTPcmd[] =
{
  // SMTP commands
  "HELO", "MAIL", "RCPT", "DATA", "SEND", "SOML", "SAML", "RSET",
  "VRFY", "EXPN", "HELP", "NOOP", "QUIT", "TURN", "\r\n.", "",

  // ESMTP commands
  "EHLO", "STARTTLS", "AUTH CRAM-MD5", "AUTH DIGEST-MD5", "AUTH LOGIN",
  "AUTH PLAIN"
};

// SMTP Status Messages
#define SMTP_SERVICE_NOT_AVAILABLE 421
#define SMTP_ACTION_OK             250

// SMTP server capabilities flags & macros
#define SMTP_FLG_ESMTP               (1<<0)
#define SMTP_FLG_AUTH_CRAM_MD5       (1<<1)
#define SMTP_FLG_AUTH_DIGEST_MD5     (1<<2)
#define SMTP_FLG_AUTH_LOGIN          (1<<3)
#define SMTP_FLG_AUTH_PLAIN          (1<<4)
#define SMTP_FLG_STARTTLS            (1<<5)
#define SMTP_FLG_SIZE                (1<<6)
#define SMTP_FLG_PIPELINING          (1<<7)
#define SMTP_FLG_8BITMIME            (1<<8)
#define SMTP_FLG_DSN                 (1<<9)
#define SMTP_FLG_ETRN                (1<<10)
#define SMTP_FLG_ENHANCEDSTATUSCODES (1<<11)
#define SMTP_FLG_DELIVERBY           (1<<12)
#define SMTP_FLG_HELP                (1<<13)
#define hasESMTP(v)                  (isFlagSet((v), SMTP_FLG_ESMTP))
#define hasCRAM_MD5_Auth(v)          (isFlagSet((v), SMTP_FLG_AUTH_CRAM_MD5))
#define hasDIGEST_MD5_Auth(v)        (isFlagSet((v), SMTP_FLG_AUTH_DIGEST_MD5))
#define hasLOGIN_Auth(v)             (isFlagSet((v), SMTP_FLG_AUTH_LOGIN))
#define hasPLAIN_Auth(v)             (isFlagSet((v), SMTP_FLG_AUTH_PLAIN))
#define hasSTARTTLS(v)               (isFlagSet((v), SMTP_FLG_STARTTLS))
#define hasSIZE(v)                   (isFlagSet((v), SMTP_FLG_SIZE))
#define hasPIPELINING(v)             (isFlagSet((v), SMTP_FLG_PIPELINING))
#define has8BITMIME(v)               (isFlagSet((v), SMTP_FLG_8BITMIME))
#define hasDSN(v)                    (isFlagSet((v), SMTP_FLG_DSN))
#define hasETRN(v)                   (isFlagSet((v), SMTP_FLG_ETRN))
#define hasENHANCEDSTATUSCODES(v)    (isFlagSet((v), SMTP_FLG_ENHANCEDSTATUSCODES))
#define hasDELIVERBY(v)              (isFlagSet((v), SMTP_FLG_DELIVERBY))
#define hasHELP(v)                   (isFlagSet((v), SMTP_FLG_HELP))

// help macros for SMTP routines
#define getResponseCode(str)          ((int)strtol((str), NULL, 10))

struct TransferContext
{
  struct Connection *conn;
  struct MailServerNode *msn;
  struct UserIdentityNode *uin;          // ptr to user identity sending mails
  Object *transferGroup;
  struct Folder *outFolder;              // the folder to send mails from
  struct Folder *sentFolder;             // the folder to store sent mails into
  char smtpBuffer[SIZE_LINE];            // RFC 2821 says 1000 should be enough
  char challenge[SIZE_LINE];
  char tempBuffer[SIZE_LINE];
  char transferGroupTitle[SIZE_DEFAULT]; // the TransferControlGroup's title
  BOOL useTLS;
};

/// SendSMTPCommand
//  Sends a command to the SMTP server and returns the response message
//  described in (RFC 2821)
static char *SendSMTPCommand(struct TransferContext *tc, const enum SMTPCommand command, const char *parmtext, const char *errorMsg)
{
  BOOL success = FALSE;
  char *result = tc->smtpBuffer;

  ENTER();

  // first we check if the socket is ready
  // now we prepare the SMTP command
  if(IsStrEmpty(parmtext))
    snprintf(tc->smtpBuffer, sizeof(tc->smtpBuffer), "%s\r\n", SMTPcmd[command]);
  else
    snprintf(tc->smtpBuffer, sizeof(tc->smtpBuffer), "%s %s\r\n", SMTPcmd[command], parmtext);

  D(DBF_NET, "TCP: send SMTP cmd '%s' with param '%s'", SMTPcmd[command], SafeStr(parmtext));

  // lets send the command via TR_WriteLine, but not if we are in connection
  // state
  if(command == SMTP_CONNECT || SendLineToHost(tc->conn, tc->smtpBuffer) > 0)
  {
    int len = 0;

    // after issuing the SMTP command we read out the server response to it
    // but only if this wasn't the SMTP_QUIT command.
    if((len = ReceiveLineFromHost(tc->conn, tc->smtpBuffer, sizeof(tc->smtpBuffer))) > 0)
    {
      // get the response code
      int rc = strtol(tc->smtpBuffer, NULL, 10);

      D(DBF_NET, "received SMTP answer '%s'", tc->smtpBuffer);

      // if the response is a multiline response we have to get out more
      // from the socket
      if(tc->smtpBuffer[3] == '-') // (RFC 2821) - section 4.2.1
      {
        char tbuf[SIZE_LINE];

        // now we concatenate the multiline reply to
        // out main buffer
        do
        {
          // lets get out the next line from the socket
          if((len = ReceiveLineFromHost(tc->conn, tbuf, sizeof(tbuf))) > 0)
          {
            // get the response code
            int rc2 = strtol(tbuf, NULL, 10);

            // check if the response code matches the one
            // of the first line
            if(rc == rc2)
            {
              // lets concatenate both strings while stripping the
              // command code and make sure we didn't reach the end
              // of the buffer
              if(strlcat(tc->smtpBuffer, tbuf, sizeof(tc->smtpBuffer)) >= sizeof(tc->smtpBuffer))
                W(DBF_NET, "buffer overrun on trying to concatenate a multiline reply!");
            }
            else
            {
              E(DBF_NET, "response codes of multiline reply doesn't match!");

              errorMsg = NULL;
              len = 0;
              break;
            }
          }
          else
          {
            errorMsg = tr(MSG_ER_CONNECTIONBROKEN);
            break;
          }
        }
        while(tbuf[3] == '-');
      }

      // check that the concatentation worked
      // out fine and that the rc is valid
      if(len > 0 && rc >= 100)
      {
        // Now we check if we got the correct response code for the command
        // we issued
        switch(command)
        {
          //  Reponse    Description (RFC 2821 - section 4.2.1)
          //  1xx        Positive Preliminary reply
          //  2xx        Positive Completion reply
          //  3xx        Positive Intermediate reply
          //  4xx        Transient Negative Completion reply
          //  5xx        Permanent Negative Completion reply

          case SMTP_HELP:    { success = (rc == 211 || rc == 214); } break;
          case SMTP_VRFY:    { success = (rc == 250 || rc == 251); } break;
          case SMTP_CONNECT: { success = (rc == 220); } break;
          case SMTP_QUIT:    { success = (rc == 221); } break;
          case SMTP_DATA:    { success = (rc == 354); } break;

          // all codes that accept 250 response code
          case SMTP_HELO:
          case SMTP_MAIL:
          case SMTP_RCPT:
          case SMTP_FINISH:
          case SMTP_RSET:
          case SMTP_SEND:
          case SMTP_SOML:
          case SMTP_SAML:
          case SMTP_EXPN:
          case SMTP_NOOP:
          case SMTP_TURN:    { success = (rc == 250); } break;

          // ESMTP commands & response codes
          case ESMTP_EHLO:            { success = (rc == 250); } break;
          case ESMTP_STARTTLS:        { success = (rc == 220); } break;

          // ESMTP_AUTH command responses
          case ESMTP_AUTH_CRAM_MD5:
          case ESMTP_AUTH_DIGEST_MD5:
          case ESMTP_AUTH_LOGIN:
          case ESMTP_AUTH_PLAIN:      { success = (rc == 334); } break;
        }
      }
    }
    else
    {
      // Unfortunately, there are broken SMTP server implementations out there
      // like the one used by "smtp.googlemail.com" or "smtp.gmail.com".
      //
      // It seems these broken SMTP servers do automatically drop the
      // data connection right after the 'QUIT' command was send and don't
      // reply with a status message like it is clearly defined in RFC 2821
      // (section 4.1.1.10). Unfortunately we can't do anything about
      // it really and have to consider this a bad and ugly workaround. :(
      if(command == SMTP_QUIT)
      {
        W(DBF_NET, "broken SMTP server implementation found on QUIT, keeping quiet...");

        success = TRUE;
        tc->smtpBuffer[0] = '\0';
      }
      else
        errorMsg = tr(MSG_ER_CONNECTIONBROKEN);
    }
  }
  else
    errorMsg = tr(MSG_ER_CONNECTIONBROKEN);

  // the rest of the responses throws an error
  if(success == FALSE)
  {
    if(errorMsg != NULL)
      ER_NewError(errorMsg, tc->msn->hostname, (char *)SMTPcmd[command], tc->smtpBuffer);

    result = NULL;
  }

  RETURN(result);
  return result;
}

///
/// ConnectToSMTP
//  Connects to a SMTP mail server - here we always try to do an ESMTP connection
//  first via an EHLO command and then check if it succeeded or not.
static BOOL ConnectToSMTP(struct TransferContext *tc)
{
  BOOL result = FALSE;

  ENTER();

  // If we did a TLS negotitaion previously we have to skip the
  // welcome message, but if it was another connection like a normal or a SSL
  // one we have wait for the welcome
  if(tc->useTLS == FALSE || hasServerSSL(tc->msn))
  {
    PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_WaitWelcome));

    result = (SendSMTPCommand(tc, SMTP_CONNECT, NULL, tr(MSG_ER_BADRESPONSE_SMTP)) != NULL);
  }
  else
    result = TRUE;

  // now we either send a HELO (non-ESMTP) or EHLO (ESMTP) command to
  // signal we wanting to start a session accordingly (RFC 1869 - section 4)
  if(result == TRUE)
  {
    ULONG flags = 0;
    char *resp = NULL;
    char hostName[256];

    // before we go on we retrieve the FQDN of the machine we are sending the
    // email from
    GetFQDN(tc->conn, hostName, sizeof(hostName));

    // per default we flag the SMTP to be capable of an ESMTP
    // connection.
    setFlag(flags, SMTP_FLG_ESMTP);

    // set the connection status
    PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_SendHello));

    D(DBF_NET, "trying ESMTP negotation");

    // in case we require SMTP-AUTH or a TLS secure connection we
    // have to force an ESMTP connection
    if(hasServerAuth(tc->msn) || hasServerTLS(tc->msn))
      resp = SendSMTPCommand(tc, ESMTP_EHLO, hostName, tr(MSG_ER_BADRESPONSE_SMTP));
    else
    {
      // in all other cases, we first try to get an ESMTP connection
      // and if that doesn't work we go and do a normal SMTP connection
      if((resp = SendSMTPCommand(tc, ESMTP_EHLO, hostName, NULL)) == NULL)
      {
        D(DBF_NET, "ESMTP negotation failed, trying normal SMTP negotation");

        // according to RFC 1869 section 4.7 we send an RSET command
        // between the two EHLO and HELO commands to play save
        SendSMTPCommand(tc, SMTP_RSET, NULL, NULL); // no error code check

        // now we send a HELO command which signals we are not
        // going to use any ESMTP stuff
        resp = SendSMTPCommand(tc, SMTP_HELO, hostName, tr(MSG_ER_BADRESPONSE_SMTP));

        // signal we are not into ESMTP stuff
        clearFlag(flags, SMTP_FLG_ESMTP);
      }
    }

    // check the EHLO/HELO answer.
    if(resp != NULL)
    {
      // check the ESMTP flags if this is an
      // ESMTP connection
      if(hasESMTP(flags))
      {
        // Now lets see what features this ESMTP Server really has
        while(resp[3] == '-')
        {
          // now lets iterate to the next line
          resp = strchr(resp, '\n');

          // if we do not find any new line or the next one would be anyway
          // too short we break here.
          if(resp == NULL || strlen(++resp) < 4)
            break;

          // lets see what features this server returns
          if(strnicmp(resp+4, "STARTTLS", 8) == 0)          // STARTTLS (RFC 2487)
            setFlag(flags, SMTP_FLG_STARTTLS);
          else if(strnicmp(resp+4, "AUTH", 4) == 0)         // SMTP-AUTH (RFC 2554)
          {
            if(NULL != strstr(resp+9,"CRAM-MD5"))
              setFlag(flags, SMTP_FLG_AUTH_CRAM_MD5);

            if(NULL != strstr(resp+9,"DIGEST-MD5"))
              setFlag(flags, SMTP_FLG_AUTH_DIGEST_MD5);

            if(NULL != strstr(resp+9,"PLAIN"))
              setFlag(flags, SMTP_FLG_AUTH_PLAIN);

            if(NULL != strstr(resp+9,"LOGIN"))
              setFlag(flags, SMTP_FLG_AUTH_LOGIN);
          }
          else if(strnicmp(resp+4, "SIZE", 4) == 0)         // STD:10 - SIZE declaration (RFC 1870)
            setFlag(flags, SMTP_FLG_SIZE);
          else if(strnicmp(resp+4, "PIPELINING", 10) == 0)  // STD:60 - PIPELINING (RFC 2920)
            setFlag(flags, SMTP_FLG_PIPELINING);
          else if(strnicmp(resp+4, "8BITMIME", 8) == 0)     // 8BITMIME support (RFC 1652)
            setFlag(flags, SMTP_FLG_8BITMIME);
          else if(strnicmp(resp+4, "DSN", 3) == 0)          // DSN - Delivery Status Notifications (RFC 1891)
            setFlag(flags, SMTP_FLG_DSN);
          else if(strnicmp(resp+4, "ETRN", 4) == 0)         // ETRN - Remote Message Queue Starting (RFC 1985)
            setFlag(flags, SMTP_FLG_ETRN);
          else if(strnicmp(resp+4, "ENHANCEDSTATUSCODES", 19) == 0) // Enhanced Status Codes (RFC 2034)
            setFlag(flags, SMTP_FLG_ENHANCEDSTATUSCODES);
          else if(strnicmp(resp+4, "DELIVERBY", 9) == 0)    // DELIVERBY Extension (RFC 2852)
            setFlag(flags, SMTP_FLG_DELIVERBY);
          else if(strnicmp(resp+4, "HELP", 4) == 0)         // HELP Extension (RFC 821)
            setFlag(flags, SMTP_FLG_HELP);
        }
      }

      #ifdef DEBUG
      D(DBF_NET, "SMTP Server '%s' provides:", tc->msn->hostname);
      D(DBF_NET, "  ESMTP..............: %s", Bool2Txt(hasESMTP(flags)));
      D(DBF_NET, "  AUTH CRAM-MD5......: %s", Bool2Txt(hasCRAM_MD5_Auth(flags)));
      D(DBF_NET, "  AUTH DIGEST-MD5....: %s", Bool2Txt(hasDIGEST_MD5_Auth(flags)));
      D(DBF_NET, "  AUTH LOGIN.........: %s", Bool2Txt(hasLOGIN_Auth(flags)));
      D(DBF_NET, "  AUTH PLAIN.........: %s", Bool2Txt(hasPLAIN_Auth(flags)));
      D(DBF_NET, "  STARTTLS...........: %s", Bool2Txt(hasSTARTTLS(flags)));
      D(DBF_NET, "  SIZE...............: %s", Bool2Txt(hasSIZE(flags)));
      D(DBF_NET, "  PIPELINING.........: %s", Bool2Txt(hasPIPELINING(flags)));
      D(DBF_NET, "  8BITMIME...........: %s", Bool2Txt(has8BITMIME(flags)));
      D(DBF_NET, "  DSN................: %s", Bool2Txt(hasDSN(flags)));
      D(DBF_NET, "  ETRN...............: %s", Bool2Txt(hasETRN(flags)));
      D(DBF_NET, "  ENHANCEDSTATUSCODES: %s", Bool2Txt(hasENHANCEDSTATUSCODES(flags)));
      D(DBF_NET, "  DELIVERBY..........: %s", Bool2Txt(hasDELIVERBY(flags)));
      D(DBF_NET, "  HELP...............: %s", Bool2Txt(hasHELP(flags)));
      #endif

      // now we check the 8BITMIME extension against
      // the user configured Allow8bit setting and if it collides
      // we raise a warning.
      if(has8BITMIME(flags) == FALSE && hasServer8bit(tc->msn) == TRUE)
        result = FALSE;
    }
    else
    {
      result = FALSE;

      W(DBF_NET, "error on SMTP server negotation");
    }

    tc->msn->smtpFlags = flags;
  }
  else
    W(DBF_NET, "SMTP connection failure!");

  RETURN(result);
  return result;
}

///
/// InitSTARTTLS
// function to initiate a TLS connection to the ESMTP server via STARTTLS
static BOOL InitSTARTTLS(struct TransferContext *tc)
{
  BOOL result = FALSE;

  ENTER();

  // If this server doesn't support TLS at all we return with an error
  if(hasSTARTTLS(tc->msn->smtpFlags))
  {
    // If we end up here the server supports STARTTLS and we can start
    // initializing the connection
    PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_INITTLS));

    // Now we initiate the STARTTLS command (RFC 2487)
    if(SendSMTPCommand(tc, ESMTP_STARTTLS, NULL, tr(MSG_ER_BADRESPONSE_SMTP)) != NULL)
    {
      // setup the TLS/SSL session
      if(MakeSecureConnection(tc->conn) == TRUE)
      {
        tc->useTLS = TRUE;
        result = TRUE;
      }
      else
      {
        ER_NewError(tr(MSG_ER_INITTLS_SMTP), tc->msn->hostname);
      }
    }
  }
  else
  {
    ER_NewError(tr(MSG_ER_NOSTARTTLS_SMTP), tc->msn->hostname);
  }

  RETURN(result);
  return result;
}

///
/// InitSMTPAUTH
// function to authenticate to a ESMTP Server
static BOOL InitSMTPAUTH(struct TransferContext *tc)
{
  int rc = SMTP_SERVICE_NOT_AVAILABLE;
  char *resp;
  int selectedMethod = MSF_AUTH_AUTO;

  ENTER();

  PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_SENDAUTH));

  // first we check if the user has supplied the User&Password
  // and if not we return with an error
  if(IsStrEmpty(tc->msn->username) || IsStrEmpty(tc->msn->password))
  {
    ER_NewError(tr(MSG_ER_NOAUTHUSERPASS));

    RETURN(FALSE);
    return FALSE;
  }

  // now we find out which of the SMTP-AUTH methods we process and which to skip
  // the user explicitly set an auth method. However, we have to
  // check wheter the SMTP server told us that it really
  // supports that method or not
  if(hasServerAuth_AUTO(tc->msn))
  {
    D(DBF_NET, "about to automatically choose which SMTP-AUTH to prefer. smtpFlags=0x%08lx", tc->msn->smtpFlags);

    // we select the most secure one the server supports
    if(hasDIGEST_MD5_Auth(tc->msn->smtpFlags))
      selectedMethod = MSF_AUTH_DIGEST;
    else if(hasCRAM_MD5_Auth(tc->msn->smtpFlags))
      selectedMethod = MSF_AUTH_CRAM;
    else if(hasLOGIN_Auth(tc->msn->smtpFlags))
      selectedMethod = MSF_AUTH_LOGIN;
    else if(hasPLAIN_Auth(tc->msn->smtpFlags))
      selectedMethod = MSF_AUTH_PLAIN;
    else
      W(DBF_NET, "Server doesn't seem to support any SMTP-AUTH method but InitSMTPAUTH function called?");
  }
  else if(hasServerAuth_DIGEST(tc->msn))
  {
    if(hasDIGEST_MD5_Auth(tc->msn->smtpFlags))
      selectedMethod = MSF_AUTH_DIGEST;
    else
      W(DBF_NET, "User selected SMTP-Auth 'DIGEST-MD5', but server doesn't support it!");
  }
  else if(hasServerAuth_CRAM(tc->msn))
  {
    if(hasCRAM_MD5_Auth(tc->msn->smtpFlags))
      selectedMethod = MSF_AUTH_CRAM;
    else
      W(DBF_NET, "User selected SMTP-Auth 'CRAM-MD5', but server doesn't support it!");
  }
  else if(hasServerAuth_LOGIN(tc->msn))
  {
    if(hasLOGIN_Auth(tc->msn->smtpFlags))
      selectedMethod = MSF_AUTH_LOGIN;
    else
      W(DBF_NET, "User selected SMTP-Auth 'LOGIN', but server doesn't support it!");
  }
  else if(hasServerAuth_PLAIN(tc->msn))
  {
    if(hasPLAIN_Auth(tc->msn->smtpFlags))
      selectedMethod = MSF_AUTH_PLAIN;
    else
      W(DBF_NET, "User selected SMTP-Auth 'PLAIN', but server doesn't support it!");
  }

  D(DBF_NET, "SMTP-AUTH method %d choosen due to server/user preference", selectedMethod);

  // now we process the SMTP Authentication by choosing the method the user
  // or the automatic did specify
  switch(selectedMethod)
  {
    // SMTP AUTH DIGEST-MD5 (RFC 2831)
    case MSF_AUTH_DIGEST:
    {
      D(DBF_NET, "processing AUTH DIGEST-MD5:");

      // send the AUTH command and get the response back
      if((resp = SendSMTPCommand(tc, ESMTP_AUTH_DIGEST_MD5, NULL, tr(MSG_ER_BADRESPONSE_SMTP))) != NULL)
      {
        char *realm = NULL;
        char *nonce = NULL;
        char cnonce[16+1];
        char response[32+1];
        char *chalRet;
        char *decResponse = NULL;
        char *enctext = NULL;

        // get the challenge code from the response line of the
        // AUTH command.
        strlcpy(tc->challenge, &resp[4], sizeof(tc->challenge));

        // now that we have the challenge phrase we need to base64decode
        // it, but have to take care to remove the ending "\r\n" cookie.
        chalRet = strpbrk(tc->challenge, "\r\n"); // find the first CR or LF
        if(chalRet != NULL)
          *chalRet = '\0'; // strip it

        D(DBF_NET, "received DIGEST-MD5 challenge: '%s'", tc->challenge);

        // lets base64 decode it
        if(base64decode(&decResponse, tc->challenge, strlen(tc->challenge)) <= 0)
        {
          RETURN(FALSE);
          return FALSE;
        }

        // now copy over decReponse to tc->challenge
        strlcpy(tc->challenge, decResponse, sizeof(tc->challenge));
        free(decResponse);

        D(DBF_NET, "decoded  DIGEST-MD5 challenge: '%s'", tc->challenge);

        // we now analyze the received challenge identifier and pick out
        // the value which we are going to need for our challenge response.
        // This is the refered STEP ONE in the RFC 2831
        {
          char *pstart;
          char *pend;

          // first we try to find out the "realm" of the challenge
          if((pstart = strstr(tc->challenge, "realm=")) != NULL)
          {
            // iterate to the beginning of the realm
            pstart += 6;

            // skip a leading "
            if(*pstart == '"')
              pstart++;

            // find the end of the string
            pend = strpbrk(pstart, "\","); // find a ending " or ,
            if(!pend)
              pend = pstart + strlen(pstart);

            // now copy the found realm into our realm string
            realm = malloc((pend-pstart)+1);
            if(realm)
              strlcpy(realm, pstart, (pend-pstart)+1);
            else
            {
              RETURN(FALSE);
              return FALSE;
            }
          }
          else
          {
            char hostName[256];

            GetFQDN(tc->conn, hostName, sizeof(hostName));

            W(DBF_NET, "'realm' not found in challenge, using '%s' instead", hostName);

            // if the challenge doesn't have a "realm" we assume our
            // choosen SMTP domain to be the realm
            realm = strdup(hostName);
          }

          D(DBF_NET, "realm: '%s'", realm);

          // grab the "nonce" token for later reference
          if((pstart = strstr(tc->challenge, "nonce=")) != NULL)
          {
            // iterate to the beginning of the nonce
            pstart += 6;

            // skip a leading "
            if(*pstart == '"')
              pstart++;

            // find the end of the string
            pend = strpbrk(pstart, "\","); // find a ending " or ,
            if(!pend)
              pend = pstart + strlen(pstart);

            // now copy the found nonce into our nonce string
            nonce = malloc((pend-pstart)+1);
            if(nonce)
              strlcpy(nonce, pstart, (pend-pstart)+1);
            else
            {
              free(realm);

              RETURN(FALSE);
              return FALSE;
            }
          }
          else
          {
            E(DBF_NET, "no 'nonce=' token found!");

            free(realm);

            RETURN(FALSE);
            return FALSE;
          }

          D(DBF_NET, "nonce: '%s'", nonce);

          // now we check the "qop" to carry "auth" so that we are
          // sure that this server really wants an authentification from us
          // RFC 2831 says that it is OK if no qop is present, because this
          // assumes the server to support at least "auth"
          if((pstart = strstr(tc->challenge, "qop=")) != NULL)
          {
            char *qop;

            // iterate to the beginning of the qop=
            pstart += 4;

            // skip a leading "
            if(*pstart == '"')
              pstart++;

            // find the end of the string
            pend = strpbrk(pstart, "\","); // find a ending " or ,
            if(!pend)
              pend = pstart + strlen(pstart);

            // now copy the found qop into our qop string
            qop = malloc((pend-pstart)+1);
            if(qop)
              strlcpy(qop, pstart, (pend-pstart)+1);
            else
            {
              free(realm);
              free(nonce);

              RETURN(FALSE);
              return FALSE;
            }

            // then we check whether we have a plain "auth" within
            // qop or not
            pstart = qop;
            while((pstart = strstr(qop+(pstart-qop), "auth")))
            {
              if(*(pstart+1) != '-')
                break;
            }

            // we don't need the qop string anymore
            free(qop);

            // check if we found a plain auth
            if(!pstart)
            {
              E(DBF_NET, "no 'auth' in 'qop' token found!");

              free(realm);
              free(nonce);

              RETURN(FALSE);
              return FALSE;
            }
          }
        }

        // if we passed here, the server seems to at least support all
        // mechanisms we need for a proper DIGEST-MD5 authentication.
        // so it's time for STEP TWO

        // let us now generate a more or less random and unique cnonce
        // identifier which we can supply to our SMTP server.
        snprintf(cnonce, sizeof(cnonce), "%08x%08x", (unsigned int)rand(), (unsigned int)rand());

        // the we generate the response according to RFC 2831 with A1
        // and A2 as MD5 encoded strings
        {
          unsigned char digest[16]; // 16 octets
          struct MD5Context context;
          char buf[SIZE_LARGE];
          char A1[32+1];
          int  A1_len = 16;         // 16 octects minimum
          char A2[32+1];

          // lets first generate the A1 string
          // A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
          //      ":", nonce-value, ":", cnonce-value }
          snprintf(buf, sizeof(buf), "%s:%s:%s", tc->msn->username, realm, tc->msn->password);
          md5init(&context);
          md5update(&context, buf, strlen(buf));
          md5final(digest, &context);
          memcpy(buf, digest, 16);
          A1_len += snprintf(&buf[16], sizeof(buf)-16, ":%s:%s", nonce, cnonce);
          D(DBF_NET, "unencoded A1: '%s' (%ld)", buf, A1_len);

          // then we directly build the hexadecimal representation
          // HEX(H(A1))
          md5init(&context);
          md5update(&context, buf, A1_len);
          md5final(digest, &context);
          md5digestToHex(digest, A1);
          D(DBF_NET, "encoded   A1: '%s'", A1);


          // then we generate the A2 string accordingly
          // A2 = { "AUTHENTICATE:", digest-uri-value }
          snprintf(buf, sizeof(buf), "AUTHENTICATE:smtp/%s", realm);
          D(DBF_NET, "unencoded A2: '%s'", buf);

          // and also directly build the hexadecimal representation
          // HEX(H(A2))
          md5init(&context);
          md5update(&context, buf, strlen(buf));
          md5final(digest, &context);
          md5digestToHex(digest, A2);
          D(DBF_NET, "encoded   A2: '%s'", A2);

          // now we build the string from which we also build the MD5
          // HEX(H(A1)), ":",
          // nonce-value, ":", nc-value, ":",
          // cnonce-value, ":", qop-value, ":", HEX(H(A2))
          snprintf(buf, sizeof(buf), "%s:%s:00000001:%s:auth:%s", A1, nonce, cnonce, A2);
          D(DBF_NET, "unencoded resp: '%s'", buf);

          // and finally build the respone-value =
          // HEX( KD( HEX(H(A1)), ":",
          //          nonce-value, ":", nc-value, ":",
          //          cnonce-value, ":", qop-value, ":", HEX(H(A2)) }))
          md5init(&context);
          md5update(&context, buf, strlen(buf));
          md5final(digest, &context);
          md5digestToHex(digest, response);
          D(DBF_NET, "encoded   resp: '%s'", response);
        }

        // form up the challenge to authenticate according to RFC 2831
        snprintf(tc->challenge, sizeof(tc->challenge),
                 "username=\"%s\","        // the username token
                 "realm=\"%s\","           // the realm token
                 "nonce=\"%s\","           // the nonce token
                 "cnonce=\"%s\","          // the client nonce (cnonce)
                 "nc=00000001,"            // the nonce count (here always 1)
                 "qop=\"auth\","           // we just use auth
                 "digest-uri=\"smtp/%s\"," // the digest-uri token
                 "response=\"%s\"",        // the response
                 tc->msn->username,
                 realm,
                 nonce,
                 cnonce,
                 realm,
                 response);

        D(DBF_NET, "prepared challenge answer....: '%s'", tc->challenge);
        if(base64encode(&enctext, tc->challenge, strlen(tc->challenge)) > 0)
        {
          strlcpy(tc->tempBuffer, enctext, sizeof(tc->tempBuffer));
          free(enctext);
        }
        else
        {
          RETURN(FALSE);
          return FALSE;
        }
        D(DBF_NET, "encoded  challenge answer....: '%s'", tc->tempBuffer);
        strlcat(tc->tempBuffer, "\r\n", sizeof(tc->tempBuffer));

        // now we send the SMTP AUTH response
        if(SendLineToHost(tc->conn, tc->tempBuffer) > 0)
        {
          // get the server response and see if it was valid
          if(ReceiveLineFromHost(tc->conn, tc->tempBuffer, sizeof(tc->tempBuffer)) <= 0 || (rc = getResponseCode(tc->tempBuffer)) != 334)
            ER_NewError(tr(MSG_ER_BADRESPONSE_SMTP), tc->msn->hostname, (char *)SMTPcmd[ESMTP_AUTH_DIGEST_MD5], tc->tempBuffer);
          else
          {
            // now that we have received the 334 code we just send a plain line
            // to signal that we don't need any option
            if(SendLineToHost(tc->conn, "\r\n") > 0)
            {
              if(ReceiveLineFromHost(tc->conn, tc->tempBuffer, sizeof(tc->tempBuffer)) <= 0 || (rc = getResponseCode(tc->tempBuffer)) != 235)
                ER_NewError(tr(MSG_ER_BADRESPONSE_SMTP), tc->msn->hostname, (char *)SMTPcmd[ESMTP_AUTH_DIGEST_MD5], tc->tempBuffer);
              else
                rc = SMTP_ACTION_OK;
            }
            else
              E(DBF_NET, "couldn't write empty line...");
          }
        }
        else
          E(DBF_NET, "couldn't write empty line...");

        // free all our dynamic buffers
        free(realm);
        free(nonce);
      }
    }
    break;

    // SMTP AUTH CRAM-MD5 (RFC 2195)
    case MSF_AUTH_CRAM:
    {
      D(DBF_NET, "processing AUTH CRAM-MD5:");

      // send the AUTH command and get the response back
      if((resp = SendSMTPCommand(tc, ESMTP_AUTH_CRAM_MD5, NULL, tr(MSG_ER_BADRESPONSE_SMTP))) != NULL)
      {
        ULONG digest[4]; // 16 chars
        char buf[512];
        char *login = tc->msn->username;
        char *password = tc->msn->password;
        char *chalRet;
        char *decResponse = NULL;
        char *enctext = NULL;

        // get the challenge code from the response line of the
        // AUTH command.
        strlcpy(tc->challenge, &resp[4], sizeof(tc->challenge));

        // now that we have the challenge phrase we need to base64decode
        // it, but have to take care to remove the ending "\r\n" cookie.
        chalRet = strpbrk(tc->challenge, "\r\n"); // find the first CR or LF
        if(chalRet)
          *chalRet = '\0'; // strip it

        D(DBF_NET, "received CRAM-MD5 challenge: '%s'", tc->challenge);

        // lets base64 decode it
        if(base64decode(&decResponse, tc->challenge, strlen(tc->challenge)) <= 0)
        {
          RETURN(FALSE);
          return FALSE;
        }

        // now copy over decReponse to tc->challenge
        strlcpy(tc->challenge, decResponse, sizeof(tc->challenge));
        free(decResponse);

        D(DBF_NET, "decoded  CRAM-MD5 challenge: '%s'", tc->challenge);

        // compose the md5 challenge
        md5hmac((unsigned char *)tc->challenge, strlen(tc->challenge), (unsigned char *)password, strlen(password), (unsigned char *)digest);
        snprintf(buf, sizeof(buf), "%s %08x%08x%08x%08x", login, (unsigned int)digest[0], (unsigned int)digest[1],
                                                                 (unsigned int)digest[2], (unsigned int)digest[3]);

        D(DBF_NET, "prepared CRAM-MD5 reponse..: '%s'", buf);
        // lets base64 encode the md5 challenge for the answer
        if(base64encode(&enctext, buf, strlen(buf)) > 0)
        {
          strlcpy(tc->tempBuffer, enctext, sizeof(tc->tempBuffer));
          free(enctext);
        }
        else
        {
          RETURN(FALSE);
          return FALSE;
        }
        D(DBF_NET, "encoded  CRAM-MD5 reponse..: '%s'", tc->tempBuffer);
        strlcat(tc->tempBuffer, "\r\n", sizeof(tc->tempBuffer));

        // now we send the SMTP AUTH response
        if(SendLineToHost(tc->conn, tc->tempBuffer) > 0)
        {
          // get the server response and see if it was valid
          if(ReceiveLineFromHost(tc->conn, tc->tempBuffer, sizeof(tc->tempBuffer)) <= 0 || (rc = getResponseCode(tc->tempBuffer)) != 235)
            ER_NewError(tr(MSG_ER_BADRESPONSE_SMTP), tc->msn->hostname, (char *)SMTPcmd[ESMTP_AUTH_CRAM_MD5], tc->tempBuffer);
          else
            rc = SMTP_ACTION_OK;
        }
      }
    }
    break;

    // SMTP AUTH LOGIN
    case MSF_AUTH_LOGIN:
    {
      D(DBF_NET, "processing AUTH LOGIN:");

      // send the AUTH command
      if((resp = SendSMTPCommand(tc, ESMTP_AUTH_LOGIN, NULL, tr(MSG_ER_BADRESPONSE_SMTP))) != NULL)
      {
        char *enctext = NULL;

        // prepare the username challenge
        D(DBF_NET, "prepared AUTH LOGIN challenge: '%s'", tc->msn->username);
        if(base64encode(&enctext, tc->msn->username, strlen(tc->msn->username)) > 0)
        {
          strlcpy(tc->tempBuffer, enctext, sizeof(tc->tempBuffer));
          free(enctext);
          enctext = NULL;
        }
        else
        {
          RETURN(FALSE);
          return FALSE;
        }
        D(DBF_NET, "encoded  AUTH LOGIN challenge: '%s'", tc->tempBuffer);
        strlcat(tc->tempBuffer, "\r\n", sizeof(tc->tempBuffer));

        // now we send the SMTP AUTH response (UserName)
        if(SendLineToHost(tc->conn, tc->tempBuffer) > 0)
        {
          // get the server response and see if it was valid
          if(ReceiveLineFromHost(tc->conn, tc->tempBuffer, sizeof(tc->tempBuffer)) > 0
             && (rc = getResponseCode(tc->tempBuffer)) == 334)
          {
            // prepare the password challenge
            D(DBF_NET, "prepared AUTH LOGIN challenge: <password>");
            if(base64encode(&enctext, tc->msn->password, strlen(tc->msn->password)) > 0)
            {
              strlcpy(tc->tempBuffer, enctext, sizeof(tc->tempBuffer));
              free(enctext);
              enctext = NULL;
            }
            else
            {
              RETURN(FALSE);
              return FALSE;
            }
            D(DBF_NET, "encoded  AUTH LOGIN challenge: <encoded password>");
            strlcat(tc->tempBuffer, "\r\n", sizeof(tc->tempBuffer));

            // now lets send the Password
            if(SendLineToHost(tc->conn, tc->tempBuffer) > 0)
            {
              // get the server response and see if it was valid
              if(ReceiveLineFromHost(tc->conn, tc->tempBuffer, SIZE_LINE) > 0
                 && (rc = getResponseCode(tc->tempBuffer)) == 235)
              {
                rc = SMTP_ACTION_OK;
              }
            }
          }

          if(rc != SMTP_ACTION_OK)
            ER_NewError(tr(MSG_ER_BADRESPONSE_SMTP), tc->msn->hostname, (char *)SMTPcmd[ESMTP_AUTH_LOGIN], tc->tempBuffer);
        }
      }
    }
    break;

    // SMTP AUTH PLAIN (RFC 2595)
    case MSF_AUTH_PLAIN:
    {
      int len=0;
      char *enctext = NULL;

      D(DBF_NET, "processing AUTH PLAIN:");

      // The AUTH PLAIN command string is a single command string, so we go
      // and prepare the challenge first
      // According to RFC 2595 this string consists of three parts:
      // "[authorize-id] \0 authenticate-id \0 password"
      // where we can left out the first one

      // we don't have a "authorize-id" so we set the first char to \0
      tc->challenge[len++] = '\0';
      len += snprintf(tc->challenge+len, sizeof(tc->challenge)-len, "%s", tc->msn->username)+1; // authenticate-id
      len += snprintf(tc->challenge+len, sizeof(tc->challenge)-len, "%s", tc->msn->password);   // password

      // now we base64 encode this string and send it to the server
      if(base64encode(&enctext, tc->challenge, len) <= 0)
      {
        RETURN(FALSE);
        return FALSE;
      }

      // lets now form up the AUTH PLAIN command we are going to send
      // to the SMTP server for authorization purposes:
      snprintf(tc->challenge, sizeof(tc->challenge), "%s %s\r\n", SMTPcmd[ESMTP_AUTH_PLAIN], enctext);

      // free the encoded text
      free(enctext);

      // now we send the SMTP AUTH command (UserName+Password)
      if(SendLineToHost(tc->conn, tc->challenge) > 0)
      {
        // get the server response and see if it was valid
        if(ReceiveLineFromHost(tc->conn, tc->tempBuffer, sizeof(tc->tempBuffer)) <= 0 || (rc = getResponseCode(tc->tempBuffer)) != 235)
          ER_NewError(tr(MSG_ER_BADRESPONSE_SMTP), tc->msn->hostname, (char *)SMTPcmd[ESMTP_AUTH_PLAIN], tc->tempBuffer);
        else
          rc = SMTP_ACTION_OK;
      }
    }
    break;

    default:
    {
      W(DBF_NET, "The SMTP server seems not to support any of the selected or automatic specified SMTP-AUTH methods");

      // if we don't have any of the Authentication Flags turned on we have to
      // exit with an error
      ER_NewError(tr(MSG_CO_ER_SMTPAUTH), tc->msn->hostname);
    }
    break;
  }

  D(DBF_NET, "Server responded with %ld", rc);

  RETURN((BOOL)(rc == SMTP_ACTION_OK));
  return (BOOL)(rc == SMTP_ACTION_OK);
}

///
/// SendMessage
// Sends a single message (-1 signals an error in DATA phase, 0 signals
// error in start phase, 1 and 2 signals success)
static int SendMessage(struct TransferContext *tc, struct Mail *mail)
{
  int result = 0;
  char mailfile[SIZE_PATHFILE];
  FILE *fh = NULL;
  char *buf = NULL;
  size_t buflen = SIZE_LINE;

  ENTER();

  GetMailFile(mailfile, sizeof(mailfile), tc->outFolder, mail);

  D(DBF_NET, "about to send mail '%s' via SMTP server '%s'", mailfile, tc->msn->hostname);

  // open the mail file for reading
  if((buf = malloc(buflen)) != NULL &&
     (fh = fopen(mailfile, "r")) != NULL)
  {
    setvbuf(fh, NULL, _IOFBF, SIZE_FILEBUF);

    // now we put together our parameters for our MAIL command
    // which in fact may contain serveral parameters as well according
    // to ESMTP extensions.
    snprintf(buf, buflen, "FROM:<%s>", tc->uin->address);

    // in case the server supports the ESMTP SIZE extension lets add the
    // size
    if(hasSIZE(tc->msn->smtpFlags) && mail->Size > 0)
      snprintf(buf, buflen, "%s SIZE=%ld", buf, mail->Size);

    // in case the server supports the ESMTP 8BITMIME extension we can
    // add information about the encoding mode
    if(has8BITMIME(tc->msn->smtpFlags))
      snprintf(buf, buflen, "%s BODY=%s", buf, hasServer8bit(tc->msn) ? "8BITMIME" : "7BIT");

    // send the MAIL command with the FROM: message
    if(SendSMTPCommand(tc, SMTP_MAIL, buf, tr(MSG_ER_BADRESPONSE_SMTP)) != NULL)
    {
      struct ExtendedMail *email = MA_ExamineMail(tc->outFolder, mail->MailFile, TRUE);

      if(email != NULL)
      {
        BOOL rcptok = TRUE;
        int j;

        // if the mail in question has some "Resent-To:" mail
        // header information we use that information instead
        // of the one of the original mail
        if(email->NumResentTo > 0)
        {
          for(j=0; j < email->NumResentTo; j++)
          {
            snprintf(buf, buflen, "TO:<%s>", email->ResentTo[j].Address);
            if(SendSMTPCommand(tc, SMTP_RCPT, buf, tr(MSG_ER_BADRESPONSE_SMTP)) == NULL)
              rcptok = FALSE;
          }
        }
        else
        {
          // specify the main 'To:' recipient
          snprintf(buf, buflen, "TO:<%s>", mail->To.Address);
          if(SendSMTPCommand(tc, SMTP_RCPT, buf, tr(MSG_ER_BADRESPONSE_SMTP)) == NULL)
            rcptok = FALSE;

          // now add the additional 'To:' recipients of the mail
          for(j=0; j < email->NumSTo && rcptok; j++)
          {
            snprintf(buf, buflen, "TO:<%s>", email->STo[j].Address);
            if(SendSMTPCommand(tc, SMTP_RCPT, buf, tr(MSG_ER_BADRESPONSE_SMTP)) == NULL)
              rcptok = FALSE;
          }
        }

        // Add "Resent-CC:" recipients first
        if(email->NumResentCC > 0)
        {
          for(j=0; j < email->NumResentCC; j++)
          {
            snprintf(buf, buflen, "TO:<%s>", email->ResentCC[j].Address);
            if(SendSMTPCommand(tc, SMTP_RCPT, buf, tr(MSG_ER_BADRESPONSE_SMTP)) == NULL)
              rcptok = FALSE;
          }
        }
        else
        {
          // add the 'Cc:' recipients
          for(j=0; j < email->NumCC && rcptok; j++)
          {
            snprintf(buf, buflen, "TO:<%s>", email->CC[j].Address);
            if(SendSMTPCommand(tc, SMTP_RCPT, buf, tr(MSG_ER_BADRESPONSE_SMTP)) == NULL)
              rcptok = FALSE;
          }
        }

        // Add "Resent-BCC" recipients first
        if(email->NumResentBCC > 0)
        {
          for(j=0; j < email->NumResentBCC; j++)
          {
            snprintf(buf, buflen, "TO:<%s>", email->ResentBCC[j].Address);
            if(SendSMTPCommand(tc, SMTP_RCPT, buf, tr(MSG_ER_BADRESPONSE_SMTP)) == NULL)
              rcptok = FALSE;
          }
        }
        else
        {
          // add the 'BCC:' recipients
          for(j=0; j < email->NumBCC && rcptok; j++)
          {
            snprintf(buf, buflen, "TO:<%s>", email->BCC[j].Address);
            if(SendSMTPCommand(tc, SMTP_RCPT, buf, tr(MSG_ER_BADRESPONSE_SMTP)) == NULL)
              rcptok = FALSE;
          }
        }

        if(rcptok == TRUE)
        {
          D(DBF_NET, "RCPTs accepted, sending mail data");

          // now we send the actual main data of the mail
          if(SendSMTPCommand(tc, SMTP_DATA, NULL, tr(MSG_ER_BADRESPONSE_SMTP)) != NULL)
          {
            BOOL lineskip = FALSE;
            BOOL inbody = FALSE;
            ssize_t curlen;
            ssize_t proclen = 0;
            size_t sentbytes = 0;

            // as long there is no abort situation we go on reading out
            // from the stream and sending it to our SMTP server

            while(tc->conn->abort == FALSE && tc->conn->error == CONNECTERR_NO_ERROR &&
                  (curlen = getline(&buf, &buflen, fh)) > 0)
            {
              #if defined(DEBUG)
              if(curlen > 998)
                W(DBF_NET, "RFC2822 violation: line length in source file is too large: %ld", curlen);
              #endif

              // as long as we process header lines we have to make differ in some ways.
              if(inbody == FALSE)
              {
                // we check if we found the body of the mail now
                // the start of a body is seperated by the header with a single
                // empty line and we have to make sure that it isn't the beginning of the file
                if(curlen == 1 && buf[0] == '\n' && proclen > 0)
                {
                  inbody = TRUE;
                  lineskip = FALSE;
                }
                else if(isspace(*buf) == FALSE) // headerlines don't start with a space
                {
                  // we make sure we don't send out BCC:, Resent-BCC: and X-YAM-#? headerlines
                  // because these lines should never be seen by others.
                  if(strnicmp(buf, "bcc", 3) == 0 ||
                     strnicmp(buf, "x-yam-", 6) == 0 ||
                     strnicmp(buf, "resent-bcc", 10) == 0)
                  {
                    lineskip = TRUE;
                  }
                  else
                    lineskip = FALSE;
                }
              }

              // lets save the length we have processed already
              proclen = curlen;

              // if we don't skip this line we write it out to the SMTP server
              if(lineskip == FALSE)
              {
                // RFC 821 says a starting period needs a second one
                // so we send out a period in advance
                if(buf[0] == '.')
                {
                  if(SendToHost(tc->conn, ".", 1, TCPF_NONE) <= 0)
                  {
                    E(DBF_NET, "couldn't send single '.' to SMTP server");

                    ER_NewError(tr(MSG_ER_CONNECTIONBROKEN), tc->msn->hostname, (char *)SMTPcmd[SMTP_DATA]);
                    break;
                  }
                  else
                    sentbytes++;
                }

                // if the last char is a LF we have to skip it right now
                // as we have to send a CRLF per definition of the RFC
                if(buf[curlen-1] == '\n')
                  curlen--;

                // now lets send the data buffered to the socket.
                // we will flush it later then.
                if(curlen > 0 && SendToHost(tc->conn, buf, curlen, TCPF_NONE) <= 0)
                {
                  E(DBF_NET, "couldn't send buffer data to SMTP server (%ld)", curlen);

                  ER_NewError(tr(MSG_ER_CONNECTIONBROKEN), tc->msn->hostname, (char *)SMTPcmd[SMTP_DATA]);
                  break;
                }
                else
                  sentbytes += curlen;

                // now we send the final CRLF (RFC 2822)
                if(SendToHost(tc->conn, "\r\n", 2, TCPF_NONE) <= 0)
                {
                  E(DBF_NET, "couldn't send CRLF to SMTP server");

                  ER_NewError(tr(MSG_ER_CONNECTIONBROKEN), tc->msn->hostname, (char *)SMTPcmd[SMTP_DATA]);
                  break;
                }
                else
                  sentbytes += 2;
              }

              PushMethodOnStack(tc->transferGroup, 3, MUIM_TransferControlGroup_Update, proclen, tr(MSG_TR_Sending));
            }

            D(DBF_NET, "transfered %ld bytes (raw: %ld bytes) error: %ld/%ld", sentbytes, mail->Size, tc->conn->abort, tc->conn->error);

            if(tc->conn->abort == FALSE && tc->conn->error == CONNECTERR_NO_ERROR)
            {
              // check if any of the above getline() operations caused a ferror or
              // if we didn't walk until the end of the mail file
              if(ferror(fh) != 0 || feof(fh) == 0)
              {
                E(DBF_NET, "input mail file returned error state: ferror(fh)=%ld feof(fh)=%ld", ferror(fh), feof(fh));

                ER_NewError(tr(MSG_ER_ErrorReadMailfile), mailfile);
                result = -1; // signal error
              }
              else
              {
                // we have to flush the write buffer if this wasn't a error or
                // abort situation
                SendToHost(tc->conn, NULL, 0, TCPF_FLUSHONLY);

                // send a CRLF+octet "\r\n." to signal that the data is finished.
                // we do it here because if there was an error and we send it, the message
                // will be send incomplete.
                if(SendSMTPCommand(tc, SMTP_FINISH, NULL, tr(MSG_ER_BADRESPONSE_SMTP)) != NULL)
                {
                  // put the transferStat to 100%
                  PushMethodOnStack(tc->transferGroup, 3, MUIM_TransferControlGroup_Update, TCG_SETMAX, tr(MSG_TR_Sending));

                  // now that we are at 100% we have to set the transfer Date of the message
                  GetSysTimeUTC(&mail->transDate);

                  result = email->DelSent ? 2 : 1;
                  AppendToLogfile(LF_VERBOSE, 42, tr(MSG_LOG_SendingVerbose), AddrName(mail->To), mail->Subject, mail->Size);
                }
              }
            }

            if(tc->conn->abort == TRUE || tc->conn->error != CONNECTERR_NO_ERROR)
              result = -1; // signal the caller that we aborted within the DATA part
          }
        }

        MA_FreeEMailStruct(email);
      }
      else
        ER_NewError(tr(MSG_ER_CantOpenFile), mailfile);
    }

    fclose(fh);
  }
  else if(buf != NULL)
    ER_NewError(tr(MSG_ER_CantOpenFile), mailfile);

  free(buf);

  RETURN(result);
  return result;
}

///
/// SendMails
BOOL SendMails(struct UserIdentityNode *uin, struct MailList *mailsToSend, enum SendMailMode mode, const ULONG flags)
{
  BOOL success = FALSE;
  struct TransferContext *tc;
  struct MailServerNode *msn = uin->smtpServer;

  ENTER();

  // make sure the mail server node does not vanish
  ObtainSemaphoreShared(G->configSemaphore);

  if((tc = calloc(1, sizeof(*tc))) != NULL)
  {
    // link the user identity and the mail server in
    // our transfercontext structure
    tc->uin = uin;
    tc->msn = msn;

    tc->outFolder = FO_GetFolderByType(FT_OUTGOING, NULL);

    // depending on the sentfolder settings we store the mail in a
    // different folder. That said the following order of sent folder
    // settings is applied:
    //
    // 1. configured 'Sent' folder in user identity
    // 2. if not set, configured 'Sent' folder in SMTP settings
    // 3. if not set, default 'Sent' folder (first SENT folder found)
    if(tc->uin->sentFolderID == 0 || (tc->sentFolder = FindFolderByID(G->folders, tc->uin->sentFolderID)) == NULL)
    {
      if(tc->msn->mailStoreFolderID == 0 || (tc->sentFolder = FindFolderByID(G->folders, tc->msn->mailStoreFolderID)) == NULL)
        tc->sentFolder = FO_GetFolderByType(FT_SENT, NULL);
    }

    if(tc->sentFolder != NULL)
    {
      // try to open the TCP/IP stack
      if((tc->conn = CreateConnection(TRUE)) != NULL && ConnectionIsOnline(tc->conn) == TRUE)
      {
        struct MailTransferList *transferList;

        // copy a link to the mailservernode for which we created
        // the connection
        tc->conn->server = tc->msn;

        if((transferList = CreateMailTransferList()) != NULL)
        {
          ULONG totalSize = 0;
          struct MailNode *mnode;

          if(isFlagClear(flags, SENDF_TEST_CONNECTION))
          {
            // start the PRESEND macro first and wait for it to terminate
            PushMethodOnStackWait(G->App, 3, MUIM_YAMApplication_StartMacro, MACRO_PRESEND, NULL);

            // now we build the list of mails to be transfered.
            LockMailListShared(mailsToSend);

            ForEachMailNode(mailsToSend, mnode)
            {
              struct Mail *mail = mnode->mail;
              struct MailTransferNode *tnode;

              if((tnode = CreateMailTransferNode(mail, TRF_TRANSFER)) != NULL)
              {
                AddMailTransferNode(transferList, tnode);

                tnode->index = transferList->count;
                totalSize += mail->Size;
              }
            }

            UnlockMailList(mailsToSend);

            D(DBF_NET, "prepared %ld mails for sending, %ld bytes", transferList->count, totalSize);
          }

          // just go on if we really have something to send
          if(isFlagSet(flags, SENDF_TEST_CONNECTION) || transferList->count > 0)
          {
            ULONG twFlags;

            snprintf(tc->transferGroupTitle, sizeof(tc->transferGroupTitle), tr(MSG_TR_MailTransferTo), msn->hostname);

            twFlags = TWF_OPEN;
            if(mode == SENDMAIL_ALL_USER || mode == SENDMAIL_ACTIVE_USER)
              setFlag(twFlags, TWF_ACTIVATE);

            D(DBF_GUI, "create transfer control group");
            if((tc->transferGroup = (Object *)PushMethodOnStackWait(G->App, 5, MUIM_YAMApplication_CreateTransferGroup, CurrentThread(), tc->transferGroupTitle, tc->conn, twFlags)) != NULL)
            {
              struct MinList *sentMailFilters;

              D(DBF_NET, "clone sent mail filters");
              if((sentMailFilters = CloneFilterList(APPLY_SENT)) != NULL)
              {
                struct BusyNode *busy;
                enum ConnectError err;

                PushMethodOnStack(tc->transferGroup, 3, MUIM_TransferControlGroup_Start, transferList->count, totalSize);

                PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_Connecting));

                busy = BusyBegin(BUSY_TEXT);
                BusyText(busy, tr(MSG_TR_MailTransferTo), msn->hostname);

                D(DBF_NET, "connecting to host '%s' port %ld", msn->hostname, msn->port);
                if((err = ConnectToHost(tc->conn, tc->msn)) == CONNECTERR_SUCCESS)
                {
                  BOOL connected = FALSE;

                  // first we check whether the user wants to connect to a plain SSLv3 server
                  // so that we initiate the SSL connection now
                  if(hasServerSSL(tc->msn) == TRUE)
                  {
                    // lets try to establish the SSL connection via AmiSSL
                    if(MakeSecureConnection(tc->conn) == TRUE)
                      tc->useTLS = TRUE;
                    else
                      err = CONNECTERR_SSLFAILED; // special SSL connection error
                  }

                  // first we have to check whether the TCP/IP connection could
                  // be successfully opened so that we can init the SMTP connection
                  // and query the SMTP server for its capabilities now.
                  if(err == CONNECTERR_SUCCESS)
                  {
                    // initialize the SMTP connection which will also
                    // query the SMTP server for its capabilities
                    connected = ConnectToSMTP(tc);

                    // Now we have to check whether the user has selected SSL/TLS
                    // and then we have to initiate the STARTTLS command followed by the TLS negotiation
                    if(connected == TRUE && hasServerTLS(tc->msn) == TRUE)
                    {
                      connected = InitSTARTTLS(tc);

                      // then we have to refresh the SMTPflags and check
                      // again what features we have after the STARTTLS
                      if(connected == TRUE)
                      {
                        // first we flag this connection as a sucessfull
                        // TLS session
                        tc->useTLS = TRUE;

                        // now run the connect SMTP function again
                        // so that the SMTP server flags will be refreshed
                        // accordingly.
                        connected = ConnectToSMTP(tc);
                      }
                    }

                    // If the user selected SMTP_AUTH we have to initiate
                    // a AUTH connection
                    if(connected == TRUE && hasServerAuth(tc->msn) == TRUE)
                      connected = InitSMTPAUTH(tc);
                  }

                  // If we are still "connected" we can proceed with transfering the data
                  if(connected == TRUE)
                  {
                    struct MailTransferNode *tn;

                    // set the success to TRUE as everything worked out fine
                    // until here.
                    success = TRUE;
                    AppendToLogfile(LF_VERBOSE, 41, tr(MSG_LOG_ConnectSMTP), msn->hostname);

                    if(isFlagClear(flags, SENDF_TEST_CONNECTION))
                    {
                      ForEachMailTransferNode(transferList, tn)
                      {
                        struct Mail *mail = tn->mail;

                        if(tc->conn->abort == TRUE || tc->conn->error != CONNECTERR_NO_ERROR)
                          break;

                        PushMethodOnStack(tc->transferGroup, 5, MUIM_TransferControlGroup_Next, tn->index, -1, mail->Size, tr(MSG_TR_Sending));

                        switch(SendMessage(tc, mail))
                        {
                          // -1 means that SendMessage was aborted within the
                          // DATA part and so we cannot issue a RSET command and have to abort
                          // immediatly by leaving the mailserver alone.
                          case -1:
                          {
                            setStatusToError(mail);
                            tc->conn->error = CONNECTERR_UNKNOWN_ERROR;
                          }
                          break;

                          // 0 means that an error occured before the DATA part and
                          // so we can abort the transaction cleanly by a RSET and QUIT
                          case 0:
                          {
                            setStatusToError(mail);
                            SendSMTPCommand(tc, SMTP_RSET, NULL, NULL); // no error check
                            tc->conn->error = CONNECTERR_NO_ERROR;
                          }
                          break;

                          // 1 means we filter the mails and then copy/move the mail to the send folder
                          case 1:
                          {
                            setStatusToSent(mail);
                            if(PushMethodOnStackWait(G->App, 3, MUIM_YAMApplication_FilterMail, sentMailFilters, mail) == TRUE)
                            {
                              // the filter process did not move the mail, hence we do it now
                              PushMethodOnStackWait(G->App, 5, MUIM_YAMApplication_MoveCopyMail, mail, tc->sentFolder, "sent mail", MVCPF_CLOSE_WINDOWS);
                            }
                            else
                            {
                              // update the Outgoing folder's stats as the mail just got (re)moved
                              PushMethodOnStack(G->App, 3, MUIM_YAMApplication_DisplayStatistics, tc->outFolder, TRUE);
                            }
                          }
                          break;

                          // 2 means we filter and delete afterwards
                          case 2:
                          {
                            setStatusToSent(mail);
                            if(PushMethodOnStackWait(G->App, 3, MUIM_YAMApplication_FilterMail, sentMailFilters, mail) == TRUE)
                            {
                              // the filter process did not delete the mail, hence we do it now
                              PushMethodOnStackWait(G->App, 3, MUIM_YAMApplication_DeleteMail, mail, DELF_UPDATE_APPICON);
                            }
                          }
                          break;
                        }
                      }
                    }

                    PushMethodOnStack(tc->transferGroup, 1, MUIM_TransferControlGroup_Finish);

                    if(tc->conn->error == CONNECTERR_NO_ERROR)
                      AppendToLogfile(LF_NORMAL, 40, tr(MSG_LOG_Sending), transferList->count, msn->hostname);
                    else
                      AppendToLogfile(LF_NORMAL, 40, tr(MSG_LOG_SENDING_FAILED), transferList->count, msn->hostname);

                    // now we can disconnect from the SMTP
                    // server again
                    PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_Disconnecting));

                    // send a 'QUIT' command, but only if
                    // we didn't receive any error during the transfer
                    if(tc->conn->error == CONNECTERR_NO_ERROR)
                      SendSMTPCommand(tc, SMTP_QUIT, NULL, tr(MSG_ER_BADRESPONSE_SMTP));
                  }
                  else
                  {
                    // check if we end up here cause of the 8BITMIME differences
                    if(has8BITMIME(tc->msn->smtpFlags) == FALSE && hasServer8bit(tc->msn) == TRUE)
                    {
                      W(DBF_NET, "incorrect Allow8bit setting!");
                      err = CONNECTERR_INVALID8BIT;
                    }
                    else if(err != CONNECTERR_SSLFAILED)
                      err = CONNECTERR_UNKNOWN_ERROR;
                  }
                }

                // make sure to shutdown the socket and all possible SSL connection stuff
                DisconnectFromHost(tc->conn);

                // if we got an error here, let's throw it
                switch(err)
                {
                  case CONNECTERR_SUCCESS:
                  case CONNECTERR_ABORTED:
                  case CONNECTERR_NO_ERROR:
                    // do nothing
                  break;

                  // a socket is already in use so we return
                  // a specific error to the user
                  case CONNECTERR_SOCKET_IN_USE:
                    ER_NewError(tr(MSG_ER_CONNECTERR_SOCKET_IN_USE_SMTP), msn->hostname);
                  break;

                  // socket() execution failed
                  case CONNECTERR_NO_SOCKET:
                    ER_NewError(tr(MSG_ER_CONNECTERR_NO_SOCKET_SMTP), msn->hostname);
                  break;

                  // couldn't establish non-blocking IO
                  case CONNECTERR_NO_NONBLOCKIO:
                    ER_NewError(tr(MSG_ER_CONNECTERR_NO_NONBLOCKIO_SMTP), msn->hostname);
                  break;

                  // the specified hostname isn't valid, so
                  // lets tell the user
                  case CONNECTERR_UNKNOWN_HOST:
                    ER_NewError(tr(MSG_ER_UNKNOWN_HOST_SMTP), msn->hostname);
                  break;

                  // the connection request timed out, so tell
                  // the user
                  case CONNECTERR_TIMEDOUT:
                    ER_NewError(tr(MSG_ER_CONNECTERR_TIMEDOUT_SMTP), msn->hostname);
                  break;

                  // an error occurred while checking for 8bit MIME
                  // compatibility
                  case CONNECTERR_INVALID8BIT:
                    ER_NewError(tr(MSG_ER_NO8BITMIME_SMTP), msn->hostname);
                  break;

                  // error during initialization of an SSL connection
                  case CONNECTERR_SSLFAILED:
                    ER_NewError(tr(MSG_ER_INITTLS_SMTP), msn->hostname);
                  break;

                  // an unknown error occurred so lets show
                  // a generic error message
                  case CONNECTERR_UNKNOWN_ERROR:
                    ER_NewError(tr(MSG_ER_CANNOT_CONNECT_SMTP), msn->hostname);
                  break;

                  case CONNECTERR_NO_CONNECTION:
                  case CONNECTERR_NOT_CONNECTED:
                    // cannot happen, do nothing
                  break;
                }

                BusyEnd(busy);

                DeleteFilterList(sentMailFilters);
              }
            }

            PushMethodOnStack(G->App, 2, MUIM_YAMApplication_DeleteTransferGroup, tc->transferGroup);
          }

          // remove any mail transfer nodes from the list and delete the list
          DeleteMailTransferList(transferList);

          // start the POSTSEND macro so that others notice that the
          // send process has finished. No need to wait for termination.
          PushMethodOnStack(G->App, 3, MUIM_YAMApplication_StartMacro, MACRO_POSTSEND, NULL);
        }
      }

      DeleteConnection(tc->conn);
    }
    else
    {
      E(DBF_FOLDER, "could not resolve sent folder of user identity '%s and SMTP server '%s'", tc->uin->description, tc->msn->description);
    }

    free(tc);
  }

  CleanMailsInTransfer(mailsToSend);
  // delete the list of mails
  DeleteMailList(mailsToSend);

  // mark the server as being no longer "in use"
  LockMailServer(msn);
  msn->useCount--;
  UnlockMailServer(msn);

  // now we are done
  ReleaseSemaphore(G->configSemaphore);

  // wake up the calling thread if this is requested
  if(isFlagSet(flags, SENDF_SIGNAL))
    WakeupThread(NULL);

  RETURN(success);
  return success;
}

///
/// CleanMailsInTransfer
// remove all mails in a list from the global list of mails in transfer
void CleanMailsInTransfer(const struct MailList *mlist)
{
  struct MailNode *mnode;

  ENTER();

  LockMailList(G->mailsInTransfer);
  ForEachMailNode(mlist, mnode)
  {
    struct MailNode *mnode2;

    if((mnode2 = FindMailByAddress(G->mailsInTransfer, mnode->mail)) != NULL)
    {
      RemoveMailNode(G->mailsInTransfer, mnode2);
      DeleteMailNode(mnode2);
    }
  }
  UnlockMailList(G->mailsInTransfer);

  LEAVE();
}

///