/*
 *  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 Library 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.
 */

#include "sendmail.h"
#define LOGSENDINFO(buffer) if (get_verbose_mode()) { \
                              logmsg(LOG_INFO, "sendmail() : << %s", buffer); \
                            }
#define SENDINFO(s, buffer) if (!socket_write(s, 0, "%s\r\n", buffer)) { \
                              logmsg(LOG_ERR, "sendmail() : %s", strerror(errno)); \
                              FREE(buffer); \
                              socket_destroy(s); \
                              return 0; \
                            } \
                            else { \
                              FREE(buffer); \
                            }

static int g_sendmail_timeout = 2;
static int g_max_timeout_number = 2;

void set_sendmail_timeout(int second) {
  g_sendmail_timeout = second;
}

void set_max_timeout_number(int max) {
  g_max_timeout_number = max;
}
                            
int get_helo(char **buffer) {
  char ptr_tmp[MAXHOSTNAMELEN];
  size_t size;

  *buffer = strdup("EHLO ");
  if (*buffer == NULL)
    return 0;
  if (gethostname(ptr_tmp, MAXHOSTNAMELEN) == -1)
    return 0;
  else {
    size = strlen("EHLO ") + strlen(ptr_tmp) + 1;
    if (!saferealloc((void **)buffer, *buffer, size * sizeof(char))) {
      FREE(*buffer);
      return 0;
    }
    strlcat(*buffer, ptr_tmp, size * sizeof(char));
  }
  return 1;
}
  
int get_authplain(char **buffer, const char *authuser, const char *authpass) {
  char *auth_64 = NULL;
  char *ptr_tmp = NULL;
  size_t ptr_size, s;
  int authuser_len = 0, authpass_len = 0;
  
  *buffer = strdup("AUTH PLAIN ");
  if (*buffer == NULL)
    return 0;  
  ptr_size = strlen(authuser)*2 + 2 + strlen(authpass)+1;
  ptr_tmp = (char *)malloc(ptr_size * sizeof(char));
  if (ptr_tmp == NULL)
    return 0;
  
  /* We prepare the AUTH string in base64 style */
  /* <user>\0<user>\0<pass> */
  s = 0;
  authuser_len = strlen(authuser);
  authpass_len = strlen(authpass);
  memset(ptr_tmp, 0, ptr_size);
  memcpy(ptr_tmp, authuser, authuser_len * sizeof(char));
  s += authuser_len;
  memcpy(&ptr_tmp[s], "\0", sizeof(char));
  s += sizeof(char);
  memcpy(&ptr_tmp[s], authuser, authuser_len * sizeof(char));
  s += authuser_len;
  memcpy(&ptr_tmp[s], "\0", sizeof(char));
  s += sizeof(char);
  memcpy(&ptr_tmp[s], authpass, authpass_len * sizeof(char));
  s += authpass_len;
  memcpy(&ptr_tmp[s], "\0", sizeof(char));
  
  if (!base64_encode(&auth_64, ptr_tmp, ptr_size-1)) {
    FREE(ptr_tmp);
    return 0;
  }
  FREE(ptr_tmp);
  
  s = strlen("AUTH PLAIN ") + strlen(auth_64) + 1;
  if (!saferealloc((void **)buffer, *buffer, s * sizeof(char))) {
    FREE(*buffer);
    FREE(auth_64);
    return 0;
  }
  strlcat(*buffer, auth_64, s * sizeof(char));
  FREE(auth_64);
  return 1;
}

int get_mailfrom(char **buffer, const char *mailfrom) {
  size_t size;
  *buffer = strdup("MAIL FROM: <");
  if (*buffer == NULL)
    return 0;
  size = strlen("MAIL FROM: <") + strlen(mailfrom) + 2;
  if (!saferealloc((void **)buffer, *buffer, size * sizeof(char))) {
    FREE(*buffer);
    return 0;
  }
  strlcat(*buffer, mailfrom, size * sizeof(char));
  strlcat(*buffer, ">", size * sizeof(char));
  return 1;
}

int get_rcptto(char **buffer, const char *mailto) {
  size_t size;
  *buffer = strdup("RCPT TO: <");
  if (*buffer == NULL)
    return 0;
  size = strlen("RCPT TO: <") + strlen(mailto) + 2;
  if (!saferealloc((void **)buffer, *buffer, size * sizeof(char))) {
    free(*buffer);
    *buffer = NULL;
    return 0;
  }
  strlcat(*buffer, mailto, size * sizeof(char));
  strlcat(*buffer, ">", size * sizeof(char));
  return 1;
}

char *SMTP_STATE_STRING(SMTP_STATE state) {
  switch(state) {
    case NOTHING:
      return "NOTHING";
      break;
    case EHLO:
      return "EHLO";
      break;
    case MAIL_FROM:
      return "MAIL_FROM";
      break;
    case RCPT_TO:
      return "RCPT_TO";
      break;
    case AUTH_PLAIN:
      return "AUTH_PLAIN";
      break;
    case AUTH_LOGIN:
      return "AUTH_LOGIN";
      break;
    case DATA:
      return "DATA";
      break;
    case QUIT:
      return "QUIT";
      break;
  }
  return NULL;
}

int sendmail(const char *host, const char *port,
             const char *mailfrom, const char *mailto,
             const char *authuser, const char *authpass, SMTP_AUTH auth, 
             const char *additional_header, const char *msg) {
/*
  S : we send
  R : We receive
  The following is a SMTP communication example
               
  R: 220 server.com Simple Mail Transfer Service Ready
  S: EHLO server.com

  [R: 250 server.com]                                      -> no AUTH mecanism
    or
  [R: 250-server.com
  R: 250-PIPELINING
  R: 250-SIZE 10240000
  R: 250-VRFY
  R: 250-ETRN
  R: 250-AUTH DIGEST-MD5 CRAM-MD5 GSSAPI PLAIN LOGIN       -> AUTH mecanism
  R: 250-AUTH=DIGEST-MD5 CRAM-MD5 GSSAPI PLAIN LOGIN
  R: 250-XVERP
  R: 250 8BITMIME
  S: AUTH PLAIN dGVzdAB0ZXN0AHRlc3RwYXNz                   -> AUTH PLAIN mecanism
  R: 235 Authentication successful
  ]

  S: MAIL FROM: <sender@example.com>
  R: 250 OK
  S: RCPT TO: <receiver@example2.com>
  R: 250 OK
  S: DATA
  R: 354 Start mail input: end with <CRLF>.<CRLF>
  S: Type everything you want
  S: <CRLF>.<CRLF>
  R: 250 OK
  S: QUIT
  R: 250 server.com closing transmission channel             
               
*/
               
  Socket *s = NULL;
  fd_set fds;
  int n, byte_read, nb_timeout = 0;
  struct timeval tv;
  char buffer_in[255];
  char *buffer_out = NULL;
  size_t size;
  SMTP_STATE state = NOTHING;

  s = socket_new(host, port, "tcp");
  if (!socket_connect(s)) {
    logmsg(LOG_ERR, "sendmail() : connect() error on %s !", host);
    socket_destroy(s);
    return 0;
  }
  
  for(;;) {
    FD_ZERO(&fds);
    FD_SET(s->fd, &fds);
    
    tv.tv_sec = g_sendmail_timeout;
    tv.tv_usec = 0;
    
    if ((n = select(s->fd+1, &fds, NULL, NULL, &tv)) != -1) {
      if (n == 0) {
      /* select timeout */
        nb_timeout++;
        logmsg(LOG_ERR, "sendmail() : select() timeout (%d time(s))", nb_timeout);
        if (g_max_timeout_number >= nb_timeout) {
          logmsg(LOG_ERR, "sendmail() : max number of select() timeout reached (%d time(s) max)",
                 g_max_timeout_number);
          break;
        }
      }
      else {
        if (FD_ISSET(s->fd, &fds)) {
          nb_timeout = 0;
          memset(buffer_in, 0, 255);
          if ((byte_read = socket_readline(s, buffer_in, sizeof(buffer_in))) > 0) {
            stripcrlf(buffer_in);
            if (get_verbose_mode())
              logmsg(LOG_INFO, "sendmail() : >> %s", buffer_in);
  
            if (strncmp(buffer_in, "220 ", strlen("220 ")) == 0) {
            /* 220 SMTP Server Name */
              if (!get_helo(&buffer_out)) {
                logmsg(LOG_ERR, "sendmail() : problem in get_helo()");
                socket_destroy(s);
                return 0;
              }            
              LOGSENDINFO(buffer_out);
              SENDINFO(s, buffer_out);            
              state = EHLO;
            }
            else if (strncmp(buffer_in, "250-AUTH ", strlen("250-AUTH ")) == 0) {
            /* 250-AUTH PLAIN LOGIN */
              if ((authuser == NULL) || (authpass == NULL) || (auth == NONE)) {
                logmsg(LOG_ERR, "sendmail() : AUTH method required : set a username/password in your config file");
                socket_destroy(s);
                return 0;
              }
              /* We check the LOGIN method, first */
              if ((strstr(buffer_in, " LOGIN") != NULL)
              && (auth == LOGIN)) {
                state = AUTH_LOGIN;
              }
              else if ((strstr(buffer_in, " PLAIN") != NULL)
              && (auth == PLAIN)) {
                state = AUTH_PLAIN;
              }
              else {
                logmsg(LOG_ERR, "sendmail() : AUTH method other than LOGIN PLAIN, not supported yet");
                socket_destroy(s);
                return 0;
              }
            }
            else if (strncmp(buffer_in, "250-", strlen("250-")) == 0) {
            /* We skip other 250 sub messages */
            }
            else if (strncmp(buffer_in, "221 ", strlen("221 ")) == 0) {
            /* 221 Bye */
              socket_destroy(s);
              return 1; /* We close the connection and it's OK */
            }
            else if (strncmp(buffer_in, "235 ", strlen("235 ")) == 0) {
            /* 235 Authentication successful */
              /* We send "MAIL FROM" command */
              if (!get_mailfrom(&buffer_out, mailfrom)) {
                logmsg(LOG_ERR, "sendmail() : problem in get_mailfrom()");
                socket_destroy(s);
                return 0;
              }
              LOGSENDINFO(buffer_out);
              SENDINFO(s, buffer_out);
              state = MAIL_FROM;
            }          
            else if (strncmp(buffer_in, "250 ", strlen("250 ")) == 0) {
            /* 250 OK */
              switch(state) {
                case EHLO:
                /* We send "MAIL FROM" command */
                  if (!get_mailfrom(&buffer_out, mailfrom)) {
                    logmsg(LOG_ERR, "sendmail() : problem in get_mailfrom()");
                    socket_destroy(s);
                    return 0;
                  }
                  LOGSENDINFO(buffer_out);
                  SENDINFO(s, buffer_out);
                  state = MAIL_FROM;
                  break;
                case MAIL_FROM:
                /* We send "RCPT TO" command */
                  if (!get_rcptto(&buffer_out, mailto)) {
                    logmsg(LOG_ERR, "sendmail() : problem in get_rcptto()");
                    socket_destroy(s);
                    return 0;
                  }
                  LOGSENDINFO(buffer_out);
                  SENDINFO(s, buffer_out);
                  state = RCPT_TO;
                  break;
                case RCPT_TO:
                /* We send "DATA" */
                  buffer_out = strdup("DATA");
                  LOGSENDINFO(buffer_out);
                  SENDINFO(s, buffer_out);
                  state = DATA;
                  break;
                case DATA:
                /* We send "quit" */
                  buffer_out = strdup("QUIT");
                  LOGSENDINFO(buffer_out);
                  SENDINFO(s, buffer_out);
                  state = QUIT;
                  break;
                case AUTH_PLAIN:
                /* We do AUTH PLAIN */
                  if (!get_authplain(&buffer_out, authuser, authpass)) {
                    logmsg(LOG_ERR, "sendmail() : problem in get_authplain()");
                    socket_destroy(s);
                    return 0;
                  }
                  LOGSENDINFO(buffer_out);
                  SENDINFO(s, buffer_out);
                  break;
                default:
                  logmsg(LOG_ERR, "sendmail() : 250 : current state %s", SMTP_STATE_STRING(state));
                  socket_destroy(s);
                  return 0;
                  break;
              }
            }
            else if (strncmp(buffer_in, "354 ", strlen("354 ")) == 0) {
            /* 354 Start mail input: end with <CRLF>.<CRLF> */
              switch(state) {
                case DATA:
                  /* Send a proper header */
                  size = strlen("From: ") + strlen(mailfrom) + 2 /* \r\n */  +
                         strlen("To: ") + strlen(mailto) + 2 /* \r\n */  +
                         (additional_header != NULL ? strlen(additional_header) : 0) + 2 /* \r\n */ + 1;
                  if (!saferealloc((void **)&buffer_out, buffer_out, size * sizeof(char))) {
                    logmsg(LOG_ERR, "sendmail() : %s", strerror(errno));
                    socket_destroy(s);
                    return 0;
                  }
                  *buffer_out = '\0';
                  strlcat(buffer_out, "From: ", size * sizeof(char));
                  strlcat(buffer_out, mailfrom, size * sizeof(char));
                  strlcat(buffer_out, "\r\n", size * sizeof(char));
                  strlcat(buffer_out, "To: ", size * sizeof(char));
                  strlcat(buffer_out, mailto, size * sizeof(char));
                  strlcat(buffer_out, "\r\n", size * sizeof(char));
                  if (additional_header != NULL) {
                    strlcat(buffer_out, additional_header, size * sizeof(char));
                    strlcat(buffer_out, "\r\n", size * sizeof(char));
                  }
                  SENDINFO(s, buffer_out);
                
                  /* We can now send the body */
                  buffer_out = strdup(msg);
                  size = strlen(msg) + strlen("\r\n.") + 1;
                  if (!saferealloc((void **)&buffer_out, buffer_out, size * sizeof(char))) {
                    logmsg(LOG_ERR, "sendmail() : %s", strerror(errno));
                    socket_destroy(s);
                    return 0;
                  }
                  strlcat(buffer_out, "\r\n.", size * sizeof(char));
                  SENDINFO(s, buffer_out);
                  break;
                default:
                  logmsg(LOG_ERR, "sendmail() : 354 : current state %s", SMTP_STATE_STRING(state));
                  socket_destroy(s);
                  return 0;
                  break;
              }
            }
            else if (strncmp(buffer_in, "535 ", strlen("535 ")) == 0) {
            /* 535 Error: Authentication failed */
              logmsg(LOG_ERR, "sendmail() : %s", buffer_in);
              break;  
            }
            else {
            /* We have a SMTP error */
              logmsg(LOG_ERR, "sendmail() : SMTP Error -> %s", buffer_in);
              break;
            }
          }
          else {
            if (errno != 0)
              logmsg(LOG_ERR, "sendmail() : readline error : %s (%d)", strerror(errno), errno);
            else
              logmsg(LOG_ERR, "sendmail() : readline error");
            break;
          }
        }
      }
    }
    else {
      logmsg(LOG_ERR, "sendmail() : select() error");
      break;
    }
  }
  
  socket_destroy(s);
  return 0;
  
}


syntax highlighted by Code2HTML, v. 0.9.1