#include<tqapplication.h>
#include<tqdom.h>
#include<tqfile.h>
#include<tqsocket.h>
#include<tqptrlist.h>
#include"base64.h"
#include"qca.h"

TQCA::Cert readCertXml(const TQDomElement &e)
{
	TQCA::Cert cert;
	// there should be one child data tag
	TQDomElement data = e.elementsByTagName("data").item(0).toElement();
	if(!data.isNull())
		cert.fromDER(Base64::stringToArray(data.text()));
	return cert;
}

void showCertInfo(const TQCA::Cert &cert)
{
	printf("-- Cert --\n");
	printf(" CN: %s\n", cert.subject()["CN"].latin1());
	printf(" Valid from: %s, until %s\n",
		cert.notBefore().toString().latin1(),
		cert.notAfter().toString().latin1());
	printf(" PEM:\n%s\n", cert.toPEM().latin1());
}

TQPtrList<TQCA::Cert> getRootCerts(const TQString &store)
{
	TQPtrList<TQCA::Cert> list;

	// open the Psi rootcerts file
	TQFile f(store);
	if(!f.open(IO_ReadOnly)) {
		printf("unable to open %s\n", f.name().latin1());
		return list;
	}
	TQDomDocument doc;
	doc.setContent(&f);
	f.close();

	TQDomElement base = doc.documentElement();
	if(base.tagName() != "store") {
		printf("wrong format of %s\n", f.name().latin1());
		return list;
	}
	TQDomNodeList cl = base.elementsByTagName("certificate");
	if(cl.count() == 0) {
		printf("no certs found in %s\n", f.name().latin1());
		return list;
	}

	int num = 0;
	for(int n = 0; n < (int)cl.count(); ++n) {
		TQCA::Cert *cert = new TQCA::Cert(readCertXml(cl.item(n).toElement()));
		if(cert->isNull()) {
			printf("error reading cert\n");
			delete cert;
			continue;
		}

		++num;
		list.append(cert);
	}
	printf("imported %d root certs\n", num);

	return list;
}

TQString resultToString(int result)
{
	TQString s;
	switch(result) {
		case TQCA::TLS::NoCert:
			s = TQObject::tr("No certificate presented.");
			break;
		case TQCA::TLS::Valid:
			break;
		case TQCA::TLS::HostMismatch:
			s = TQObject::tr("Hostname mismatch.");
			break;
		case TQCA::TLS::Rejected:
			s = TQObject::tr("Root CA rejects the specified purpose.");
			break;
		case TQCA::TLS::Untrusted:
			s = TQObject::tr("Not trusted for the specified purpose.");
			break;
		case TQCA::TLS::SignatureFailed:
			s = TQObject::tr("Invalid signature.");
			break;
		case TQCA::TLS::InvalidCA:
			s = TQObject::tr("Invalid CA certificate.");
			break;
		case TQCA::TLS::InvalidPurpose:
			s = TQObject::tr("Invalid certificate purpose.");
			break;
		case TQCA::TLS::SelfSigned:
			s = TQObject::tr("Certificate is self-signed.");
			break;
		case TQCA::TLS::Revoked:
			s = TQObject::tr("Certificate has been revoked.");
			break;
		case TQCA::TLS::PathLengthExceeded:
			s = TQObject::tr("Maximum cert chain length exceeded.");
			break;
		case TQCA::TLS::Expired:
			s = TQObject::tr("Certificate has expired.");
			break;
		case TQCA::TLS::Unknown:
		default:
			s = TQObject::tr("General validation error.");
			break;
	}
	return s;
}

class SecureTest : public TQObject
{
	TQ_OBJECT
public:
	SecureTest()
	{
		sock = new TQSocket;
		connect(sock, TQ_SIGNAL(connected()), TQ_SLOT(sock_connected()));
		connect(sock, TQ_SIGNAL(readyRead()), TQ_SLOT(sock_readyRead()));
		connect(sock, TQ_SIGNAL(connectionClosed()), TQ_SLOT(sock_connectionClosed()));
		connect(sock, TQ_SIGNAL(error(int)), TQ_SLOT(sock_error(int)));

		ssl = new TQCA::TLS;
		connect(ssl, TQ_SIGNAL(handshaken()), TQ_SLOT(ssl_handshaken()));
		connect(ssl, TQ_SIGNAL(readyRead()), TQ_SLOT(ssl_readyRead()));
		connect(ssl, TQ_SIGNAL(readyReadOutgoing(int)), TQ_SLOT(ssl_readyReadOutgoing(int)));
		connect(ssl, TQ_SIGNAL(closed()), TQ_SLOT(ssl_closed()));
		connect(ssl, TQ_SIGNAL(error(int)), TQ_SLOT(ssl_error(int)));

		rootCerts.setAutoDelete(true);
		rootCerts = getRootCerts("/usr/local/share/psi/certs/rootcert.xml");
	}

	~SecureTest()
	{
		delete ssl;
		delete sock;
	}

	void start(const TQString &_host)
	{
		int n = _host.find(':');
		int port;
		if(n != -1) {
			host = _host.mid(0, n);
			port = _host.mid(n+1).toInt();
		}
		else {
			host = _host;
			port = 443;
		}

		printf("Trying %s:%d...\n", host.latin1(), port);
		sock->connectToHost(host, port);
	}

signals:
	void quit();

private slots:
	void sock_connected()
	{
		printf("Connected, starting TLS handshake...\n");
		ssl->setCertificateStore(rootCerts);
		ssl->startClient(host);
	}

	void sock_readyRead()
	{
		TQByteArray buf(sock->bytesAvailable());
		int num = sock->readBlock(buf.data(), buf.size());
		if(num < (int)buf.size())
			buf.resize(num);
		ssl->writeIncoming(buf);
	}

	void sock_connectionClosed()
	{
		printf("\nConnection closed.\n");
		quit();
	}

	void sock_error(int)
	{
		printf("\nSocket error.\n");
		quit();
	}

	void ssl_handshaken()
	{
		cert = ssl->peerCertificate();
		int vr = ssl->certificateValidityResult();

		printf("Successful SSL handshake.\n");
		if(!cert.isNull())
			showCertInfo(cert);
		if(vr == TQCA::TLS::Valid)
			printf("Valid certificate.\n");
		else
			printf("Invalid certificate: %s\n", resultToString(vr).latin1());

		printf("Let's try a GET request now.\n");
		TQString req = "GET / HTTP/1.0\nHost: " + host + "\n\n";
		TQCString cs = req.latin1();
		TQByteArray buf(cs.length());
		memcpy(buf.data(), cs.data(), buf.size());
		ssl->write(buf);
	}

	void ssl_readyRead()
	{
		TQByteArray a = ssl->read();
		TQCString cs;
		cs.resize(a.size()+1);
		memcpy(cs.data(), a.data(), a.size());
		printf("%s", cs.data());
	}

	void ssl_readyReadOutgoing(int)
	{
		TQByteArray a = ssl->readOutgoing();
		sock->writeBlock(a.data(), a.size());
	}

	void ssl_closed()
	{
		printf("SSL session closed\n");
	}

	void ssl_error(int x)
	{
		if(x == TQCA::TLS::ErrHandshake) {
			printf("SSL Handshake Error!\n");
			quit();
		}
		else {
			printf("SSL Error!\n");
			quit();
		}
	}

private:
	TQString host;
	TQSocket *sock;
	TQCA::TLS *ssl;
	TQCA::Cert cert;
	TQPtrList<TQCA::Cert> rootCerts;
};

#include"ssltest.moc"

int main(int argc, char **argv)
{
	TQApplication app(argc, argv, false);
	TQString host = argc > 1 ? argv[1] : "andbit.net";

	if(!TQCA::isSupported(TQCA::CAP_TLS)) {
		printf("TLS not supported!\n");
		return 1;
	}

	SecureTest *s = new SecureTest;
	TQObject::connect(s, TQ_SIGNAL(quit()), &app, TQ_SLOT(quit()));
	s->start(host);
	app.exec();
	delete s;

	return 0;
}
