/*
 *   $Id: 
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

/* Make these what you want for cert & key files */
#define CERTF  "server.cert.pem"
#define KEYF  "server.key.pem"
  
#include "config.h"
#ifdef HAVE_SSL

#ifdef HAVE_GNUTLS
#include "gnutls/gnutls.h"
#include "gnutls/openssl.h"
#endif

#include "ssl.h"

#include "common.h"
#include "ircd.h"
#include "client.h"
#include "s_log.h"
#include "s_bsd.h"

#define SAFE_SSL_READ 1
#define SAFE_SSL_WRITE 2
#define SAFE_SSL_ACCEPT 3
#define SAFE_SSL_CONNECT 4

/* The SSL structures */
SSL_CTX* ctx;
SSL_METHOD* meth;
static int fatal_ssl_error(int ssl_error, int where, struct Client *sptr);

void init_ssl() {
    /* SSL preliminaries. We keep the certificate and key with the context. */

  SSL_load_error_strings();
  SSLeay_add_ssl_algorithms();
  meth = SSLv23_server_method();
  ctx = SSL_CTX_new (meth);

       if (!ctx)
         {
               irclog(L_ERROR, "Error creating SSL CTX");                        
#if 0         
               ERR_print_errors_fp (stderr);
#endif               
               exit (2);               
         }

       if (SSL_CTX_use_certificate_file (ctx, CERTF, SSL_FILETYPE_PEM) <= 0)
         {
            irclog(L_ERROR, "Failed to load SSL certificate %s, SSL disabled", CERTF);
            no_ssl = -1;
            return;
         }
       if (SSL_CTX_use_PrivateKey_file (ctx, KEYF, SSL_FILETYPE_PEM) <= 0)
         {
            irclog(L_ERROR, "Failed to load SSL private key %s, SSL disabled", KEYF);
            no_ssl = -1;            
            return;
         }

#if 0
       if (!SSL_CTX_check_private_key (ctx))
         {
               fprintf (stderr, "Private key does not match the certificate public key\n");
               exit (5);
         }
#endif
}

#define CHK_NULL(x) if ((x)==NULL) {\
        irclog(L_NOTICE, "Lost connection to %s:Error in SSL", \
                     get_client_name(cptr, TRUE)); \
	return 0;\
	}

int ssl_handshake(struct Client *cptr) {

  char *str;
  int err;

      cptr->ssl = (struct SSL*) SSL_new (ctx);
//      cptr->use_ssl=1;


      CHK_NULL (cptr->ssl);
      SSL_set_fd ((SSL *)cptr->ssl, cptr->fd);
      set_non_blocking(cptr->fd);
      err = ircd_SSL_accept (cptr, cptr->fd);
      if ((err)==-1) {
        irclog(L_ERROR,"Lost connection to %s:Error in SSL_accept()", 
                     get_client_name(cptr, TRUE));
	SSL_shutdown((SSL *)cptr->ssl);
	SSL_free((SSL *)cptr->ssl);
	cptr->ssl = NULL;
        return 0;
      }

      /* Get the cipher - opt */
      SetSecure(cptr);
      
      irclog (L_DEBUG, "SSL connection using %s", SSL_get_cipher ((SSL *)cptr->ssl));

      /* Get client's certificate (note: beware of dynamic
       * allocation) - opt */

      cptr->client_cert = (struct X509*)SSL_get_peer_certificate ((SSL *)cptr->ssl);

      if (cptr->client_cert != NULL)
      {
        irclog (L_DEBUG,"Client certificate:");

        str = X509_NAME_oneline (X509_get_subject_name ((X509*)cptr->client_cert), 0, 0);
        CHK_NULL (str);
        irclog (L_DEBUG, "\t subject: %s", str);
        // Bejvavalo
 	//       Free (str);
	free(str);
	
        str = X509_NAME_oneline (X509_get_issuer_name ((X509*)cptr->client_cert), 0, 0);
        CHK_NULL (str);
        irclog (L_DEBUG, "\t issuer: %s", str);
        // Bejvavalo
        // Free (str);
	free(str);
	
        /* We could do all sorts of certificate
         * verification stuff here before
         *        deallocating the certificate. */

        X509_free ((X509*)cptr->client_cert);
      }
      else
        irclog (L_DEBUG, "Client does not have certificate.");

      return 1;

}

int ssl_client_handshake(struct Client *cptr) {

  char *str;
  int err;

  cptr->ssl = (struct SSL*)SSL_new (ctx);                         CHK_NULL(cptr->ssl);    
  SSL_set_fd ((SSL*)cptr->ssl, cptr->fd);
  set_blocking(cptr->fd);
  err = SSL_connect ((SSL*)cptr->ssl);                     
  set_non_blocking(cptr->fd);
  if ((err)==-1) {
     irclog(L_NOTICE,"Could connect to %s:Error in SSL_connect()", 
                  get_client_name(cptr, TRUE));
     return 0;
     }
    
  /* Following two steps are optional and not required for
     data exchange to be successful. */
  
  /* Get the cipher - opt */
  set_blocking(cptr->fd);
  irclog (L_NOTICE,"SSL connection using %s", SSL_get_cipher ((SSL*)cptr->ssl));
  
  
  SetSecure(cptr);      
  /* Get server's certificate (note: beware of dynamic allocation) - opt */

  cptr->client_cert = (struct X509*)SSL_get_peer_certificate ((SSL *)cptr->ssl);
  set_non_blocking(cptr->fd);

  if (cptr->client_cert != NULL)
    {
      irclog (L_NOTICE,"Server certificate:");
  
      str = X509_NAME_oneline (X509_get_subject_name ((X509*)cptr->client_cert),0,0);
      CHK_NULL(str);
      irclog (L_NOTICE, "\t subject: %s", str);
      // Free (str);
      free (str);
  
      str = X509_NAME_oneline (X509_get_issuer_name  ((X509*)cptr->client_cert),0,0);
      CHK_NULL(str);
      irclog (L_NOTICE, "\t issuer: %s", str);
      // Free (str);
      free (str);
      /* We could do all sorts of certificate verification stuff here before
      deallocating the certificate. */

      X509_free ((X509*)cptr->client_cert);

    }
    else
      irclog (L_NOTICE, "Server does not have certificate.");
      
  
  return 1;
}

int ircd_SSL_read(struct Client *acptr, void *buf, int sz)
{
    int len, ssl_err;
    len = SSL_read((SSL *)acptr->ssl, buf, sz);
    if (len <= 0)
    {
       switch(ssl_err = SSL_get_error((SSL *)acptr->ssl, len)) {
           case SSL_ERROR_SYSCALL:
               if (ERRNO == EWOULDBLOCK || ERRNO == EAGAIN ||
                       ERRNO == EINTR) {
           case SSL_ERROR_WANT_READ:
                   SET_ERRNO(EWOULDBLOCK);
		   Debug((DEBUG_ERROR, "ircd_SSL_read: returning EWOULDBLOCK and 0 for %s - %s", acptr->name,
			ssl_err == SSL_ERROR_WANT_READ ? "SSL_ERROR_WANT_READ" : "SSL_ERROR_SYSCALL"		   
		   ));
                   return -1;
               }
           case SSL_ERROR_SSL:
               if(ERRNO == EAGAIN)
                   return -1;
           default:
           	Debug((DEBUG_ERROR, "ircd_SSL_read: returning fatal_ssl_error for %s",
           	 acptr->name));
		return  -1;
       }
    }
    Debug((DEBUG_ERROR, "ircd_SSL_read for %s (%p, %i): success", acptr->name, buf, sz));
    return len;
}
int ircd_SSL_write(struct Client *acptr, const void *buf, int sz)
{
    int len, ssl_err;

    len = SSL_write((SSL *)acptr->ssl, buf, sz);
    if (len <= 0)
    {
       switch(ssl_err = SSL_get_error((SSL *)acptr->ssl, len)) {
           case SSL_ERROR_SYSCALL:
               if (ERRNO == EWOULDBLOCK || ERRNO == EAGAIN ||
                       ERRNO == EINTR)
		{
			SET_ERRNO(EWOULDBLOCK);
			return -1;
		}
		return -1;
          case SSL_ERROR_WANT_WRITE:
                   SET_ERRNO(EWOULDBLOCK);
                   return -1;
           case SSL_ERROR_SSL:
               if(ERRNO == EAGAIN)
                   return -1;
           default:
		Debug((DEBUG_ERROR, "ircd_SSL_write: returning fatal_ssl_error for %s", acptr->name));
		return -1;
       }
    }
    Debug((DEBUG_ERROR, "ircd_SSL_write for %s (%p, %i): success", acptr->name, buf, sz));
    return len;
}

int ircd_SSL_accept(struct Client *acptr, int fd) {

    int ssl_err;

    if((ssl_err = SSL_accept((SSL *)acptr->ssl)) <= 0) {
	switch(ssl_err = SSL_get_error((SSL *)acptr->ssl, ssl_err)) {
	    case SSL_ERROR_SYSCALL:
		if (ERRNO == EINTR || ERRNO == EWOULDBLOCK
			|| ERRNO == EAGAIN)
	    case SSL_ERROR_WANT_READ:
	    case SSL_ERROR_WANT_WRITE:
		    Debug((DEBUG_DEBUG, "ircd_SSL_accept(%s), - %s", get_client_name(acptr, TRUE), ssl_error_str(ssl_err)));
		    /* handshake will be completed later . . */
		    return 1;
	    default:
		return fatal_ssl_error(ssl_err, SAFE_SSL_ACCEPT, acptr);
		
	}
	/* NOTREACHED */
	return -1;
    }
    return 1;
}

int ircd_SSL_shutdown(struct Client *acptr)
  {
    SSL_shutdown((SSL *) acptr->ssl);
    SSL_free((SSL *)acptr->ssl);
    acptr->ssl = NULL;
    return 0;
  }

static int fatal_ssl_error(int ssl_error, int where, struct Client *sptr)
{
    /* don`t alter ERRNO */
    int errtmp = ERRNO;
    char *ssl_errstr, *ssl_func;

    switch(where) {
	case SAFE_SSL_READ:
	    ssl_func = "SSL_read()";
	    break;
	case SAFE_SSL_WRITE:
	    ssl_func = "SSL_write()";
	    break;
	case SAFE_SSL_ACCEPT:
	    ssl_func = "SSL_accept()";
	    break;
	case SAFE_SSL_CONNECT:
	    ssl_func = "SSL_connect()";
	    break;
	default:
	    ssl_func = "undefined SSL func";
    }

    switch(ssl_error) {
    	case SSL_ERROR_NONE:
	    ssl_errstr = "SSL: No error";
	    break;
	case SSL_ERROR_SSL:
	    ssl_errstr = "Internal OpenSSL error or protocol error";
	    break;
	case SSL_ERROR_WANT_READ:
	    ssl_errstr = "OpenSSL functions requested a read()";
	    break;
	case SSL_ERROR_WANT_WRITE:
	    ssl_errstr = "OpenSSL functions requested a write()";
	    break;
#if 0	    
	case SSL_ERROR_WANT_X509_LOOKUP:
	    ssl_errstr = "OpenSSL requested a X509 lookup which didn`t arrive";
	    break;
#endif	    
	case SSL_ERROR_SYSCALL:
	    ssl_errstr = "Underlying syscall error";
	    break;
	case SSL_ERROR_ZERO_RETURN:
	    ssl_errstr = "Underlying socket operation returned zero";
	    break;
#if 0	    
	case SSL_ERROR_WANT_CONNECT:
	    ssl_errstr = "OpenSSL functions wanted a connect()";
	    break;
#endif	    
	default:
	    ssl_errstr = "Unknown OpenSSL error (huh?)";
    }
    /* if we reply() something here, we might just trigger another
     * fatal_ssl_error() call and loop until a stack overflow... 
     * the client won`t get the ERROR : ... string, but this is
     * the only way to do it.
     * IRC protocol wasn`t SSL enabled .. --vejeta
     */
	sptr->flags |= FLAGS_DEADSOCKET;
	if (errtmp)
	{
		SET_ERRNO(errtmp);
		/* sptr->error_str = strdup(strerror(errtmp)); */
	} else {
		SET_ERRNO(EIO);
		/* sptr->error_str = strdup(ssl_errstr); */
	}
    return -1;
}


#endif


syntax highlighted by Code2HTML, v. 0.9.1