/*
 * sslServer.cpp
 *
 *  Created on: Feb 9, 2012
 *      Author: jirka
 */

#include <openssl/bio.h> // BIO objects for I/O
#include <openssl/ssl.h> // SSL and SSL_CTX for SSL connections
#include <openssl/err.h> // Error reporting
#include <openssl/rand.h>
#include <sys/socket.h>
#include <unistd.h>
#include <malloc.h>
#include <resolv.h>
#include <string>
#include <iostream>
#include <signal.h>
#include "sslServer.h"
#include <sstream>

static string password;

/*
 * Destruktor tridy
 */
sslServer::~sslServer()
{
  pthread_kill(reader,SIGKILL); //Ukonceni vlakna cteni ze SSL

  /* Uvolneni alokovanych zdroju */
  if (bio!=0)
    BIO_free_all(bio);
  if (ctx!=0)
    SSL_CTX_free(ctx);

}

/*
 * Konstruktor instance tridy
 *
 * Vstupy:
 *      port:           na kterem naslouchame
 *      confPath:       cesta k certifikatum
 *      pass:           heslo pro odemknuti certifikatu
 *
 */
sslServer::sslServer(int port, char* confPath, char* pass)
{

  /* Inicializace tridy */
  if (!this->Init(port,confPath,pass))
    {
      perror("SSL initialization failed.");
      abort();
    }

}


/*
bool sslServer::OpenListener(int port)
{   int sd;
    struct sockaddr_in addr;

    sd = socket(PF_INET, SOCK_STREAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    if ( bind(sd,(sockaddr*) &addr, sizeof(addr)) != 0 )
    {
        perror("can't bind port");
        return false;
    }
    if ( listen(sd, 10) != 0 )
    {
        perror("Can't configure listening port");
        return false;
    }
    server = sd;
    return true;
}*/

/*
 * Inicializacni funkce tridy
 *
 * Vstupy:
 *      port:           na kterem naslouchame
 *      confPath:       cesta k certifikatum
 *      pass:           heslo pro odemknuti certifikatu
 *
 */
bool sslServer::Init(int port, char* confPath, char* pass)
{
   bio = 0;
   CRYPTO_malloc_init();                                // Inicializace funkci OpenSSL
   SSL_library_init();                                  // Inicializace OpenSSL
   SSL_load_error_strings();                            // Inicializace chybovych hlasek
   ERR_load_BIO_strings();                              // Inicializace BIO chybovych hlasek
   OpenSSL_add_all_algorithms();                        // Nacteni algoritmu OpenSSL

   pathCert = confPath;
   pathCert.append(CERTIFICATE_NAME);                   //Sestaveni cesty k certifikatu

   pathKey = confPath;
   pathKey.append(CERTIFICATE_KEY);                     //Sestaveni cesty k privatnimu klici

   pathCA = confPath;
   pathCA.append(CERTIFICATE_CA);                       //Sestaveni cesty k certifikacni autorite

   pathDHParam = confPath;
   pathDHParam.append(PARAMS_DH);                       //Sestaveni cesty k DH souboru

   password = pass;                                     //Heslo...

   this->port = port;                                   //Sestaveni SSLv3 kontextu
   ctx = SSL_CTX_new(SSLv3_method());

   if (ctx == 0)                                        //V pripade keuspechu, konec
     {
       perror("Could not create SSL context.");
       return false;
     }

   /* Callback pro overeni hesla */
   SSL_CTX_set_default_passwd_cb(ctx, password_callback);

   /* Nastaveni cest ke klicovym souborum */
   SSL_CTX_use_certificate_file(ctx, pathCert.c_str(), SSL_FILETYPE_PEM);
   SSL_CTX_use_PrivateKey_file(ctx, pathKey.c_str(), SSL_FILETYPE_PEM);
   SSL_CTX_load_verify_locations(ctx, pathCA.c_str(), 0);

   /* Nastaveni hloubky provereni */
   SSL_CTX_set_verify_depth(ctx, 1);

   /* Nastaveni proverovaci callbacku */
   SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback);

   /* Nacteni sifrovaciho pseudoklice */
   RAND_load_file("random.pem", 1024 << 10);


   BIO* bio = BIO_new_file(pathDHParam.c_str(), "r");
   if (bio == NULL)
     {
       perror("Couldn't open DH param file!\n");
       SSL_CTX_free(ctx);
       ctx = 0;
       return false;
     }


   DH* ret = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
   BIO_free(bio);
   if (SSL_CTX_set_tmp_dh(ctx, ret) < 0)
     {
       SSL_CTX_free(ctx);
       ctx = 0;
       perror("Couldn't set DH parameters!\n");
       return false;
     }

   /* Generovani RSA klice */
   RSA* rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL);

   /* Nastaveni docasneho RSA klice */
   if (!SSL_CTX_set_tmp_rsa(ctx, rsa))
     {
       printf("Warning: Couldn't set RSA key!\n");

     }

   /* Uvolneni alokovanych prostredku */
   RSA_free(rsa);

   /* Inicializace sifrovaciho kontextu */
   SSL_CTX_set_cipher_list(ctx, "ALL");

   /* Inicializace zamku */
   pthread_mutex_init(&readerLock,NULL);
   pthread_mutex_init(&connectionLock,NULL);

   /* Spusteni cteciho vlakna */
   pthread_create(&reader,NULL,&sslReader,this);
   return true;
}

/*
 * Overeni hesla k certifikatu - callback
 */
int password_callback(char* buffer, int num, int rwflag, void* userdata)
{
    if (num < (strlen(password.data()) + 1)) {
  return(0);
    }
    strcpy(buffer, password.data());
    return strlen(password.data());
}


/*
 * Callback pro overeni sifrovaneho spojeni
 */

int verify_callback(int ok, X509_STORE_CTX* store)
{
    char data[255];
    /* Pokud se nepodarilo overit spojeni */
    if (!ok)
      {
        /* Nacist informace o chybe */
        X509* cert = X509_STORE_CTX_get_current_cert(store);
        int depth = X509_STORE_CTX_get_error_depth(store);
        int err = X509_STORE_CTX_get_error(store);

        /* Vytisknuti informaci o chybe */
        printf("Chyba certifikatu na urovni: %d!\n", depth);
        X509_NAME_oneline(X509_get_issuer_name(cert), data, 255);
        printf("\tVydavatel: %s\n", data);
        X509_NAME_oneline(X509_get_subject_name(cert), data, 255);
        printf("\tPredmet: %s\n", data);
        printf("\tChyba %d: %s\n", err, X509_verify_cert_error_string(err));
      }

    return ok;
}

/*
 * Metoda vraci ukazatel na zamek zabezpecujici spojeni
 */
pthread_mutex_t *sslServer::GetConnectionLock(void)
{
  return &connectionLock;
}

/*
 * Metoda vraci ukazatel na podminenou promennou zabezpecujici spojeni
 */
pthread_cond_t* sslServer::GetConnectionCond(void)
{
  return &connectionCond;
}

/*
 * Metoda vraci ukazatel na zamek zabezpecujici frontu pro data
 */
pthread_mutex_t *sslServer::GetReaderLock(void)
{
  return &readerLock;
}

/*
 * Metoda vraci ukazatel na podminenou promennou zabezpecujici cteci promennou
 */
pthread_cond_t* sslServer::GetReaderCond(void)
{

  return &this->dataRdyCond;
}

/*
 * Metoda vraci informace o poctu prijatych bajtu
 */

int sslServer::AvailableData(void)
{
  return this->receivedData.length();
}

/*
 * Metoda vraci data prijata z SSL
 */
string sslServer::ReadData(void)
{
  /* Uyamknuti zamku na */
  pthread_mutex_lock(&readerLock);

  /* Kopie prijatych dat */
  string tmp = receivedData;

  /* Vymazani bufferu */
  receivedData.clear();

  /* Odemknuti zamku na datech */
  pthread_mutex_unlock(&readerLock);
  //cout << tmp;
  return tmp;
}



/*
 * Metoda vraci informace o aktualnim stavu spojeni
 */
bool sslServer::CheckConnectionState(void)
{
  return isConnected;
}

/*
 * Metoda odesle pomoci SSL spojeni data
 *
 * Vstup:
 *      data:           data k odeslani
 */
bool sslServer::WriteData(string data)
{
  /* Pokud lze data odeslat */
  if (bio != 0 && ctx !=0)
    {
      /* Pokus se je odeslat */
      return (BIO_write(bio,data.c_str(),data.length()) > 0);

    }
  return false;
}

/*
 * Funkce je zakladem cteciho vlakna
 */
void * sslReader(void* input)
{
  char buff[BUFF_LENGTH];                                               // Buffer pro prijata data
  sslServer* srv = (sslServer*) input;                                  // Pretypovani vstupu vlakna na instanci tridy
  BIO *bbio, *acpt;                                                     //Docasne promenne
  SSL* ssl;
  while(1)
    {

      //cout << "Listenner started.\r";
      /* Uzamceni zamku na spojeni */
      pthread_mutex_lock(&srv->connectionLock);

      /* Spojeni je aktualne odpojeno */
      srv->isConnected = false;

      /* Odeslani signalu o odpojeni */
      pthread_cond_signal(&srv->connectionCond);
      pthread_mutex_unlock(&srv->connectionLock);

      /* Vytvoreni kontextu spojeni */
      srv->bio=BIO_new_ssl(srv->ctx,0);

      /* Ziskani struktury ssl z handleru spojeni */
      BIO_get_ssl(srv->bio, &ssl);

      /* Pokud nebyl ziskan handler SSL */
      if(!ssl) {
        perror("Can't locate SSL pointer\n");

      }

      /* Nastaveni parametru spojeni */
      SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);


      srv->bio = BIO_push(bbio, srv->bio);

      /* Vytvoreni socketu pro naslouchani */
      std::stringstream port;
      port << (srv->port);
      acpt=BIO_new_accept((char*)port.str().c_str());

      /* Propojeni socketu se strukturou BIO */
      BIO_set_accept_bios(acpt,srv->bio);


       /* Aktivace socketu pro naslouchani */
       if(BIO_do_accept(acpt) <= 0)
         {
             /* Pokud se nezadarilo, uvolneni alokonanych struktur */
              BIO_free_all(srv->bio);
              BIO_free(acpt);
              srv->bio = 0;
              fprintf(stderr, "Error setting up accept BIO\n");
              ERR_print_errors_fp(stderr);

              /* Cekani (Ochranna doba) */
              sleep(10);
              continue;
         }

       /* Cekani pro prichozi spojeni */
       if(BIO_do_accept(acpt) <= 0)
         {
             /* Opet pri neuspechu koncime a opakujeme */
             BIO_free_all(srv->bio);
             BIO_free(acpt);
             srv->bio = 0;
             fprintf(stderr, "Error in connection\n");
             ERR_print_errors_fp(stderr);
             sleep(10);
             continue;
         }


       /* Spojeni je ziskano, tudiz nepotrebujeme poslouchat */
       srv->bio = BIO_pop(acpt);

       /* Uvolnime strukturu */
       BIO_free(acpt);

       /* Provedeme handshake spojeni - overeni certifikatu*/
       if(BIO_do_handshake(srv->bio) <= 0) {
             BIO_free_all(srv->bio);
             srv->bio = 0;
             fprintf(stderr, "Error in SSL handshake\n");
             ERR_print_errors_fp(stderr);
             sleep(10);
             continue;
        }

       /* Odeslani signalu o aktivaci spojeni */
      pthread_mutex_lock(&srv->connectionLock);
      srv->isConnected = true;
      pthread_cond_signal(&srv->connectionCond);
      pthread_mutex_unlock(&srv->connectionLock);

      while(1)
        {
          /* Cekani na prijeti dat */
          int cnt = BIO_read(srv->bio,buff,BUFF_LENGTH);

          /* Pokud nastala chyba, dostaneme nulu a mene */
          if (cnt <= 0)
            {
              sleep(3);
              //BIO_free(srv->bio);
              srv->bio = 0;

              break;
            }
          else
            {

              /* Zamknout zamek */
              pthread_mutex_lock(&srv->readerLock);

              /* Prijata data ulozime do zasobniku */
              srv->receivedData.append(buff);

              /* Odesleme signal o datech */
              pthread_cond_signal(&srv->dataRdyCond);
              pthread_mutex_unlock(&srv->readerLock);
              /* Vynulovani zasobniku */
              bzero(buff,BUFF_LENGTH);
            }

        }

     }
  return 0;
}


