#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define K	1024
#define PORT	47350

#ifndef SYSTEM_SSL_CERT_DIR
  #define SYSTEM_SSL_CERT_DIR   "./cert"
//#define SYSTEM_SSL_CERT_DIR   SYSTEM_CONFIG_DIR "/ssl/certs/"
#endif

// Command to make a certificate (self-signed):
// openssl req -new -x509 -nodes -out server.pem -keyout server.pem -days 370

#define SERVER_CERT     SYSTEM_SSL_CERT_DIR "/server.pem"
#define SERVER_KEY      SYSTEM_SSL_CERT_DIR "/server.pem"
#define CA_CERT         SYSTEM_SSL_CERT_DIR "/server.pem"

typedef struct ssl_conn {
  int sock;
  SSL *handle;
} ssl_conn;

SSL_CTX *setup_ssl_context(void)
{
  SSL_CTX *context = NULL;

  /* Create a SSL context */
  if ((context = SSL_CTX_new(SSLv23_method())) == NULL) {
    ERR_print_errors_fp(stderr);
  }
  SSL_CTX_set_options(context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);

  /* Load the certificate (public key) into the SSL context: */
  if (SSL_CTX_use_certificate_file(context, SERVER_CERT, SSL_FILETYPE_PEM) <= 0) {
    ERR_print_errors_fp(stderr);
    exit(1);
  }
  /* Load the server private-key into the SSL context: */
  if (SSL_CTX_use_PrivateKey_file(context, SERVER_KEY, SSL_FILETYPE_PEM) <= 0) {
    ERR_print_errors_fp(stderr);
    exit(1);
  }
  /* Check that the public / private keys go together */
  if (!SSL_CTX_check_private_key(context)) {
    fprintf(stderr,"Private key does not match public key\n");
    exit(1);
  }
  /* Load trusted CA. (NULL for a self signed cert) */
  if (!SSL_CTX_load_verify_locations(context,CA_CERT,NULL)) { 
    ERR_print_errors_fp(stderr);
    exit(1);
  }
  /* Set to require peer (client) certificate verification */
  SSL_CTX_set_verify(context, SSL_VERIFY_NONE, NULL); // verify_callback);
  /* Set the verification depth to 1 */
  SSL_CTX_set_verify_depth(context,1);

  return context;
}

ssl_conn *ssl_accept(int sock, SSL_CTX *context)
{
  struct ssl_conn *c;
  int conn;
  
  if ((conn = accept(sock, NULL, NULL)) < 0) {
    perror("accept");
    return NULL;
  }

  c = malloc(sizeof(struct ssl_conn));
  c->sock = conn;
  if ((c->handle = SSL_new(context)) == NULL) {
    ERR_print_errors_fp(stderr);
    return NULL;
  }
  if (!SSL_set_fd(c->handle, conn)) {
    ERR_print_errors_fp(stderr);
    return NULL;
  }
  if (SSL_accept(c->handle) != 1) {
    ERR_print_errors_fp(stderr);
  }
  return c;
}

void ssl_disconnect(ssl_conn *c)
{
  if (c->handle) {
    SSL_shutdown(c->handle);
    SSL_free(c->handle);
  }
  if (c->sock) close(c->sock);
  free(c);  
}

int main(int argc, char *argv[])
{
  int sock, conn, r = 1;
  struct sockaddr_in server;
  SSL_CTX *context = NULL;
  ssl_conn *c;
  char buf[K];

  SSL_load_error_strings();
  SSL_library_init();

  context = setup_ssl_context();

  if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    exit(1);
  }
  memset(&server, 0, sizeof(server));
  server.sin_family = AF_INET;
  server.sin_port = htons(PORT);
  server.sin_addr.s_addr = htonl(INADDR_ANY);

  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &r, sizeof(r));

  if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
    perror("bind()");
    exit(1);
  }
  if (listen(sock, 20) < 0) {
    perror("listen()");
    exit(1);
  }

  while(1) {
    c = ssl_accept(sock, context);
    if (c == NULL) {
      fprintf(stderr,"Error accepting connection.\n");
      continue;
    }
    r = SSL_read(c->handle, buf, K);
    printf("%.*s\n", r, buf);
    SSL_write(c->handle, "OK", 3);
    ssl_disconnect(c);
  }
}
