/* *

	Don't use this file anymore. The maintained version is in http2.d, just use that.

	Old docs below:

	This is CLIENT only at this point. Don't try to
	bind/accept with these.

	FIXME: Windows isn't implemented

	On Windows, it uses Microsoft schannel so it doesn't
	need openssl or gnutls as a dependency.

	On other platforms, it uses the openssl api, which should
	work with both openssl and gnutls.


	btw, interesting:
	http://msdn.microsoft.com/en-us/library/windows/desktop/aa364510%28v=vs.85%29.aspx
*/
module sslsocket;


import std.socket;

// see also:
// http://msdn.microsoft.com/en-us/library/aa380536%28v=vs.85%29.aspx

// import deimos.openssl.ssl;

version=use_openssl;

version(use_openssl) {
	alias SslClientSocket = OpenSslSocket;

	extern(C) {
		int SSL_library_init();
		void OpenSSL_add_all_ciphers();
		void OpenSSL_add_all_digests();
		void SSL_load_error_strings();

		struct SSL {}
		struct SSL_CTX {}
		struct SSL_METHOD {}

		SSL_CTX* SSL_CTX_new(const SSL_METHOD* method);
		SSL* SSL_new(SSL_CTX*);
		int SSL_pending(SSL*);
		int SSL_set_fd(SSL*, int);
		int SSL_connect(SSL*);
		int SSL_write(SSL*, const void*, int);
		int SSL_read(SSL*, void*, int);
		void SSL_free(SSL*);
		void SSL_CTX_free(SSL_CTX*);

		void SSL_set_verify(SSL*, int, void*);
		enum SSL_VERIFY_NONE = 0;

		SSL_METHOD* SSLv3_client_method();
		SSL_METHOD* TLS_client_method();
		SSL_METHOD* SSLv23_client_method();

		void ERR_print_errors_fp(FILE*);
	}

	import core.stdc.stdio;

	shared static this() {
		SSL_library_init();
		OpenSSL_add_all_ciphers();
		OpenSSL_add_all_digests();
		SSL_load_error_strings();
	}

	pragma(lib, "crypto");
	pragma(lib, "ssl");

	class OpenSslSocket : Socket {
		private SSL* ssl;
		private SSL_CTX* ctx;
		private void initSsl(bool verifyPeer) {
			ctx = SSL_CTX_new(SSLv23_client_method());
			assert(ctx !is null);

			ssl = SSL_new(ctx);
			if(!verifyPeer)
				SSL_set_verify(ssl, SSL_VERIFY_NONE, null);
			SSL_set_fd(ssl, cast(int) this.handle);
		}

		bool dataPending() {
			return SSL_pending(ssl) > 0;
		}

		@trusted
		override void connect(Address to) {
			super.connect(to);
			if(SSL_connect(ssl) == -1) {
				ERR_print_errors_fp(stderr);
				int i;
				printf("wtf\n");
				scanf("%d\n", i);
				throw new Exception("ssl connect");
			}
		}
		
		@trusted
		override ptrdiff_t send(const(void)[] buf, SocketFlags flags) {
			auto retval = SSL_write(ssl, buf.ptr, cast(uint) buf.length);
			if(retval == -1) {
				ERR_print_errors_fp(stderr);
				int i;
				printf("wtf\n");
				scanf("%d\n", i);
				throw new Exception("ssl send");
			}
			return retval;

		}
		override ptrdiff_t send(const(void)[] buf) {
			return send(buf, SocketFlags.NONE);
		}
		@trusted
		override ptrdiff_t receive(void[] buf, SocketFlags flags) {
			auto retval = SSL_read(ssl, buf.ptr, cast(int)buf.length);
			if(retval == -1) {
				ERR_print_errors_fp(stderr);
				int i;
				printf("wtf\n");
				scanf("%d\n", i);
				throw new Exception("ssl send");
			}
			return retval;
		}
		override ptrdiff_t receive(void[] buf) {
			return receive(buf, SocketFlags.NONE);
		}

		this(AddressFamily af, SocketType type = SocketType.STREAM, bool verifyPeer = true) {
			super(af, type);
			initSsl(verifyPeer);
		}

		this(socket_t sock, AddressFamily af) {
			super(sock, af);
			initSsl(true);
		}

		~this() {
			SSL_free(ssl);
			SSL_CTX_free(ctx);
		}
	}
}

version(ssl_test)
void main() {
	auto sock = new SslClientSocket(AddressFamily.INET);
	sock.connect(new InternetAddress("localhost", 443));
	sock.send("GET / HTTP/1.0\r\n\r\n");
	import std.stdio;
	char[1024] buffer;
	writeln(buffer[0 .. sock.receive(buffer)]);
}