/*
	Linux Socket SSL Version Added 2013. T.Inaba

# WINDOWS
azpc -p socket_ssl.c /i /lib ssl /lib crypto /e azexe /dcurses /lib ws2_32.lib 
# LINUX
azpc -p socket_ssl.c /i /lib ssl /lib crypto /e azexe /dcurses 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
SSL Socket send/receive sample program

%%% socket_ssl_download %%%
?-ssl_client_connect('127.0.0.1','443','',S),
  G1='GET /data.zip',
  atom_concat(G1,' HTTP/1.0',G2),
  name(C,[10]),
  atom_concat(G2,C,G3),
  atom_concat(G3,C,Get),
  ssl_cert_send(S,Get),
  socket_ssl_download(S,'data.zip'),
  ssl_socket_close(S).
*/

#include <azprolog.h>
#ifdef WIN32
	#include <sys/stat.h>
	#include <stdio.h>
	#include <winsock2.h>
	#include <ws2tcpip.h>
	#include <fcntl.h>

	#include <openssl/rand.h>
	#include <openssl/ssl.h>
	#include <openssl/err.h>
	#include <openssl/crypto.h>
	#include <openssl/x509.h>
	#include <openssl/pem.h>
#else
	#include <stdio.h>
	#include <sys/types.h>
	#include <sys/socket.h>
	#include <sys/time.h>
	#include <sys/stat.h>
	#include <sys/ioctl.h>
	#include <netinet/in.h>
	#include <arpa/inet.h>
	#include <unistd.h>
	#include <string.h>
	#include <stdlib.h>
	#include <unistd.h>
	#include <fcntl.h>
	#include <errno.h>

	#include <openssl/rand.h>
	#include <openssl/ssl.h>
	#include <openssl/err.h>
	#include <openssl/crypto.h>
	#include <openssl/x509.h>
	#include <openssl/pem.h>
#endif

/* 2015.2.13 Telly
extern  void yield_error();
#define ERROR(n)     yield_error(n)
*/

#define ILL_ARG      9

#define PARG(n,i)    (next_var_cell - n + i)

#define GET_INT_CAR_LIST(top,data)     \
           REALVALUE(top);             \
           if(!IS_LIST(top)) break;    \
           top=BODY(top);              \
           data = (int)Numeric_s_num_eval_int(top++)

#define MAKE_INT_CAR_LIST(top,data) \
	  SETTAG(top,  list_tag); BODY(top)=top+1;      \
	  SETTAG(top+1,int_tag);  INT_BODY(top+1)=data; \
	  top += 2

#define Rec_buff_size  0xfffff+1         /* 10240000 => 1048576 */
#define Rec_buff_size2 0xffff+1          /* 102400   => 65536   */

//static	char s_buffer[Rec_buff_size];
static	char s_buffer2[Rec_buff_size2];

typedef unsigned char BYTE;

/* for SSL */
static SSL     *G_Ssl[Rec_buff_size2];
static SSL_CTX *G_SslCtx[Rec_buff_size2];
int            SSL_Socket_No = 0;


/* Prototype */
extern pred P4_ssl_client_connect();       /* (1) */
extern pred P2_ssl_socket_send();          /* (2) */
extern pred P2_ssl_socket_receive();       /* (3) */
extern pred P1_ssl_socket_close();         /* (4) */
extern pred P3_ssl_cert_load_cafile();     /* (5) */
extern pred P4_ssl_cert_load_file();       /* (6) */
extern pred P4_ssl_cert_load_data();       /* (7) */
extern pred P2_ssl_cert_send();            /* (8) */
extern pred P3_ssl_cert_receive();         /* (9) */
extern pred P1_ssl_cert_close();           /* (10) */
extern pred P2_socket_ssl_download();      /* (11) */

#ifdef WIN32
__declspec(dllexport) int initiate_socket_ssl(){
#else
extern int initiate_socket_ssl(){
#endif
  put_bltn("ssl_client_connect\0", 4,P4_ssl_client_connect);
  put_bltn("ssl_socket_send\0",    2,P2_ssl_socket_send);
  put_bltn("ssl_socket_receive\0", 2,P2_ssl_socket_receive);
  put_bltn("ssl_socket_close\0",   1,P1_ssl_socket_close);
  put_bltn("ssl_cert_load_cafile\0", 3,P3_ssl_cert_load_cafile);
  put_bltn("ssl_cert_load_file\0", 4,P4_ssl_cert_load_file);
  put_bltn("ssl_cert_load_data\0", 4,P4_ssl_cert_load_data);
  put_bltn("ssl_cert_send\0",      2,P2_ssl_cert_send);
  put_bltn("ssl_cert_receive\0",   3,P3_ssl_cert_receive);
  put_bltn("ssl_cert_close\0",     1,P1_ssl_cert_close);
  put_bltn("socket_ssl_download\0",2,P2_socket_ssl_download);

  return 1;
}


/* ?-ssl_client_connect(+HostName,+Port,+Ciphers,-Socket). */
pred P4_ssl_client_connect(Frame *Env)
{
	struct sockaddr_in server;

	char hostname[1024];
	char ciphers[512];
	char port[1024];
    int i,s;
	int on=1;

#ifdef WIN32
	WSADATA data;
	WSAStartup(MAKEWORD(2,0), &data);
#endif
	Atom2Asciz(GetAtom(PARG(4,0)),hostname);
	Atom2Asciz(GetAtom(PARG(4,1)),port);
	Atom2Asciz(GetAtom(PARG(4,2)),ciphers);

	memset((char *)&server, '\0',sizeof(server));
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = inet_addr(hostname);
	server.sin_port = htons(atoi(port));

	s = socket(AF_INET, SOCK_STREAM, 0);
	if(s < 0) YIELD(FAIL);

	if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))) {
		close(s);
		YIELD(FAIL);
	}

	if(connect(s, (struct sockaddr*)&server, sizeof(server)) == -1) {
		close(s);
		YIELD(FAIL);
	}

    SSL_load_error_strings();
	SSL_library_init();
	OpenSSL_add_all_algorithms();

	//i = ++SSL_Socket_No;
	if((SSL_Socket_No+1) < Rec_buff_size2) {
		SSL_Socket_No++;
		i = SSL_Socket_No;
	}
	else {
		YIELD(FAIL);
	}

    G_SslCtx[i] = SSL_CTX_new(SSLv23_client_method());
	if(G_SslCtx[i] == NULL) YIELD(FAIL);

    G_Ssl[i] = SSL_new(G_SslCtx[i]);
	if(G_Ssl[i] == NULL) YIELD(FAIL);

	if(strlen(ciphers) > 0) {
		if(SSL_CTX_set_cipher_list(G_SslCtx[i],ciphers) == 0) YIELD(FAIL);
	}

	if(SSL_set_fd(G_Ssl[i], s) == 0) YIELD(FAIL);

	/* SSL接続 */
	if(SSL_connect(G_Ssl[i]) != 1) YIELD(FAIL);


    //G_Bio[i] = BIO_new(BIO_f_ssl());
    //BIO_set_ssl(G_Bio[i],G_Ssl[i],BIO_CLOSE);

    //G_Bio_Info[i] = BIO_new(BIO_s_connect());
    //BIO_set_conn_hostname(G_Bio_Info[i],host);
    //BIO_set_nbio(G_Bio_Info[i],1);
    //G_Bio_Info[i] = BIO_push(G_Bio[i],G_Bio_Info[i]);

	YIELD(UnifyInt(PARG(4,3), i));
}

/* ?-ssl_socket_send(+Socket,+SendData). */
pred P2_ssl_socket_send(Frame *Env)
{
	char text[Rec_buff_size];
	int  socket;

	socket = GetInt(PARG(2,0));
	Atom2Asciz(GetAtom(PARG(2,1)),text);

	if(SSL_write(G_Ssl[socket], text, strlen(text)) < 1) YIELD(FAIL);

	YIELD(DET_SUCC);
}

/* ?-ssl_socket_receive(+Socket,-ReceiveData). */
pred P2_ssl_socket_receive(Frame *Env)
{
	char buf[Rec_buff_size];
	char p[1024];
    int read_size;
	int socket;

	socket = GetInt(PARG(2,0));

	memset(buf,'\0',sizeof(buf));
	while(1) {
		memset(p,'\0',sizeof(p));
		read_size = SSL_read(G_Ssl[socket], p, sizeof(p)-1);
    
		if(read_size > 0) {
			strcat(buf, p);
		}
		else if(read_size == 0) {
			break;
		}
		else {
			YIELD(FAIL);
		}
	}

	YIELD(UnifyAtom(PARG(2,1), PutAtom(Env,buf)));
}

/* ?-ssl_socket_close(+Socket). */
pred P1_ssl_socket_close(Frame *Env)
{
	int socket;

	socket = GetInt(PARG(1,0));

	//if(SSL_shutdown(G_Ssl[socket]) != 1) YIELD(FAIL);
	SSL_shutdown(G_Ssl[socket]);
    if(G_SslCtx[socket] != NULL) SSL_CTX_free(G_SslCtx[socket]);

	G_Ssl[socket] = NULL;
	G_SslCtx[socket] = NULL;

	YIELD(DET_SUCC);
}

/* ?-ssl_cert_load_cafile(+Socket,+CertFile,-SslNo). */
pred P3_ssl_cert_load_cafile(Frame *Env)
{
	int socket,sslno;
	char filepath_crt[1024];
	long verify_result;

	/* 引数の取得 */
	memset(filepath_crt,'\0',sizeof(filepath_crt));
	socket = GetInt(PARG(3,0));
	Atom2Asciz(GetAtom(PARG(3,1)),filepath_crt);

	SSL_library_init(); 
	SSL_load_error_strings(); 
	OpenSSL_add_all_algorithms();
	if((SSL_Socket_No+1) < Rec_buff_size2) {
		SSL_Socket_No++;
		sslno = SSL_Socket_No;
	}
	else {
		YIELD(FAIL);
	}
	G_SslCtx[sslno] = SSL_CTX_new(SSLv23_method());

	/* 証明書ファイルの読込み(CAを登録) */ 
	if(!SSL_CTX_load_verify_locations(G_SslCtx[sslno], filepath_crt, NULL)) YIELD(FAIL);

	/* 接続処理 */
	G_Ssl[sslno] = SSL_new(G_SslCtx[sslno]);
	SSL_set_fd(G_Ssl[sslno], socket);
	if(!SSL_connect(G_Ssl[sslno])) {
		YIELD(FAIL);
    }

	/* SSLチェック */
	verify_result = SSL_get_verify_result(G_Ssl[sslno]);
	if(!(verify_result == X509_V_OK || verify_result == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || verify_result == X509_V_ERR_CERT_HAS_EXPIRED)) {
		YIELD(FAIL);
	}

	YIELD(UnifyInt(PARG(3,2), sslno));
}

/* ?-ssl_cert_load_file(+Socket,+CertFile,+KeyFile,-SslNo). */
pred P4_ssl_cert_load_file(Frame *Env)
{
	int socket,sslno,fd;
	char filepath_crt[1024],filepath_key[1024];
	unsigned char cert_buf[Rec_buff_size2],key_buf[Rec_buff_size2];
	long verify_result;
	BIO *cbio,*kbio;
	X509 *x509 = X509_new();

	/* 引数の取得 */
	memset(filepath_crt,'\0',sizeof(filepath_crt));
	memset(filepath_key,'\0',sizeof(filepath_key));
	socket = GetInt(PARG(4,0));
	Atom2Asciz(GetAtom(PARG(4,1)),filepath_crt);
	Atom2Asciz(GetAtom(PARG(4,2)),filepath_key);

	/* 初期化 */ 
	cbio = BIO_new(BIO_s_mem());
	kbio = BIO_new(BIO_s_mem());

	SSL_library_init(); 
	SSL_load_error_strings(); 
	OpenSSL_add_all_algorithms();
	if((SSL_Socket_No+1) < Rec_buff_size2) {
		SSL_Socket_No++;
		sslno = SSL_Socket_No;
	}
	else {
		YIELD(FAIL);
	}
	G_SslCtx[sslno] = SSL_CTX_new(SSLv23_method());

	/* 証明書ファイルを取得する */
	memset(cert_buf,'\0',sizeof(cert_buf));
	if((fd = open(filepath_crt, O_RDONLY)) == -1) YIELD(FAIL);
	if(read(fd, cert_buf, sizeof(cert_buf)-1) == -1) YIELD(FAIL);
	close(fd);
	memset(key_buf,'\0',sizeof(key_buf));
	if((fd = open(filepath_key, O_RDONLY)) == -1) YIELD(FAIL);
	if(read(fd, key_buf, sizeof(key_buf)-1) == -1) YIELD(FAIL);
	close(fd);

    /* CA を登録 */ 
	BIO_puts(cbio, (const char *)cert_buf);
	x509 = PEM_read_bio_X509(cbio,NULL,NULL,NULL);
	if(x509 == NULL) {
		YIELD(FAIL);
    }
	if(SSL_CTX_use_certificate(G_SslCtx[sslno],x509) < 1) {
		YIELD(FAIL);
    }
	BIO_free(cbio);
	X509_free(x509);

	/* Private-Key を登録 */
	BIO_puts(kbio, (const char *)key_buf);
	x509 = (X509 *)PEM_read_bio_PrivateKey(kbio,NULL,NULL,NULL);
	if(x509 == NULL) {
		YIELD(FAIL);
    }
	if(SSL_CTX_use_PrivateKey(G_SslCtx[sslno], (EVP_PKEY *)x509) < 1) {
		YIELD(FAIL);
    }
	BIO_free(kbio);
	EVP_PKEY_free((EVP_PKEY *)x509);

	/* 接続処理 */
	G_Ssl[sslno] = SSL_new(G_SslCtx[sslno]);
	SSL_set_fd(G_Ssl[sslno], socket);
	if(!SSL_connect(G_Ssl[sslno])) {
		YIELD(FAIL);
    }

	/* SSLチェック */
	verify_result = SSL_get_verify_result(G_Ssl[sslno]);
	if(!(verify_result == X509_V_OK || verify_result == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)) {
		YIELD(FAIL);
	}

	YIELD(UnifyInt(PARG(4,3), sslno));
}

/* ?-ssl_cert_load_data(+Socket,+CertData,+KeyData,-SslNo). */
pred P4_ssl_cert_load_data(Frame *Env)
{
	int socket,sslno;
	unsigned char cert_buf[Rec_buff_size2],key_buf[Rec_buff_size2];
	long verify_result;
	BIO *cbio,*kbio;
	X509 *x509 = X509_new();

	/* 引数の取得 */
	memset(cert_buf,'\0',sizeof(cert_buf));
	memset(key_buf,'\0',sizeof(key_buf));
	socket = GetInt(PARG(4,0));
	Atom2Asciz(GetAtom(PARG(4,1)),(char *)cert_buf);
	Atom2Asciz(GetAtom(PARG(4,2)),(char *)key_buf);

	/* 初期化 */ 
	cbio = BIO_new(BIO_s_mem());
	kbio = BIO_new(BIO_s_mem());

	SSL_library_init(); 
	SSL_load_error_strings(); 
	OpenSSL_add_all_algorithms();
	//sslno = ++SSL_Socket_No;
	if((SSL_Socket_No+1) < Rec_buff_size2) {
		SSL_Socket_No++;
		sslno = SSL_Socket_No;
	}
	else {
		YIELD(FAIL);
	}

	G_SslCtx[sslno] = SSL_CTX_new(SSLv23_method());

    /* CA を登録 */ 
	BIO_puts(cbio, (const char *)cert_buf);
	x509 = PEM_read_bio_X509(cbio,NULL,NULL,NULL);
	if(x509 == NULL) {
		YIELD(FAIL);
    }
	if(SSL_CTX_use_certificate(G_SslCtx[sslno],x509) < 1) {
		YIELD(FAIL);
    }
	BIO_free(cbio);
	X509_free(x509);

	/* Private-Key を登録 */
	BIO_puts(kbio, (const char *)key_buf);
	x509 = (X509 *)PEM_read_bio_PrivateKey(kbio,NULL,NULL,NULL);
	if(x509 == NULL) {
		YIELD(FAIL);
    }
	if(SSL_CTX_use_PrivateKey(G_SslCtx[sslno], (EVP_PKEY *)x509) < 1) {
		YIELD(FAIL);
    }
	BIO_free(kbio);
	EVP_PKEY_free((EVP_PKEY *)x509);

	/* 接続処理 */
	G_Ssl[sslno] = SSL_new(G_SslCtx[sslno]);
	SSL_set_fd(G_Ssl[sslno], socket);
	if(!SSL_connect(G_Ssl[sslno])) {
		YIELD(FAIL);
    }

	/* SSLチェック */
	verify_result = SSL_get_verify_result(G_Ssl[sslno]);
	if(!(verify_result == X509_V_OK || verify_result == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)) {
		YIELD(FAIL);
	}

	YIELD(UnifyInt(PARG(4,3), sslno));

	YIELD(DET_SUCC);
}

/* ?-ssl_cert_send(+SslNo,+SendData). */
pred P2_ssl_cert_send(Frame *Env)
{
	int sslno,size;

	/* 引数の取得 */
	sslno = GetInt(PARG(2,0));
	Atom2Asciz(GetAtom(PARG(2,1)),s_buffer2);

	if((size = strlen(s_buffer2)) == 0) YIELD(FAIL);

	/* リクエストを送信 */
	if(SSL_write(G_Ssl[sslno], s_buffer2, size) <= 0) {
		YIELD(FAIL);
	}

	YIELD(DET_SUCC);
}

/* ?-ssl_cert_receive(+SslNo,-RecvData,-RecvSize). */
pred P3_ssl_cert_receive(Frame *Env)
{
	int sslno,read_size;
	char out_buffer[Rec_buff_size];

	/* 引数の取得 */
	sslno = GetInt(PARG(3,0));

	/* SSL読込み */
	memset(out_buffer,'\0',Rec_buff_size);
	read_size = SSL_read(G_Ssl[sslno], out_buffer, Rec_buff_size);
	if(read_size < 0) {
		YIELD(FAIL);
	}

	YIELD(UnifyAtom(PARG(3,1), PutAtom(Env,out_buffer)));
	YIELD(UnifyInt(PARG(3,2), read_size));
}

/* ?-ssl_cert_close(+SslNo). */
pred P1_ssl_cert_close(Frame *Env)
{
	int sslno;

	/* 引数の取得 */
	sslno = GetInt(PARG(1,0));

	/* リソース解放 */
	SSL_shutdown(G_Ssl[sslno]);
    SSL_free(G_Ssl[sslno]);
    SSL_CTX_free(G_SslCtx[sslno]);

    G_Ssl[sslno] = NULL;
	//G_Bio_Info[sslno] = NULL;
    G_SslCtx[sslno] = NULL;

	YIELD(DET_SUCC);
}

/* ?-socket_ssl_download(+SslNo,+DownloadFile). */
pred P2_socket_ssl_download(Frame *Env)
{
    int sslno,fd;
    int read_size,i,j,size;
    char download_file[1024];
    char *out_buffer;

    /* 引数の取得 */
    sslno = GetInt(PARG(2,0));
    Atom2Asciz(GetAtom(PARG(2,1)),download_file);

    /* 領域を確保 */
    //out_buffer = (char *)malloc(Rec_buff_size);
	size = 1024*1024*1024;
    out_buffer = (char *)malloc(size);
    if(out_buffer == NULL) {
        YIELD(FAIL);
    }

    /* 読込み処理 */
    memset(out_buffer,'\0',Rec_buff_size);
    i = 0;
    while(1) {
        memset(s_buffer2,'\0',Rec_buff_size2);
        read_size = SSL_read(G_Ssl[sslno], s_buffer2, Rec_buff_size2);
        if(read_size == 0) break;
        memcpy(&out_buffer[i],s_buffer2,read_size);
        i = i + read_size;
		if(i > size) {
            free(out_buffer);
            AZ_ERROR(ILL_ARG);
        }
    }

    /* 読込みバッファ上のヘッダ部分を取り除く */
    for(j=0;;j++) {
        if(out_buffer[j]    == '\r' &&
            out_buffer[j+1] == '\n' &&
            out_buffer[j+2] == '\r' &&
            out_buffer[j+3] == '\n') {
            j = j+4;
            break;
        }
    }

    /* ファイル書込み処理 */
#ifdef WIN32
	if((fd = open(download_file, O_CREAT|O_WRONLY|O_BINARY, S_IREAD|S_IWRITE)) == -1) {
#else
	if((fd = open(download_file, O_CREAT|O_WRONLY, S_IREAD|S_IWRITE)) == -1) {
#endif
        YIELD(FAIL);
    }
    if(write(fd, &out_buffer[j], i-j) <= 0) YIELD(FAIL);
    close(fd);

    /* 確保した領域を開放 */
    free(out_buffer);

    YIELD(DET_SUCC);
}
