#ifdef linux
/*
 sendmail.c
 From Palm Programming: the Developer's Guide
 by Neil Rhodes and Julie McKeehan
 

*/
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#else
#include <sys_socket.h>
#endif

// Application headers

#include "sendmail.h"
 

static const int kLinefeedChr = '\012';
static const int kCrChr = '\015';

static StatusCallbackFunc gStatusFunc;
static ErrorCallbackFunc  gErrorFunc;

/* This function reads from a socket, until it recieves a linefeed
   character.  It fills the buffer "str" up to the maximum size "count".

   This function will return -1 if the socket is closed during the read
   operation.

   Note that if a single line exceeds the length of count, the extra data
   will be read and discarded!  You have been warned.

 この関数は、改行文字を受けるまで、ソケットから文字を読み出して、
 "count"を最大文字数として、"str"で示されるバッファに取り込む。

 読み出し中にソケットがクローズされた場合、この関数は-1を返却する

 に注意。
*/

static int sock_gets(int sockfd, char *str, size_t count)
{
  int bytes_read;
  int total_count = 0;
  char *current_position;
  char last_read = 0;
  const char kLinefeed = 10;
  const char kCR = 13;

  current_position = str;
  while (last_read != kLinefeed) {
    bytes_read = read(sockfd, &last_read, 1);
    if (bytes_read <= 0) {
      /* The other side may have closed unexpectedly */
      return -1; /* Is this effective on other platforms than linux? */
    }
    if ( (total_count < count) && (last_read != kLinefeed) &&
      (last_read !=kCR) ) {
      current_position[0] = last_read;
      current_position++;
      total_count++;
    }
  }
  if (count > 0)
    current_position[0] = 0;
  return total_count;
}
 

#define IsDigit(c) ((c) >= '0' && (c) <= '9')

// reads lines until we get a non-continuation line
static int ReadReply(int fd, char *s, unsigned int sLen)
{
 int  numBytes;
 do {
  numBytes = sock_gets(fd, s, sLen);
 } while (numBytes >= 0 && !(strlen(s) >= 4 && s[3] == ' ' &&
  IsDigit(s[0]) && IsDigit(s[1]) && IsDigit(s[2])));
 if (numBytes < 0)
   return numBytes;
 else
  return 0;
}

/* Take a service name, and a service type, and return a port number.  he number
   returned is byte ordered for the network. */
static int atoport(char *service, char *proto)
{
  int port;
  struct servent *serv;

  /* First try to read it from /etc/services */
  serv = getservbyname(service, proto);
  if (serv != NULL)
    port = serv->s_port;
  else {
      return -1; /* Invalid port address */
  }
  return port;
}
 

/* Converts ascii text to in_addr struct.  NULL is returned if the address
   can not be found. */
static struct in_addr *atoaddr(char *address)
{
  struct hostent *host;
  static struct in_addr saddr;

  /* First try it as aaa.bbb.ccc.ddd. */
  saddr.s_addr = inet_addr(address);
  if (saddr.s_addr != -1) {
    return &saddr;
  }
  host = gethostbyname(address);
  if (host != NULL) {
    return (struct in_addr *) *host->h_addr_list;
  }
  return NULL;
}

/* This is a generic function to make a connection to a given server/port.
   service is the port name/number,
   type is either SOCK_STREAM or SOCK_DGRAM, and
   netaddress is the host name to connect to.
   The function returns the socket, ready for action.*/
static int make_connection(char *service, int type, char *netaddress)
{
  /* First convert service from a string, to a number... */
  int port = -1;
  struct in_addr *addr;
  int sock, connected;
  struct sockaddr_in address;

  if (type == SOCK_STREAM)
    port = atoport(service, "tcp");
  if (type == SOCK_DGRAM)
    port = atoport(service, "udp");
  if (port == -1) {
 (*gErrorFunc)("make_connection:  Invalid socket type.\n", NULL);
    return -1;
  }
  addr = atoaddr(netaddress);
  if (addr == NULL) {
    (*gErrorFunc)("make_connection:  Invalid network address.\n", NULL);
    return -1;
  }

  memset((char *) &address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = (port);
  address.sin_addr.s_addr = addr->s_addr;

  sock = socket(AF_INET, type, 0);

  if (type == SOCK_STREAM) {
    connected = connect(sock, (struct sockaddr *) &address,
      sizeof(address));
    if (connected < 0) {
   (*gErrorFunc)("connect", NULL);
      return -1;
    }
    return sock;
  }
  /* Otherwise, must be for udp, so bind to address. */
  if (bind(sock, (struct sockaddr *) &address, sizeof(address)) < 0) {
 (*gErrorFunc)("bind", NULL);
    return -1;
  }
  return sock;
}
 

static unsigned int nwrite(int fd, char *ptr, unsigned int nbytes)
{
 unsigned int nleft;
 int    chunk;
 int    nwritten;

 nleft = nbytes;
 while (nleft > 0) {

  if (nleft > 0x7000) chunk = 0x7000;
  else chunk = nleft;

  nwritten = write(fd, ptr, chunk);
  if (nwritten <= 0)
   return(nwritten);  /* error */

  nleft -= nwritten;
  ptr   += nwritten;
 }
 return(nbytes - nleft);
}
 

#define kMaxReplySize 512

static int GotReply(int fd, char expectedLeadingChar)
{
 int  err;
 char reply[kMaxReplySize];

 err = ReadReply(fd, reply, sizeof(reply));
 if (err != 0) {
  (*gErrorFunc)("Read error", NULL);
  return 0;
 }
 if (*reply != expectedLeadingChar) {
  (*gErrorFunc)("Protocol error", reply);
  return 0;
 }
 return 1;
}

// sends s1 followed by s2 followed by s3 followed by CRLF
static int Send(int fd, char *s1, char *s2, char *s3)
{

 if (s1 && nwrite(fd, s1, strlen(s1)) < 0)
  goto error;
 if (s2 && nwrite(fd, s2, strlen(s2)) < 0)
  goto error;
 if (s3 && nwrite(fd, s3, strlen(s3)) < 0)
  goto error;
 if (nwrite(fd, "\015\012", 2) < 0)
  goto error;
 return 1;

error:
 (*gErrorFunc)("Write error", NULL);
 return 0;
}

// sends aLine which is length chars long
static int SendSingleBodyLine(int fd, char *aLine, int length)
{
 if (*aLine == '.') // double-up on '.' lines
  if (nwrite(fd, ".", 1) < 0)
   goto error;
 if (nwrite(fd, aLine, length) < 0)
  goto error;
 if (nwrite(fd, "\015\012", 2) < 0)
  goto error;
 return 1;

error:
 (*gErrorFunc)("Write error", NULL);
 return 0;
}

static int SendBody(int fd, char *body)
{
 char *lineStart = body;
 int  result = 0;

 // send all the newline-terminated lines
 while (*body != '\0' && result == 0) {
  if (*body == '\n') {
   result = SendSingleBodyLine(fd, lineStart,
    body - lineStart);
   lineStart = body + 1;
  }
  body++;
 }
 // send the last partial line
 if (lineStart < body && result == 0)
  result = SendSingleBodyLine(fd, lineStart,
   body - lineStart);
 return result;
}

#define kOK   '2'
#define kWantMore '3'

int sendmail(char *smtpHost, char *from, char *to, char *subject,
 char *data, StatusCallbackFunc statusFunc,
 ErrorCallbackFunc errorFunc)
{
 int success = 0;
 int fd    = -1;  // socket file descriptor

 gErrorFunc = errorFunc;
 gStatusFunc = statusFunc;

 // open connection to the server
 if ((fd = make_connection("smtp", SOCK_STREAM, smtpHost)) < 0 )
 {
  (*errorFunc)("Couldn't open connection", NULL);
  goto _Exit;
 }

 // send & receive the data
 if (!GotReply(fd, kOK))
  goto _Exit;

 if (!Send(fd, "HELO [", "127.0.0.1", "]"))
  goto _Exit;
 if (!GotReply(fd, kOK))
  goto _Exit;

 if (!Send(fd, "MAIL from:<", from, ">"))
  goto _Exit;
 if (!GotReply(fd, kOK))
  goto _Exit;

 if (!Send(fd, "RCPT to:<", to, ">"))
  goto _Exit;
 if (!GotReply(fd, kOK))
  goto _Exit;

 if (!Send(fd, "DATA", NULL, NULL))
  goto _Exit;
 if (!GotReply(fd, kWantMore))
  goto _Exit;

 if (!Send(fd, "Subject: ", subject, NULL))
  goto _Exit;

// need empty line between headers and data
 if (!Send(fd, NULL, NULL, NULL))
  goto _Exit;

 if (!SendBody(fd,data))
  goto _Exit;
 if (!Send(fd, ".", NULL, NULL))
  goto _Exit;

 if (!GotReply(fd, kOK))
  goto _Exit;

 if (!Send(fd, "QUIT", NULL, NULL))
  goto _Exit;
 

 success = 1;
 // cleanup the mess...

_Exit:

 if ( fd >= 0 ) close( fd );

 return success;
}