#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>
#include <sys/epoll.h>

#define K	1024
#define PORT	47350

#define MSG	"Hello, World!"

// SSL = Secure Sockets Layer (deprecated)
// TLS = Transport Layer Security

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

  // Create a SSL context, used as the framework to establish SSL/TLS
  // connections:
  if ((context = SSL_CTX_new(SSLv23_method())) == NULL) {
    ERR_print_errors_fp(stderr);
  }
  // Remove old SSL and TLSv1 protocols from consideration:
  SSL_CTX_set_options(context,
	SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);

  return context;
}

void ssl_disconnect(int sock, SSL *handle)
{
  SSL_shutdown(handle);
  SSL_free(handle);
  close(sock);
}

// Compile with:
// gcc -o ssl-c ssl-c.c -lcrypto -lssl

int main(int argc, char *argv[])
{
  int sock, conn, epfd, r, n, i, len;
  struct epoll_event epv[2];
  struct hostent *he;
  struct sockaddr_in server;
  SSL_CTX *context = NULL;
  SSL *handle;
  char buf[K];

  // Registers all the error strings used by libcrypto and libssl:
  SSL_load_error_strings();
  // Registers the available SSL/TLS ciphers and digests:
  SSL_library_init();

  context = setup_ssl_context();

  if (argc < 3) {
    fprintf(stderr,"Usage: ssl-c <host> <port>\n");
    exit(1);
  }

  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(atoi(argv[2]));
  if ((he = gethostbyname(argv[1])) == NULL) {
    fprintf(stderr,"Host not found [%s]\n", argv[1]);
    exit(0);
  }
  memcpy(&server.sin_addr.s_addr, he->h_addr, he->h_length);

  if (connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
    perror("connect");
    exit(1);
  }
  // Create an SSL struct/object for the connection:
  if ((handle = SSL_new (context)) == NULL) {
    ERR_print_errors_fp(stderr);
  }
  // Connect the SSL object to our sockets file descriptor:
  if (!SSL_set_fd(handle, sock)) {
    ERR_print_errors_fp(stderr);
  }
  // Initiate the SSL/TLS handshake with the server:
  if (SSL_connect(handle) != 1) {
    ERR_print_errors_fp(stderr);
  }

  epfd = epoll_create(5);
  epv[0].events = EPOLLIN;
  epv[0].data.fd = 0;
  epv[1].events = EPOLLIN;
  epv[1].data.fd = sock;

  epoll_ctl(epfd,EPOLL_CTL_ADD, 0, &(epv[0]));
  epoll_ctl(epfd,EPOLL_CTL_ADD, sock, &(epv[1]));

  while(1) {
    n = epoll_wait(epfd, epv, 2, -1);
    if (n <= 0) continue;

    for(i=0; i < n; i++) 
      if (epv[i].events & (EPOLLHUP | EPOLLERR)) goto done;

    for(i=0; i < n; i++) {
      if (epv[i].events & EPOLLIN) {
	if (epv[i].data.fd == 0) {
	  if (fgets(buf, K, stdin) == NULL) goto done;
	  len = strlen(buf)-1;
	  buf[len] = '\r';
	  buf[len+1] = '\n';
	  buf[len+2] = 0;
	  if (SSL_write(handle, buf, strlen(buf)) < 0) goto done;
	} else if (epv[i].data.fd == sock) {
	  r = SSL_read(handle, buf, K);
	  if (r <= 0) goto done;
	  fprintf(stderr,"%.*s", r, buf);
	}
      }
    }
  }

done:
  ssl_disconnect(sock, handle);
}
