/* This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 dated June, 1991.

   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., 675 Mass Ave., Cambridge, MA 02139, USA. */

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_OPENSSL_SSL_H 
#include <openssl/ssl.h>
#include <openssl/bio.h>
#endif
#ifdef __QNX__
#include <sys/select.h> /* for select() et. al. */
#endif

#include "config.h"
#include "socklib.h"

#define S_LIBRARY
#define MG_SOCKLIB_C

/* This source code was originally based on

    "The Linux A-Z" by Phil Cornes,
    chapter 18 - "Tiny Socket Library"
    ISBN 0-13-234709-1
    
   The original source has been modified by
   Martin Domig <martin.domig@gmx.net>			*/


SOCKET *Sopen(void) {
   SOCKET *sp;
   
   if ((sp = (SOCKET *)malloc(sizeof(SOCKET))) == NULL)
      return 0;
   
   /* Create a socket and store its descriptor in sp->sd. Declarations:
      AF_INET.......ARPA Internet protocol
      SOCK_STREAM...TCP-type connection
      0.............IP Protocol Type
      See socket(2) for details.											*/
   if ((sp->sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
      free(sp);	/* Oops, we got no socket! Free the mem and return error	*/
      return 0;
   }
   
   sp->sinlen = sizeof(sp->sin);
   sp->bindflag = SOCKET_RESET;

#ifdef HAVE_OPENSSL_SSL_H 
   /* ssl is initialized after the tcp connection is opened */
   sp->ssl = NULL;
   sp->sslBufSize = 0;
#endif
   
   return sp;
}

int Sclose(SOCKET *sp) {
   int sd;

   if (!sp)
      return -1;
   
   sd = sp->sd;
#ifdef HAVE_OPENSSL_SSL_H 
   if (sp->ssl) {
      SSL_shutdown(sp->ssl);
      SSL_free(sp->ssl);
   }

   if (sp->sslBufSize) {
      free(sp->sslBufMalloc);
      sp->sslBufSize = 0;
   }
#endif

   free(sp);
   return close(sd);
}

int Sserver(SOCKET *sp, int port, int sync) {
   int flags;
   struct hostent *hostent;
   /* struct hostent {
      	char	*h_name;        official name of host
        char    **h_aliases;    alias list
        int     h_addrtype;     host address type
        int     h_length;       length of address
        char    **h_addr_list;  list of addresses
      }																		*/
   char localhost[HOST_NAMELEN+1];

   /* We have to bind an address to the socket and then set up a queue
      to listen for connection requests. This should only be done the first
      time sserver() is called, and this is what sp->bindflag is used for.	*/   
   if (sp->bindflag == SOCKET_RESET) {	/* Bind server to localhost				*/
      if (gethostname(localhost, HOST_NAMELEN) == -1
         || (hostent = gethostbyname(localhost)) == 0)
         return -1;
   
      sp->sin.sin_family = (short)hostent->h_addrtype;
      sp->sin.sin_port = htons((unsigned short)port);
      sp->sin.sin_addr.s_addr = *(unsigned long *)hostent->h_addr;
   
      if (bind(sp->sd, (struct sockaddr *)&sp->sin, sp->sinlen) == -1
         || listen(sp->sd, 5) == -1)
         return -1;
   
      sp->bindflag = SOCKET_SET;
          					/* All done and OK. Never bind this one again.	*/
   }
   
   switch (sync) {
      case SOCKET_DELAY:
         if ((flags = fcntl(sp->sd, F_GETFL)) == -1
            || fcntl(sp->sd, F_SETFL, flags & ~O_NDELAY) == -1)
            return -1;
         break;
      case SOCKET_NDELAY:
         if ((flags = fcntl(sp->sd, F_GETFL)) == -1
            || fcntl(sp->sd, F_SETFL, flags | O_NDELAY) == -1)
            return -1;
         break;
      default:
         return -1;
   }
   
   return accept(sp->sd, (struct sockaddr *)&sp->sin, &sp->sinlen);
}

/* sclient tries to connect to a specified server on a given machine.
   The function waits until a connection with the server is established and
   then returns a socket descriptor connected to the server, -1 on error.	*/
int Sclient(SOCKET *sp, char *name, int port) {
   struct hostent *hostent;
   
   if ((hostent = gethostbyname(name)) == 0)
      return -1;
   
   sp->sin.sin_family = (short)hostent->h_addrtype;
   sp->sin.sin_port = htons((unsigned short)port);
   sp->sin.sin_addr.s_addr = *(unsigned long *)hostent->h_addr;
   
   if (connect(sp->sd, (struct sockaddr *)&sp->sin, sp->sinlen) == -1)
      return -1;
   
   return sp->sd;
}

#ifdef HAVE_OPENSSL_SSL_H 
/* modify an open socket to use SSL */
int Sslclient(SOCKET *sp, char *trustedCaDir)
{
   SSL_METHOD *meth;
   SSL_CTX *ctx;
   BIO *sbio;
   X509 *peerCert;
   
   /* assert(sp contains an open socket) */

   SSL_library_init();

   meth = TLSv1_method();
   ctx = SSL_CTX_new(meth);
   if (!ctx)
      return -1;

   sp->ssl = SSL_new(ctx);
   if (sp->ssl == NULL)
      return -1;

   /* don't check return value
    * in case of error, verification will fail later on */
   SSL_CTX_load_verify_locations(ctx, NULL, trustedCaDir);

   sbio = BIO_new_socket(sp->sd, BIO_NOCLOSE);
   if (!sbio) {
      return -1;
   }

   SSL_set_bio(sp->ssl, sbio, sbio); /* read, write bio */

   if (SSL_connect(sp->ssl) != 1) {
      return -1;
   }

   peerCert = SSL_get_peer_certificate(sp->ssl);
   if (!peerCert) {
      return -1;
   }

   X509_free(peerCert);

   if(SSL_get_verify_result(sp->ssl) != X509_V_OK) {
      printf("asmail warning: could not verify server certificate\n");
   }

   return sp->sd;
}
#endif


/* Read a maximum of n-1 characters from sd until a newline or
   a '\0' is received. string stores the input WITHOUT a ter-
   minating newline. Returns number of read characters, without
   terminating \0.
   HINT: pass the SIZE of the array as parameter for n.		 */
   
size_t Sread(int sd, char *string, int n, int timeout)
{
   fd_set rfds;
   struct timeval tv;
   int ret, rcd, i;
   
/* See man 3 select for details ;)

NOTES
       Some  code calls select with all three sets empty, n zero,
       and a non-null timeout as a fairly portable way  to  sleep
       with subsecond precision.

       On  Linux,  timeout  is  modified to reflect the amount of
       time not slept; most other implementations do not do this.
       This  causes  problems  both  when  Linux code which reads
       timeout is ported to other  operating  systems,  and  when
       code  is  ported to Linux that reuses a struct timeval for
       multiple selects in  a  loop  without  reinitializing  it.
       Consider timeout to be undefined after select returns. */

   FD_ZERO(&rfds);
   FD_SET(sd, &rfds);
   i = 0;
   n--;

   do {
      tv.tv_sec = timeout;
      tv.tv_usec = 0;
      
      ret = select(sd+1, &rfds, NULL, NULL, &tv);
      if (ret > 0 && (!(rcd = read(sd, &string[i++], 1)))) {
            string[i] = '\0';
            return 0;
      }
   } while (ret > 0 && rcd > 0 && string[i-1] != 13 && string[i-1] && i < n);
   
   if (ret < 0 || rcd < 0) {
      string[--i] = '\0';
      return -1;
   }

   if (string[i-1] == 13)
      read(sd, &string[--i], 1); /* read 10 from sd */
      
   string[i] = '\0';
   return i;
}

size_t Swrite(int sd, char *string)
{
   return write(sd, string, strlen(string));
}


#ifdef HAVE_OPENSSL_SSL_H 
/* behaves exactly like Sread() above */
size_t Sslread(SOCKET *s, char *string, int n, int timeout)
{
   size_t sizeRead;
   fd_set rfds;
   struct timeval tv;
   int ret;

   if (s->ssl == NULL)
      return -1;

   if (!s->sslBufSize) {
      /* empty buffer: read from SSL */

      FD_ZERO(&rfds);
      FD_SET(s->sd, &rfds);

      tv.tv_sec = timeout;
      tv.tv_usec = 0;

      ret = select((s->sd)+1, &rfds, NULL, NULL, &tv);
      if (ret > 0) {
         s->sslBufMalloc = malloc(BUFSIZ * sizeof(char));
         s->sslBuf = s->sslBufMalloc;
         if (!s->sslBuf)
            return -1;
         s->sslBufSize = SSL_read(s->ssl, s->sslBuf, BUFSIZ);
         if (s->sslBufSize <= 0) {
            free(s->sslBufMalloc);
            return -1;
         }
      }
      else
         return 0;
   }

   /* read from buffer */
   sizeRead = 0;
   while (s->sslBufSize-- && (sizeRead++ < n-1)) {
      if ( ((*string++ = *(s->sslBuf)++) == 0x0d) &&
            (*(s->sslBuf)++ == 0x0a) ) {
         s->sslBufSize--;     /* for the 0x0a we just read */
         string--;   /* undo 0x0d in target string */
         sizeRead--;
         break;
      }
   }

   if (!s->sslBufSize)
      free(s->sslBufMalloc);

   *string = 0x0;
   return sizeRead;
}

size_t Sslwrite(SOCKET *s, char *string)
{
   if (s->ssl == NULL)
      return -1;

   return SSL_write(s->ssl, string, strlen(string));
}
#endif

