/*
 *  Copyright (C) 2007-2008 CMM Sigma Andrzej Chmielowiec <cmmsigma@cmmsigma.eu>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 3
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 */

#include "CA.h"
#include "CaNewDialog.h"
#include "FileDialogWPR.h"
#include "crd_ut10xx.h"
#include "DirectoryArchiver.h"
#include "CaOptionsDialog.h"
#include "SendMailDialog.h"
#include "ReceiveMailDialog.h"
#include "FtpDistDialog.h"
#include "QDetailsMessageBox.h"

#include <QtCore>
#include <QtGui>

static const int StateClear = 0;
static const int StateInit = 1;
static const int StateConf = 2;
static const int StateLock = 3;

#ifndef STOKEN_SOFT
static int
sTokenState(CRD_UT10XX_t *ut)
{
	if (ut->info.info & 0x04)
		return StateLock;
	
	else if (ut->info.info & 0x02)
		return StateConf;
	
	else if (ut->info.info & 0x01)
		return StateInit;
	
	else
		return StateClear;
}
#endif

CA::CA()
{
	mOpensslCmd = QCoreApplication::applicationDirPath().append(QString("/../bin/stoken_openssl"));
#ifdef Q_WS_WIN
	mOpensslCmd.append(QString(".exe"));
#endif
	
	mXmlConfFileName = QString("xml_conf");
	mCertsDirName = QString("certs");
	mNewCertsDirName = QString("certs_new");
	mCrlDirName = QString("crl");
	mPrivateDirName = QString("private");
	mReqDirName = QString("req");
	mNewReqDirName = QString("req_new");
	mDbFileName = QString("db_cert");
	mCertSerialFileName = QString("serial_cert");
	mCrlSerialFileName = QString("serial_crl");
#ifdef STOKEN_SOFT
	mCaKeyDir = QString("key");
#endif
	
	mDefaultCertDays = 3650;
	mDefaultCrlDays = 30;
	mDefaultMD = QString("ecdsa-with-sha256");
	
	mPolicyCountryName = QString("optional");
	mPolicyStateOrProvinceName = QString("optional");
	mPolicyLocalityName = QString("optional");
	mPolicyOrganizationName = QString("optional");
	mPolicyOrganizationalUnitName = QString("optional");
	mPolicyCommonName = QString("supplied");
	mPolicyEmailAddress = QString("optional");
	
	mCrlIsActual = true;
	mCertOnlyValidDisplay = false;
	mCertTreeWidget = 0;
	mCertInfoTextEdit = 0;
	mReqTreeWidget = 0;
	mReqInfoTextEdit = 0;
}

CA::~CA()
{
	mUserPin.clear();
}

void
CA::setName(const QString &name)
{
	mName = name;
	mConfFileName = name + QString(".conf");
	mConfDirPath = QDir::current().path() + QDir::separator() + name;
	mCaCertFileName = name + QString(".cert");
	mCrlFileName = name + QString(".crl");
}

void
CA::setCaKeyName(const QString &name)
{
	mCaKeyName = name;
}

void
CA::setDefaultCertDays(int days)
{
	mDefaultCertDays = days;
}

void
CA::setDefaultCrlDays(int days)
{
	mDefaultCrlDays = days;
}

void
CA::setDefaultMD(const QString &md)
{
	mDefaultMD = md;
}

void
CA::setPolicy(const QString &countryName, const QString &stateOrProvinceName,
	const QString &localityName, const QString &organizationName,
	const QString &organizationalUnitName, const QString &commonName,
	const QString &emailAddress)
{
	mPolicyCountryName = countryName;
	mPolicyStateOrProvinceName = stateOrProvinceName;
	mPolicyLocalityName = localityName;
	mPolicyOrganizationName = organizationName;
	mPolicyOrganizationalUnitName = organizationalUnitName;
	mPolicyCommonName = commonName;
	mPolicyEmailAddress = emailAddress;
}

void
CA::setInfoWidget(QWidget *widget)
{
	mInfoWidget = widget;
}

void
CA::setCertOnlyValidDisplay(bool b)
{
	mCertOnlyValidDisplay = b;
	update();
}

void
CA::setCertificateTreeWidget(QTreeWidget *tree)
{
	mCertTreeWidget = tree;
}

void
CA::setCertificateInfoWidget(QTextEdit *edit)
{
	mCertInfoTextEdit = edit;
}

void
CA::setRequestTreeWidget(QTreeWidget *tree)
{
	mReqTreeWidget = tree;
}

void
CA::setRequestInfoWidget(QTextEdit *edit)
{
	mReqInfoTextEdit = edit;
}

QString
CA::name()
{
	return mName;
}

QString
CA::caKeyName()
{
	return mCaKeyName;
}

QString
CA::generalInfo()
{
	QString info;
	QStringList keyParts(mCaKeyName.split(QChar('/')));
	
	info.append(QObject::tr("CA key: "));
#ifndef STOKEN_SOFT
	info.append(QObject::tr("<ol><li>Token name: "));
	info.append(keyParts[0]).append(QString("</li>"));
#else
	info.append(QString("<ol>"));
#endif
#ifndef STOKEN_SOFT	
	info.append(QObject::tr("<li>Key name: "));
	info.append(keyParts[1]).append(QString("</li></ol>"));
#else
	info.append(QObject::tr("<li>File name: "));
	info.append(keyParts[2]).append(QString("</li></ol>"));
#endif
	info.append(QObject::tr("CA certificate: "));
	info.append(QObject::tr("<ol><li>File name: "));
	info.append(mCaCertFileName).append(QString("</li>"));
	info.append(QObject::tr("<li>Valid from: "));
	info.append(mCertList[0].notBefore().toString(QString("yyyy-MM-dd (hh:mm:ss)")));
	info.append(QString(" GMT</li>"));
	info.append(QObject::tr("<li>Valid to: "));
	info.append(mCertList[0].notAfter().toString(QString("yyyy-MM-dd  (hh:mm:ss)")));
	info.append(QString(" GMT</li></ol>"));
	
	return info;
}

QString
CA::crlInfo()
{
	QString info;
	
	readCaCrl();
	
	if (!mCrlIsActual)
	{
		info.append(QObject::tr("<center><h3><font color=red>CRL list is "
			"outdated!</font></h3></center>"));
	}
	
	info.append(QObject::tr("File name: ")).append(mCrlFileName);
	
	mOpensslArgs.clear();
	mOpensslArgs << "crl" << "-in" << mCrlFileName << "-noout" << "-lastupdate" <<
		"-nextupdate";
	
	if (!runOpenssl())
		return info;
	
	QStringList crlDateTime(mOpensslOut.split(QChar('\n')));
	
	info.append(QObject::tr("<br>Last updated: "));
	info.append(mCrlLastUpdate.toString(QString("yyyy-MM-dd (hh:mm:ss)")));
	info.append(QObject::tr(" GMT<br>Next update: "));
	info.append(mCrlNextUpdate.toString(QString("yyyy-MM-dd (hh:mm:ss)")));
	info.append(QObject::tr(" GMT"));
	
	return info;
}

static QString
trPolicy(const QString &policy)
{
	if (policy == QString("optional"))
		return QObject::tr("optional");
	
	else if (policy == QString("supplied"))
		return QObject::tr("supplied");
	
	else if (policy == QString("match"))
		return QObject::tr("match");
	
	else
		return QObject::tr("UNKNOWN");
}

QString
CA::policy()
{
	QString policy;
	
	policy.append(QString("<table><tr><td>"));
	policy.append(QObject::tr("Country code: "));
	policy.append(QString("</td><td>"));
	policy.append(trPolicy(mPolicyCountryName));
	policy.append(QString("</td></tr><tr><td>"));
	policy.append(QObject::tr("Region: "));
	policy.append(QString("</td><td>"));
	policy.append(trPolicy(mPolicyStateOrProvinceName));
	policy.append(QString("</td></tr><tr><td>"));
	policy.append(QObject::tr("City: "));
	policy.append(QString("</td><td>"));
	policy.append(trPolicy(mPolicyLocalityName));
	policy.append(QString("</td></tr><tr><td>"));
	policy.append(QObject::tr("Organization: "));
	policy.append(QString("</td><td>"));
	policy.append(trPolicy(mPolicyOrganizationName));
	policy.append(QString("</td></tr><tr><td>"));
	policy.append(QObject::tr("Department: "));
	policy.append(QString("</td><td>"));
	policy.append(trPolicy(mPolicyOrganizationalUnitName));
	policy.append(QString("</td></tr><tr><td>"));
	policy.append(QObject::tr("Common name: "));
	policy.append(QString("</td><td>"));
	policy.append(trPolicy(mPolicyCommonName));
	policy.append(QString("</td></tr><tr><td>"));
	policy.append(QObject::tr("E-mail: "));
	policy.append(QString("</td><td>"));
	policy.append(trPolicy(mPolicyEmailAddress));
	policy.append(QString("</td></tr></table>"));
	
	return policy;
}

bool
CA::create(QString title, QWidget *parent)
{
	bool createdCorrect = false;
	
	CaNewDialog *dialog = new CaNewDialog(parent);
	dialog->setWindowTitle(title);
	MailAccountWidget *mail = dialog->getMailWidget();
	mail->setData(mCaOptions.mailConfig);
	
	if (dialog->exec() == QDialog::Accepted)
	{
		// Set CA configuration.
		setName(dialog->name());
#ifndef STOKEN_SOFT
		setCaKeyName(dialog->caKeyName());
#else
		setCaKeyName(dialog->name() + ".key");
#endif
		setDefaultCertDays(dialog->defaultCertDays());
		setDefaultCrlDays(dialog->defaultCrlDays());
		setDefaultMD(dialog->defaultMD());
		setPolicy(dialog->policyCountryName(), dialog->policyStateOrProvinceName(),
			dialog->policyLocalityName(), dialog->policyOrganizationName(),
			dialog->policyOrganizationalUnitName(), dialog->policyCommonName(),
			dialog->policyEmailAddress());
		
#ifndef STOKEN_SOFT
		mSTokenName = dialog->caKeyName();
		mSTokenName.remove(mSTokenName.indexOf('/'), mSTokenName.size());
		mSTokenSerial = mSTokenName;
		mSTokenName.remove(mSTokenName.lastIndexOf(':'), mSTokenName.size());
		mSTokenSerial.remove(0, mSTokenSerial.lastIndexOf(':') + 1);
#endif
		
#ifndef STOKEN_SOFT
		// Get User PIN to generate self signad CA certificate.
		if (!getUserPin(parent))
		{
			delete dialog;
			return createdCorrect;
		}
		
		// Generate configuration directory and CA certificate.
		createConfDir();
#else
		// Get User PIN.
		if (!generatePIN(parent))
		{
			delete dialog;
			return createdCorrect;
		}
		
		// Generate configuration directory and CA certificate.
		createConfDir();
		
		// Generate ECDSA params set I.
		mOpensslArgs.clear();
		mOpensslArgs << "ecparam" << "-name" << "prime256v1" << "-out" << "prime256v1";
		
		if (!runOpenssl())
		{
			QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
				QObject::tr("Cannot create domain file."), mOpensslError);
			deleteConfDir();
			delete dialog;
			return createdCorrect;
		}
		
		// Generate ECDSA params set II.
		mOpensslArgs.clear();
		mOpensslArgs << "ecparam" << "-name" << "secp384r1" << "-out" << "secp384r1";
		
		if (!runOpenssl())
		{
			QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
				QObject::tr("Cannot create domain file."), mOpensslError);
			deleteConfDir();
			delete dialog;
			return createdCorrect;
		}
#endif

		// write xml config
		mail->getData(mCaOptions.mailConfig);
		writeXmlConfig();
		writeConfiguration();
		
		mOpensslArgs.clear();
		
#ifdef STOKEN_SOFT
		mOpensslArgs << "req" << "-new" << "-x509" << "-utf8" << "-config" << mConfFileName <<
			"-set_serial" << "0" << "-days" << QString("%1").arg(dialog->caCertDays()) <<
			"-md" << mDefaultMD << "-out" << mCaCertFileName << "-subj" << dialog->caSubject() <<
			"-newkey" << dialog->caAlgo() << "-keyout" << mCaKeyDir + QDir::separator() +
			mCaKeyName << "-passout" << QString("pass:") + mUserPin;
#else
		mOpensslArgs << "req" << "-new" << "-x509" << "-utf8" << "-config" << mConfFileName <<
			"-set_serial" << "0" << "-engine" << "stoken" << "-engctrl" <<
			QString("user_pin:").append(mUserPin) << "-key" << mCaKeyName << "-keyform" <<
			"ENGINE" << "-days" << QString("%1").arg(dialog->caCertDays()) << "-md" <<
			mDefaultMD << "-out" << mCaCertFileName << "-subj" << dialog->caSubject();
#endif
		if (!runOpenssl())
		{
			if (mOpensslError.contains("error:0906A065"))
			{
				QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
					QObject::tr("Incorrect password."), mOpensslError);
			}
			else
			{
				QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
					QObject::tr("Error generating CA autocertificate."), 
					mOpensslOut + QString("\n\n") + mOpensslError);
			}
			deleteConfDir();
			delete dialog;
			return createdCorrect;
		}
		
		createdCorrect = true;
		
		QMessageBox::information(parent, QObject::tr("Information"), 
			QObject::tr("CA is created properly."));
	}
	
	delete dialog;
	return createdCorrect;
}

bool
CA::open(QWidget *parent)
{
	// Read CA configuration.
	if (!readConfiguration() || !readXmlConfig())
	{
		QMessageBox::critical(parent, QObject::tr("Error"), 
			QObject::tr("Canno load CA configuration files."));
		return false;
	}
	
	// Get PIN for CA private key.
	if (!getUserPin(parent))
		return false;
	
	// Run OpenSSL and update certificate database.
	mOpensslArgs.clear();
#ifndef STOKEN_SOFT
	mOpensslArgs << "ca" << "-config" << mConfFileName << "-updatedb" << "-engine" <<
		"stoken" << "-engctrl" << QString("user_pin:").append(mUserPin) <<
		"-keyform" << "ENGINE";
#else
	mOpensslArgs << "ca" << "-config" << mConfFileName << "-updatedb" << "-engine" <<
		"stokensoft" << "-engctrl" << QString("user_pin:").append(mUserPin) <<
		"-keyform" << "ENGINE";
#endif
	
	if (!runOpenssl())
	{
		if(mOpensslError.contains("error:0906A065"))
		{
			QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
				QObject::tr("Incorrect password."), mOpensslError);
		}
		else if(mOpensslError.contains("error:80064065"))
		{
			QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
				QObject::tr("Cannot load private key file."), mOpensslError);
		}
		else
		{
			QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
				QObject::tr("Cannot update certificate database."), mOpensslError);
		}
		return false;
	}
	
	// Read list of new requests.
	if (!readNewRequests(parent))
		return false;
	
	updateRequestTree();
	updateRequestInfo();
	
	return true;
}

bool
CA::update(QWidget *parent)
{
	if (!readCertificateDb())
	{
		QMessageBox::critical(parent, QObject::tr("Error"), 
			QObject::tr("Cannot load certificate database."));
		return false;
	}
	
	updateCertificateTree();
	updateCertificateInfo();
	
	return true;
}

bool
CA::close(QWidget *parent)
{
	mUserPin.clear();
	
	parent = 0;
	
	mCertOnlyValidDisplay = false;
	
	if (mCertTreeWidget)
	{
		while (0 < mCertTreeWidget->topLevelItemCount())
			mCertTreeWidget->takeTopLevelItem(0);
		
		mCertTreeWidget = 0;
	}
	
	if (mCertInfoTextEdit)
	{
		mCertInfoTextEdit->clear();
		mCertInfoTextEdit = 0;
	}
	
	if (mReqTreeWidget)
	{
		while (0 < mReqTreeWidget->topLevelItemCount())
			mReqTreeWidget->takeTopLevelItem(0);
		
		mReqTreeWidget = 0;
	}
	
	if (mReqInfoTextEdit)
	{
		mReqInfoTextEdit->clear();
		mReqInfoTextEdit = 0;
	}
	
	return true;
}

bool
CA::remove(QWidget *parent)
{
	int button = QMessageBox::question(parent, QObject::tr("Question"), 
		QObject::tr("Do you want to delete CA?"), QMessageBox::Yes | 
		QMessageBox::No, QMessageBox::No);
	
	if (button == QMessageBox::No)
		return false;
	
	close(parent);
	deleteConfDir();
	
	return true;
}

void
CA::changeCertificateInfo()
{
	updateCertificateInfo();
}

void
CA::changeRequestInfo()
{
	updateRequestInfo();
}

bool
CA::reqGet(QWidget *parent)
{
	QStringList requests = FileDialogWPR::getOpenFileNames(parent, 
		QObject::tr("Select requests"), QString(), 
		QObject::tr("Requests (*.req);;All files (*)"));
	
	for (int i = 0; i < requests.size(); i++)
	{
		QFile reqFile(requests[i]);
		QFile reqLocFile(mConfDirPath + QDir::separator() + mNewReqDirName + 
			QDir::separator() + QFileInfo(requests[i]).fileName());
		
		if (!reqFile.open(QIODevice::ReadOnly))
		{
			QMessageBox::critical(parent, QObject::tr("Error"), 
				QObject::tr("Cannot open file '%1'.").arg(QFileInfo(requests[i]).fileName()));
			continue;
		}
		
		reqLocFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
		reqLocFile.write(reqFile.readAll());
		reqLocFile.close();
		reqFile.close();
	}
	
	if (!readNewRequests(parent))
		return false;
	
	updateRequestTree();
	updateRequestInfo();
	
	return true;
}

bool
CA::reqRecv(QWidget *parent)
{
	if(!mCaOptions.mailConfig.checkIncommingConfig())
	{
		QMessageBox::information(parent, QObject::tr("Information"), 
			QObject::tr("Receiving requests requires mail account configured."));
		
		return false;
	}
	
	ReceiveMailDialog dlg(mCaOptions.mailConfig, mConfDirPath, parent);
	
	if (dlg.exec() == QDialog::Accepted)
	{
		QList<AttachmentItem> &requests = dlg.getRequestFiles();
		
		foreach (AttachmentItem req, requests)
		{
			QFile reqLocFile(mConfDirPath + QDir::separator() + mNewReqDirName + 
				QDir::separator() + req.file);
			
			reqLocFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
			reqLocFile.write(req.body);
			reqLocFile.close();
		}
		
		if (!readNewRequests(parent))
			return false;
		
		updateRequestTree();
		updateRequestInfo();
		
		return true;
	}
	
	return false;
}

bool
CA::reqSign(QWidget *parent)
{
	Request *req = static_cast<Request*>(mReqTreeWidget->currentItem());
	
	if (!req)
	{
		QMessageBox::information(parent, QObject::tr("Information"), 
			QObject::tr("Select request to be changed to certificate and signed."));
		return false;
	}
	
	// Setup OpenSSL to generate certificate and sign it.
	mOpensslArgs.clear();
#ifndef STOKEN_SOFT
	mOpensslArgs << "ca" << "-config" <<  mConfFileName << "-engine" << "stoken" <<
		"-engctrl" << QString("user_pin:").append(mUserPin) << "-keyform" << "ENGINE" <<
		"-batch" << "-in" << mNewReqDirName + QDir::separator() + req->fileName();
#else
	mOpensslArgs << "ca" << "-config" <<  mConfFileName << "-engine" << "stokensoft" <<
		"-engctrl" << QString("user_pin:").append(mUserPin) << "-keyform" << "ENGINE" <<
		"-batch" << "-in" << mNewReqDirName + QDir::separator() + req->fileName();
#endif
	
	if (!runOpenssl())
	{
		QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
			QObject::tr("Cannot sign selected request."), mOpensslError);
		return false;
	}
	
	// Remove request.
	QDir newReqDir(mConfDirPath + QDir::separator() + mNewReqDirName);
	newReqDir.remove(req->fileName());
	
	// Update requests list.
	if (!readNewRequests(parent))
		return false;
	
	updateRequestTree();
	updateRequestInfo();
	
	// Update CA informations.
	update();
	
	if(mCaOptions.mailConfig.mUseMail)
	{
		int button = QMessageBox::question(parent, QObject::tr("Question"), 
			QObject::tr("Send certificate to User now?"), 
			QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
		
		if (button == QMessageBox::Yes)
		{
			certSend(true, parent);
		}
	}
	
	return true;
}

bool
CA::reqSignAll(QWidget *parent)
{
	// Setup OpenSSL to generate certificate and sign it.
	mOpensslArgs.clear();
#ifndef STOKEN_SOFT
	mOpensslArgs << "ca" << "-config" <<  mConfFileName << "-engine" << "stoken" <<
		"-engctrl" << QString("user_pin:").append(mUserPin) << "-keyform" << "ENGINE" <<
		"-batch" << "-infiles";
#else
	mOpensslArgs << "ca" << "-config" <<  mConfFileName << "-engine" << "stokensoft" <<
		"-engctrl" << QString("user_pin:").append(mUserPin) << "-keyform" << "ENGINE" <<
		"-batch" << "-infiles";
#endif
	
	for (int i = 0; i < mReqList.size(); i++)
		mOpensslArgs << mNewReqDirName + QDir::separator() + mReqList[i].fileName();
	
	if (!runOpenssl())
	{
		QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
			QObject::tr("Cannot sign request."), mOpensslError);
		return false;
	}
	
	// Remove requests.
	QDir newReqDir(mConfDirPath + QDir::separator() + mNewReqDirName);
	
	for (int i = 0; i < mReqList.size(); i++)
		newReqDir.remove(mReqList[i].fileName());
	
	// Update requests list.
	if (!readNewRequests(parent))
		return false;
	
	updateRequestTree();
	updateRequestInfo();
	
	// Update CA informations.
	update();
	
	return true;
}

bool
CA::reqDelete(QWidget *parent)
{
	Request *req = static_cast<Request*>(mReqTreeWidget->currentItem());
	
	if (!req)
	{
		QMessageBox::information(parent, QObject::tr("Information"), 
			QObject::tr("Select request to delete."));
		return false;
	}
	
	int button = QMessageBox::question(parent, QObject::tr("Question"), 
		QObject::tr("Do you want to delete selected request?"), QMessageBox::Yes |
		QMessageBox::No, QMessageBox::No);
	
	if (button == QMessageBox::No)
		return false;
	
	// Remove request.
	QDir newReqDir(mConfDirPath + QDir::separator() + mNewReqDirName);
	newReqDir.remove(req->fileName());
	
	// Update requests list.
	if (!readNewRequests(parent))
		return false;
	
	updateRequestTree();
	updateRequestInfo();
	
	// Update CA informations.
	update();
	
	return true;
}

bool
CA::certSaveAs(QWidget *parent)
{
	Certificate *cert = static_cast<Certificate*>(mCertTreeWidget->currentItem());
	
	if (!cert)
	{
		QMessageBox::information(parent, QObject::tr("Information"), 
			QObject::tr("Select certificate to be saved."));
		return false;
	}
	
	// Get destination certificate file path.
	QString defaultCertFilePath(cert->commonName().toLower());
	defaultCertFilePath.replace(QRegExp("\\s+"), QString("_"));
	defaultCertFilePath.append(QString(".cert"));
	defaultCertFilePath.prepend(QDir::homePath() + QDir::separator());
	
	QString certFilePath = FileDialogWPR::getSaveFileName(parent, QObject::tr("Save certificate as"),
		defaultCertFilePath, QObject::tr("Certificate files (*.cert)"));
	
	if (certFilePath.isEmpty())
		return false;
	
	// Save certificate.
	QFile certFile(certFilePath);
	QFile certLocFile(mConfDirPath + QDir::separator() + cert->fileName());
		
	if (!certLocFile.open(QIODevice::ReadOnly))
	{
		QMessageBox::critical(parent, QObject::tr("Error"), QObject::tr("Cannot open file "
			"'%1'.").arg(cert->fileName()));
		return false;
	}
		
	certFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
	
	QByteArray certContents = certLocFile.readAll();
	certContents.remove(0, certContents.indexOf("-----BEGIN CERTIFICATE-----"));
	certFile.write(certContents);
	
	certLocFile.close();
	certFile.close();
	
	// change permissions of saved certificate file
	certFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser |
		QFile::WriteUser | QFile::ReadGroup | QFile::WriteGroup | QFile::ReadOther |
		QFile::WriteOther);
	
	return true;
}

bool
CA::certRevoke(QWidget *parent)
{
	Certificate *cert = static_cast<Certificate*>(mCertTreeWidget->currentItem());
	
	if (!cert)
	{
		QMessageBox::information(parent, QObject::tr("Information"),
			QObject::tr("Select certificate to be revoked."));
		return false;
	}
	
	if (cert->state() != QString("V") || cert->serial() == 0)
	{
		QMessageBox::information(parent, QObject::tr("Information"),
			QObject::tr("Selected certificate cannot be revokend."));
		return false;
	}
	
	// Setup OpenSSL arguments for certificate revocation.
	mOpensslArgs.clear();
#ifndef STOKEN_SOFT
	mOpensslArgs << "ca" << "-config" <<  mConfFileName << "-engine" << "stoken" <<
		"-engctrl" << QString("user_pin:").append(mUserPin) << "-keyform" << "ENGINE" <<
		"-revoke" << cert->fileName();
#else
	mOpensslArgs << "ca" << "-config" <<  mConfFileName << "-engine" << "stokensoft" <<
		"-engctrl" << QString("user_pin:").append(mUserPin) << "-keyform" << "ENGINE" <<
		"-revoke" << cert->fileName();
#endif
	
	if (!runOpenssl())
	{
		QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
			QObject::tr("Cannot revoke certificate."), mOpensslError);
		return false;
	}
	
	// Mark that CRL is not actual.
	if (mCrlIsActual)
	{
		mCrlIsActual = false;
		
		QFile crlFile(mConfDirPath + QDir::separator() + mCrlFileName);
		
		if (!crlFile.open(QIODevice::ReadOnly | QIODevice::Text))
		{
			QMessageBox::critical(parent, QObject::tr("Error"), 
				QObject::tr("Cannot load CRL list."));
			return false;
		}
		
		QByteArray crlContents = crlFile.readAll();
		crlFile.close();
		
		if (!crlFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
		{
			QMessageBox::critical(parent, QObject::tr("Error"), 
				QObject::tr("Cannot write data to CRL list file."));
			return false;
		}
		
		crlContents.prepend("#IsNotActual\n");
		crlFile.write(crlContents);
		crlFile.close();
	}
	
	// Update.
	update();
	
	return true;
}

bool
CA::crlNew(QWidget *parent)
{
	QFile crlSerialFile(mConfDirPath + QDir::separator() + mCrlSerialFileName);
	
	if (!crlSerialFile.open(QIODevice::ReadOnly | QIODevice::Text))
	{
		QMessageBox::critical(parent, QObject::tr("Error"), 
			QObject::tr("Cannot open file '%1'.").arg(mCrlSerialFileName));
		return false;
	}
	
	QString crlArchiveFileName = mCrlDirName + QDir::separator() + QString(crlSerialFile.readLine());
	crlArchiveFileName.remove(QChar('\n'));
	crlArchiveFileName.append(QString(".crl"));
	
	crlSerialFile.close();
	
	// Setup OpenSSL arguments for new CRL generation.
	mOpensslArgs.clear();
#ifndef STOKEN_SOFT
	mOpensslArgs << "ca" << "-config" <<  mConfFileName << "-gencrl" << "-engine" <<
		"stoken" << "-engctrl" << QString("user_pin:").append(mUserPin) <<
		"-keyform" << "ENGINE" << "-out" << mCrlFileName;
#else
	mOpensslArgs << "ca" << "-config" <<  mConfFileName << "-gencrl" << "-engine" <<
		"stokensoft" << "-engctrl" << QString("user_pin:").append(mUserPin) <<
		"-keyform" << "ENGINE" << "-out" << mCrlFileName;
#endif
	
	if (!runOpenssl())
	{
		QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
			QObject::tr("Cannot create new CRL list."), mOpensslError);
		return false;
	}
	
	// Copy CRL file to archive.
	QFile crlFile(mConfDirPath + QDir::separator() + mCrlFileName);
	QFile crlArchiveFile(mConfDirPath + QDir::separator() + crlArchiveFileName);
	
	crlFile.open(QIODevice::ReadOnly);
	crlArchiveFile.open(QIODevice::WriteOnly);
	
	crlArchiveFile.write(crlFile.readAll());
	
	crlArchiveFile.close();
	crlFile.close();
	
	// change new CRL file premissions
	crlFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser |
		QFile::WriteUser | QFile::ReadGroup | QFile::WriteGroup | QFile::ReadOther |
		QFile::WriteOther);
	// change archived CRL file premissions
	crlArchiveFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser |
		QFile::WriteUser | QFile::ReadGroup | QFile::WriteGroup | QFile::ReadOther |
		QFile::WriteOther);
	
	return true;
}

bool
CA::crlSaveAs(QWidget *parent)
{
	// Get destination CRL file path.
	QString crlFilePath(QDir::homePath() + QDir::separator() + mCrlFileName);
	
	crlFilePath = FileDialogWPR::getSaveFileName(parent, QObject::tr("Save CRL list as"), 
		crlFilePath, QObject::tr("Revoked certificates list (*.crl)"));
	
	if (crlFilePath.isEmpty())
		return false;
	
	// Save certificate.
	QFile crlFile(crlFilePath);
	QFile crlLocFile(mConfDirPath + QDir::separator() + mCrlFileName);
		
	if (!crlLocFile.open(QIODevice::ReadOnly))
	{
		QMessageBox::critical(parent, QObject::tr("Error"), 
			QObject::tr("Cannot open file '%1'.").arg(mCrlFileName));
		return false;
	}
		
	crlFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
	
	QByteArray crlContents = crlLocFile.readAll();
	crlContents.remove(0, crlContents.indexOf("-----BEGIN X509 CRL-----"));
	crlFile.write(crlContents);
	
	crlLocFile.close();
	crlFile.close();
	
	// change permissions of saved CRL file
	crlFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser |
		QFile::WriteUser | QFile::ReadGroup | QFile::WriteGroup | QFile::ReadOther |
		QFile::WriteOther);
	
	return true;
}

bool
CA::crlDistribute(QWidget *parent)
{
	if (mCaOptions.ftpConfig.mAccounts.size() == 0)
	{
		QMessageBox::information(parent, QObject::tr("Information"),
			QObject::tr("Please configure FTP server list in 'CA Settings' first."));
		return false;
	}
	
	if (mCaOptions.ftpConfig.getActiveCount() == 0)
	{
		QMessageBox::information(parent, QObject::tr("Information"),
			QObject::tr("No active servers to send CRL list."));
		return false;
	}
	
	QFile crlLocFile(mConfDirPath + QDir::separator() + mCrlFileName);
	
	if (!crlLocFile.open(QIODevice::ReadOnly))
	{
		QMessageBox::critical(parent, QObject::tr("Error"), 
			QObject::tr("Cannot open file '%1'.").arg(mCrlFileName));
		return false;
	}
	
	QByteArray crlContents = crlLocFile.readAll();
	crlContents.remove(0, crlContents.indexOf("-----BEGIN X509 CRL-----"));
	crlLocFile.close();
	
	FtpDistDialog dlg(&mCaOptions.ftpConfig, &crlContents, parent);
	dlg.exec();
	
	return true;
}

bool
CA::config(int page, QWidget *parent)
{
	CaOptionsDialog *dialog = new CaOptionsDialog(parent);
	dialog->setWindowTitle(QObject::tr("CA settings"));
	dialog->setStartPage(page);
	
	// write xml config
	dialog->setData(mCaOptions);
	
	if (dialog->exec() == QDialog::Accepted)
	{
		dialog->getData(mCaOptions);
		writeXmlConfig();
	}
	
	return true;
}

bool
CA::certSend(bool lastSigned, QWidget *parent)
{
	Certificate *cert = 0;
	Certificate *caCert = &mCertList.first();
	
	if (lastSigned)
		cert = &mCertList.last();
	else
		cert = static_cast<Certificate*>(mCertTreeWidget->currentItem());
	
	if (!cert)
	{
		QMessageBox::information(parent, QObject::tr("Information"), 
			QObject::tr("Select certificate to be send."));
		return false;
	}
	
	if (!mCaOptions.mailConfig.checkOutgoingConfig())
	{
		QMessageBox::information(parent, QObject::tr("Information"), 
			QObject::tr("Sending certificates requires mail account configured."));
		
		return false;
	}
	
	QString emailAddress = cert->emailAddress();
	
	// ask for e-mail for CA certificate
	if (!lastSigned && cert == caCert)
	{
		emailAddress.clear();
		bool ok = true;
		
		while (emailAddress.isEmpty() && ok)
		{
			emailAddress = QInputDialog::getText(parent, QObject::tr("Enter e-mail address"),
				QObject::tr("E-mail:"), QLineEdit::Normal, QString(), &ok);
		}
		
		if (!ok)
			return false;
	}
	else if (emailAddress.isEmpty()) // e-mail not found in request
	{
		QMessageBox::information(parent, QObject::tr("Information"), 
			QObject::tr("There is no e-mail address in request."));
		return false;
	}
	
	SendMailDialog dlg(parent);
	QtSmtp *smtp = dlg.getSmtp();
	
	smtp->beginNewMail();
	smtp->setMsgId("sToken_certificate");
	smtp->setServer(mCaOptions.mailConfig.mSmtpServer);
	smtp->setSender(mCaOptions.mailConfig.mMail, mCaOptions.mailConfig.mUser);
	smtp->addRecipient(emailAddress);
	
	if (mCaOptions.mailConfig.mOutgoingAuth)
	{
		if (mCaOptions.mailConfig.mUseIncomming)
		{
			smtp->setUserPassword(mCaOptions.mailConfig.mPop3User,
				mCaOptions.mailConfig.mPop3Pass);
		}
		else
		{
			smtp->setUserPassword(mCaOptions.mailConfig.mSmtpUser,
				mCaOptions.mailConfig.mSmtpPass);
		}
	}
	
	// Get certificate data
	QString fileName;
	QByteArray certContents;
	
	if (!certGetData(cert, certContents, fileName, parent))
		return false;
	
	smtp->addAttachment(certContents, fileName);
	
	// add CA certificate
	if (caCert != cert)
	{
		if (!certGetData(caCert, certContents, fileName, parent))
			return false;
		
		smtp->addAttachment(certContents, fileName);
		smtp->setMessage(QObject::tr("This message contains User and CA certificates."));
		smtp->setSubject(QObject::tr("User and CA certificates"));
	}
	else
	{
		smtp->setMessage(QObject::tr("This message contains User certificate."));
		smtp->setSubject(QObject::tr("User certificate"));
	}
	
	smtp->sendMail();
	dlg.exec();
	
	return true;
}

bool
CA::certGetData(Certificate *cert, QByteArray &certContents, QString &fileName, QWidget *parent)
{
	// Get destination certificate file path.
	fileName = (cert->commonName().toLower());
	fileName.replace(QRegExp("\\s+"), QString("_"));
	fileName.append(QString(".cert"));
	
	QFile certLocFile(mConfDirPath + QDir::separator() + cert->fileName());
	
	if (!certLocFile.open(QIODevice::ReadOnly))
	{
		QMessageBox::critical(parent, QObject::tr("Error"), 
			QObject::tr("Cannot open file '%1'.").arg(cert->fileName()));
		return false;
	}
		
	certContents = certLocFile.readAll();
	certContents.remove(0, certContents.indexOf("-----BEGIN CERTIFICATE-----"));
	certLocFile.close();
	
	return true;
}

QList<CA>
CA::findAll(QString path)
{
	QList<CA> caList;
	QStringList caNames(QDir(path).entryList(QDir::Dirs | QDir::NoDotAndDotDot));
	
	for (int i = 0; i < caNames.size(); i++)
	{
		CA ca;
		ca.setName(caNames[i]);
		
		if (ca.readConfiguration())
			caList.append(ca);
	}
	
	return caList;
}

bool
CA::importCA(const QStringList &caNames, QWidget *parent)
{
	bool result = true;
	
	QString xmlFilePath = FileDialogWPR::getOpenFileName(parent, QObject::tr("CA import"),
		QString(), QObject::tr("XML Files (*.xml);;All files (*)"));
	
	if(!xmlFilePath.isEmpty())
	{
		QString name;
		result = DirectoryArchiver::checkFile(xmlFilePath, name);
		
		if(false == result || name.isEmpty())
		{
			QMessageBox::critical(parent, QObject::tr("Error"),
				QObject::tr("This is not archived CA file."));
		}
		else
		{
			if(caNames.contains(name, Qt::CaseInsensitive))
			{
				bool ok = true;;
				QString newName = name;
				
				while(name.compare(newName, Qt::CaseInsensitive) == 0 && ok)
				{
					newName = QInputDialog::getText(parent, QObject::tr("CA already exists"),
						QObject::tr("Enter new CA name"), QLineEdit::Normal, name, &ok);
				}
				
				if(!ok)
					return false; // cancel
				
				result = DirectoryArchiver::unarchive(QDir::current().path(), xmlFilePath, newName);
				if(result)
				{
					QString caDir = QDir::current().path() + QDir::separator() + newName + 
						QDir::separator();
					QFile::rename(caDir + name + ".conf", caDir + newName + ".conf");
				}
			}
			else
			{
				result = DirectoryArchiver::unarchive(QDir::current().path(), xmlFilePath);
			}
			
			if(result)
			{
				QMessageBox::information(parent, QObject::tr("Information"),
					QObject::tr("CA is imported properly."));
			}
			else
			{
				QMessageBox::critical(parent, QObject::tr("Error"),
					QObject::tr("Cannot import CA."));
			}
		}
	}
	
	return result;
}

bool
CA::exportCA(const QStringList &caNames, QWidget *parent)
{
	bool result;
	QString name = QInputDialog::getItem(parent, QObject::tr("Export"), QObject::tr("Select CA"), 
		caNames, 0, false, &result);
		
	if (result && (0 < caNames.size()))
	{
		QString defaultName(name);
		defaultName.replace(QRegExp("\\s+"), QString("_"));
		defaultName.append(QString(".xml"));
		defaultName.prepend(QDir::homePath() + QDir::separator());
		
		QString xmlFilePath = FileDialogWPR::getSaveFileName(parent, QObject::tr("CA export"),
			defaultName, QObject::tr("XML files (*.xml)"));
		
		if(!xmlFilePath.isEmpty())
		{
			result = DirectoryArchiver::archive(QDir::current().path() + QDir::separator() + name,
				xmlFilePath, name);
			
			if(result)
			{
				// change permission of archive file
				QFile::setPermissions(xmlFilePath, QFile::ReadOwner | QFile::WriteOwner |
					QFile::ReadUser | QFile::WriteUser | QFile::ReadGroup |
					QFile::WriteGroup | QFile::ReadOther | QFile::WriteOther);
				
				QMessageBox::information(parent, QObject::tr("Information"),
					QObject::tr("CA is exported properly."));
			}
			else
			{
				QMessageBox::critical(parent, QObject::tr("Error"),
					QObject::tr("Cannot export CA."));
			}
		}
	}
	
	return result;
}

static void
rmdir(QDir &parent, QDir &dir)
{
	if (!dir.exists())
		return;
	
	QStringList subDirNames = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
	
	for (int i = 0; i < subDirNames.size(); i++)
	{
		QDir subDir(dir.absolutePath() + QDir::separator() + subDirNames[i]);
		rmdir(dir, subDir);
	}
	
	QStringList fileNames = dir.entryList(QDir::Files);
	
	for (int i = 0; i < fileNames.size(); i++)
		dir.remove(fileNames[i]);
	
	parent.rmdir(dir.dirName());
}

void
CA::createConfDir()
{
	QDir rootDir(QDir::current());
	QDir confDir(mConfDirPath);
	
	if (confDir.exists())
		rmdir(rootDir, confDir);
	
	rootDir.mkdir(confDir.dirName());
	confDir.mkdir(mCertsDirName);
	confDir.mkdir(mNewCertsDirName);
	confDir.mkdir(mCrlDirName);
	confDir.mkdir(mPrivateDirName);
	confDir.mkdir(mReqDirName);
	confDir.mkdir(mNewReqDirName);
#ifdef STOKEN_SOFT	
	confDir.mkdir(mCaKeyDir);
	// change permissions of created key directory
	QFile::setPermissions(mCaKeyDir, QFile::ReadUser | QFile::ReadOwner);
#endif
	
	QFile file;
	QStringList fileNames;
	
	fileNames << mConfFileName << mXmlConfFileName << mDbFileName << 
		mCaCertFileName << mCrlFileName << mCertSerialFileName << 
		mCrlSerialFileName;
	
	for (int i = 0; i < fileNames.size(); i++)
	{
		file.setFileName(confDir.path() + QDir::separator() + fileNames[i]);
		file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
		
		if ((fileNames[i] == mCertSerialFileName)
			|| (fileNames[i] == mCrlSerialFileName))
		{
			file.write(QByteArray("01"));
		}
		
		file.close();
	}
}

void
CA::deleteConfDir()
{
	QDir rootDir(QDir::current());
	QDir confDir(mConfDirPath);
	
	if (confDir.exists())
		rmdir(rootDir, confDir);
}

bool
CA::readConfiguration()
{
	QFile confFile(mConfDirPath + QDir::separator() + mConfFileName);
	
	// Read OpenSSL CA configuration.
	if (!confFile.open(QIODevice::ReadOnly | QIODevice::Text))
		return false;
	
	bool caPolicy = false;
	
	while (!confFile.atEnd())
	{
		QString line(confFile.readLine());
		line.remove(QRegExp("\\s+"));
		
		if (line == QString("[CA_policy]"))
			caPolicy = true;
		else if (line.indexOf(QChar('[')) != -1)
			caPolicy = false;
		
		QStringList list = line.split(QChar('='));
		
		if (list.size() < 2)
			continue;
		
		if (list[0] == QString("certs"))
			mCertsDirName = list[1].remove(0, list[1].lastIndexOf(QChar('/')) + 1);
		else if (list[0] == QString("new_certs_dir"))
			mNewCertsDirName = list[1].remove(0, list[1].lastIndexOf(QChar('/')) + 1);
		else if (list[0] == QString("crl_dir"))
			mCrlDirName = list[1].remove(0, list[1].lastIndexOf(QChar('/')) + 1);
		else if (list[0] == QString("database"))
			mDbFileName = list[1].remove(0, list[1].lastIndexOf(QChar('/')) + 1);
		else if (list[0] == QString("private_key"))
		{
			mCaKeyName = list[1];
			mSTokenName = mCaKeyName;
			mSTokenName.remove(mSTokenName.indexOf('/'), mSTokenName.size());
			mSTokenSerial = mSTokenName;
			mSTokenName.remove(mSTokenName.lastIndexOf(':'), mSTokenName.size());
			mSTokenSerial.remove(0, mSTokenSerial.lastIndexOf(':') + 1);
		}
		else if (list[0] == QString("certificate"))
			mCaCertFileName = list[1].remove(0, list[1].lastIndexOf(QChar('/')) + 1);
		else if (list[0] == QString("serial"))
			mCertSerialFileName = list[1].remove(0, list[1].lastIndexOf(QChar('/')) + 1);
		else if (list[0] == QString("crl"))
			mCrlFileName = list[1].remove(0, list[1].lastIndexOf(QChar('/')) + 1);
		else if (list[0] == QString("crlnumber"))
			mCrlSerialFileName = list[1].remove(0, list[1].lastIndexOf(QChar('/')) + 1);
		else if (list[0] == QString("default_days"))
			mDefaultCertDays = list[1].toInt();
		else if (list[0] == QString("default_crl_days"))
			mDefaultCrlDays = list[1].toInt();
		else if (list[0] == QString("default_md"))
			mDefaultMD = list[1];
		else if (list[0] == QString("countryName") && caPolicy)
			mPolicyCountryName = list[1];
		else if (list[0] == QString("stateOrProvinceName") && caPolicy)
			mPolicyStateOrProvinceName = list[1];
		else if (list[0] == QString("localityName") && caPolicy)
			mPolicyLocalityName = list[1];
		else if (list[0] == QString("organizationName") && caPolicy)
			mPolicyOrganizationName = list[1];
		else if (list[0] == QString("organizationalUnitName") && caPolicy)
			mPolicyOrganizationalUnitName = list[1];
		else if (list[0] == QString("commonName") && caPolicy)
			mPolicyCommonName = list[1];
		else if (list[0] == QString("emailAddress") && caPolicy)
			mPolicyEmailAddress = list[1];
	}
	
	confFile.close();
	
	// Read configuration from XML file.
	confFile.setFileName(mConfDirPath + QDir::separator() + mXmlConfFileName);
	
	if (!confFile.open(QIODevice::ReadOnly | QIODevice::Text))
		return false;
	
	confFile.close();
	
	return true;
}

bool
CA::writeConfiguration()
{
	QByteArray conf;
	
	conf.append("[ ca ]\n\n");
	conf.append("default_ca = CA_configuration\n\n");
	
	conf.append("[ CA_configuration ]\n\n");
	conf.append("dir = .\n");
	conf.append("certs = $dir/").append(mCertsDirName).append("\n");
	conf.append("new_certs_dir = $dir/").append(mNewCertsDirName).append("\n");
	conf.append("crl_dir = $dir/").append(mCrlDirName).append("\n");
	conf.append("database = $dir/").append(mDbFileName).append("\n");
#ifndef STOKEN_SOFT
	conf.append("private_key = ").append(mCaKeyName).append("\n");
#else
	conf.append("private_key = $dir/key/").append(mCaKeyName).append("\n");
#endif
	conf.append("certificate = $dir/").append(mCaCertFileName).append("\n");
	conf.append("serial = $dir/").append(mCertSerialFileName).append("\n");
	conf.append("crl = $dir/").append(mCrlFileName).append("\n");
	conf.append("crlnumber = $dir/").append(mCrlSerialFileName).append("\n");
	conf.append("RANDFILE = $dir/private/.rand\n");
	conf.append("x509_extensions = usr_cert\n");
	conf.append("name_opt = ca_default\n");
	conf.append("cert_opt = ca_default\n");
	conf.append("default_days = ").append(QString("%1\n").arg(mDefaultCertDays));
	conf.append("default_crl_days = ").append(QString("%1\n").arg(mDefaultCrlDays));
	conf.append("default_md = ").append(mDefaultMD).append("\n");
	conf.append("preserve = no\n");
	conf.append("policy = CA_policy\n\n");
	
	conf.append("[ CA_policy ]\n\n");
	conf.append("countryName = ").append(mPolicyCountryName).append("\n");
	conf.append("stateOrProvinceName = ").append(mPolicyStateOrProvinceName).append("\n");
	conf.append("localityName = ").append(mPolicyLocalityName).append("\n");
	conf.append("organizationName = ").append(mPolicyOrganizationName).append("\n");
	conf.append("organizationalUnitName = ").append(mPolicyOrganizationalUnitName).append("\n");
	conf.append("commonName = ").append(mPolicyCommonName).append("\n");
	conf.append("emailAddress = ").append(mPolicyEmailAddress).append("\n\n");
	
	conf.append("[ usr_cert ]\n\n");
	conf.append("basicConstraints = CA:false\n");
	conf.append("nsComment = \"OpenSSL Generated Certificate\"\n");
	conf.append("subjectKeyIdentifier = hash\n");
	conf.append("authorityKeyIdentifier = keyid, issuer\n\n");
	
	conf.append("[ v3_ca ]\n\n");
	conf.append("basicConstraints = CA:true\n");
	conf.append("subjectKeyIdentifier = hash\n");
	conf.append("authorityKeyIdentifier = keyid:always, issuer:always\n\n");
	
	conf.append("[ crl_ext ]\n\n");
	conf.append("authorityKeyIdentifier = keyid:always, issuer:always\n\n");
	
	conf.append("[ req ]\n\n");
	conf.append("default_bits = 1024\n");
	conf.append("default_keyfile = ").append(mCaKeyName).append("\n");
	conf.append("default_md = ").append(mDefaultMD).append("\n");
	conf.append("distinguished_name = req_distinguished_name\n");
	conf.append("attributes = req_attributes\n");
	conf.append("x509_extensions = v3_ca\n");
	conf.append("string_mask = nombstr\n\n");
	
	conf.append("[ req_distinguished_name ]\n\n");
	conf.append("countryName = Country Name (2 letter code)\n");
	conf.append("countryName_default = PL\n");
	conf.append("countryName_min = 2\n");
	conf.append("countryName_max = 2\n");
	conf.append("stateOrProvinceName = State or Province Name (full name)\n");
	conf.append("stateOrProvinceName_default = Some-State\n");
	conf.append("localityName = Locality Name (eg, city)\n");
	conf.append("0.organizationName = Organization Name (eg, company)\n");
	conf.append("0.organizationName_default = Internet Widgits Pty Ltd\n");
	conf.append("organizationalUnitName = Organizational Unit Name (eg, section)\n");
	conf.append("commonName = Common Name (eg, YOUR name)\n");
	conf.append("commonName_max = 64\n");
	conf.append("emailAddress = Email Address\n\n");
	conf.append("emailAddress_max = 64\n\n");
	
	conf.append("[ req_attributes ]\n");
	conf.append("challengePassword = A challenge password\n");
	conf.append("challengePassword_min = 4\n");
	conf.append("challengePassword_max = 20\n");
	conf.append("unstructuredName = An optional company name\n\n");
	
	QFile confFile(mConfDirPath + QDir::separator() + mConfFileName);
	
	if (!confFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
		return false;
	
	confFile.write(conf);
	confFile.close();
	
	return true;
}

bool
CA::writeXmlConfig()
{
	QFile file(mConfDirPath + QDir::separator() + mXmlConfFileName);
	
	if (file.open(QIODevice::WriteOnly))
	{
		mCaOptions.serialize(file);
		file.close();
	}
	
	return true;
}

bool
CA::readXmlConfig()
{
	QFile file(mConfDirPath + QDir::separator() + mXmlConfFileName);
	
	if (file.open(QIODevice::ReadOnly))
	{
		mCaOptions.deserialize(file);
		file.close();
	}
	
	return true;
}

bool
CA::readCaCrl()
{
	// Read CRL dates.
	mOpensslArgs.clear();
	mOpensslArgs << "crl" << "-in" << mCrlFileName << "-noout" << "-lastupdate" <<
		"-nextupdate";
	
	if (!runOpenssl())
		return false;
	
	QStringList crlDateTime(mOpensslOut.split(QChar('\n')));
	
	mCrlLastUpdate = readOpensslDateTime(crlDateTime[0]);
	mCrlNextUpdate = readOpensslDateTime(crlDateTime[1]);
	
	// Read CRL file contents.
	QFile crlFile(mConfDirPath + QDir::separator() + mCrlFileName);
	
	if (!crlFile.open(QIODevice::ReadOnly | QIODevice::Text))
		return false;
	
	if (crlFile.readAll().contains("#IsNotActual"))
		mCrlIsActual = false;
	else
		mCrlIsActual = true;
	
	crlFile.close();
	
	// Check next update time.
	if (mCrlNextUpdate < QDateTime::currentDateTime().toUTC())
		mCrlIsActual = false;
	
	return true;
}

bool
CA::readCertificateDb()
{
	// Clear certificate list.
	mCertList.clear();
	
	// Read data from CA certificate.
	mOpensslArgs.clear();
	mOpensslArgs << "x509" << "-in" << mCaCertFileName << "-noout" << "-startdate" <<
		"-enddate" << "-subject";
	
	if (!runOpenssl())
		return false;
	
	QStringList caCertFields(mOpensslOut.split(QChar('\n')));
	caCertFields[2].remove(0, caCertFields.indexOf(QChar('/')));
	
	Certificate caCert;
	
	caCert.setSerial(0);
	caCert.setFileName(mCaCertFileName);
	caCert.setNotBefore(readOpensslDateTime(caCertFields[0]).toString(QString("yyMMddhhmmss")));
	caCert.setNotAfter(readOpensslDateTime(caCertFields[1]).toString(QString("yyMMddhhmmss")));
	caCert.setSubject(caCertFields[2]);
	caCert.setState(QString("V"));
	caCert.setIcon(0, QIcon(QString(":img/cacert.png")));
	
	// Read all data from CA certificate.
	mOpensslArgs.clear();
	mOpensslArgs << "x509" << "-in" << mCaCertFileName << "-noout" << "-text";
	
	if (!runOpenssl())
		return false;
	
	caCert.setText(mOpensslOut);
	
	mCertList.append(caCert);
	
	// Read user certificates.
	QFile db(mConfDirPath + QDir::separator() + mDbFileName);
	
	if (!db.open(QIODevice::ReadOnly | QIODevice::Text))
		return false;
	
	while (!db.atEnd())
	{
		Certificate cert;
		
		QString line = QString::fromUtf8(db.readLine().data());
		line.remove(QChar('\n'));
		QStringList fields = line.split(QRegExp("\\t+"));
		
		cert.setState(fields[0]);
		cert.setNotAfter(fields[1]);
		
		if (fields[0] == QString("V"))
		{
			cert.setSerial(fields[2]);
			cert.setSubject(fields[4]);
		}
		else
		{
			QStringList list = fields[2].split(QChar(','));
			
			if (0 < list.count())
				cert.setRevocationDate(list[0]);
			
			if (1 < list.count())
				cert.setRevocationReason(list[1]);
			
			cert.setSerial(fields[3]);
			cert.setSubject(fields[5]);
		}
		
		cert.setFileName(mNewCertsDirName + QDir::separator() + cert.fileName());
		
		// Read all data from certificate.
		mOpensslArgs.clear();
		mOpensslArgs << "x509" << "-in" << cert.fileName() << "-noout" << "-text";
		
		if (!runOpenssl())
			return false;
		
		cert.setText(mOpensslOut);
		
		mCertList.append(cert);
	}
	
	return true;
}

bool
CA::readNewRequests(QWidget *parent)
{
	QDir newReqDir(mConfDirPath + QDir::separator() + mNewReqDirName);
	QStringList requests = newReqDir.entryList(QDir::Files);
	
	mReqList.clear();
	
	for (int i = 0; i < requests.size(); i++)
	{
		Request req;
		
		mOpensslArgs.clear();
		mOpensslArgs << "req" << "-config" << mConfFileName << "-in" << mNewReqDirName +
			QDir::separator() + requests[i] << "-noout" << "-verify" << "-subject";
		
		if (!runOpenssl() || mOpensslError.contains(QString("verify failure")))
		{
			QDetailsMessageBox::critical(parent, QObject::tr("Error"), 
				QObject::tr("Request %1 has incorrect format or sign and will "
				"be removed from CA database.").arg(requests[i]), mOpensslError);
			newReqDir.remove(requests[i]);
			continue;
		}
		
		mOpensslOut.remove(QString("subject="));
		mOpensslOut.remove(QChar('\n'));
		
		req.setSubject(mOpensslOut);
		req.setFileName(requests[i]);
		
		mOpensslArgs.clear();
		mOpensslArgs << "req" << "-config" << mConfFileName << "-in" << mNewReqDirName +
			QDir::separator() + requests[i] << "-noout" << "-text";
		
		if (!runOpenssl())
		{
			QDetailsMessageBox::critical(parent, QObject::tr("Error"), QObject::tr("Request %1 "
				"has incorrect format or sign and will be removed from CA database.").arg(requests[i]), 
				mOpensslError);
			newReqDir.remove(requests[i]);
			continue;
		}
		
		req.setText(mOpensslOut);
		
		mReqList.append(req);
	}
	
	return true;
}

bool
CA::getUserPin(QWidget *parent)
{
	// Get User PIN to generate self signed self signad CA certificate.
	bool ok;
	mUserPin = QInputDialog::getText(parent, QObject::tr("Enter password"), QObject::tr("Password:"), 
		QLineEdit::Password, QString(), &ok);
	
	if (!ok)
		return false;
	
#ifndef STOKEN_SOFT
	int status;
	
	// Check if PIN is correct.
	if (((status = CRD_UT10XX_lib_init()) < 0) || ((status = CRD_UT10XX_lib_find_devices()) < 0)
		|| ((status = CRD_UT10XX_lib_actualize_devices()) < 0))
	{
		QDetailsMessageBox::critical(parent, QObject::tr("Error"), QObject::tr("Cannot "
			"initialize token library!"), CRD_UT10XX_lib_error_desc(status));
		mUserPin.clear();
		qApp->quit();
	}
	
	CRD_UT10XX_t *ut = CRD_UT10XX_find_by_serial_number(mSTokenSerial.toUtf8().data());
	
	if (!ut || (mSTokenName != QString(ut->info.name)))
	{
		QMessageBox::critical(parent, QObject::tr("Token not found"), QObject::tr("Cannot find "
			"token. Check if %1 token is connected.").arg(mSTokenName));
		CRD_UT10XX_lib_finish();
		return false;
	}
	
	if (sTokenState(ut) != StateConf)
	{
		if (sTokenState(ut) == StateLock)
		{
			QMessageBox::critical(parent, QObject::tr("Error"), QObject::tr("Token %1 is "
				"locked.").arg(mSTokenName));
		}
		else
		{
			QMessageBox::critical(parent, QObject::tr("Error"), QObject::tr("Token %1 is "
				"not initialized.").arg(mSTokenName));
		}
		
		CRD_UT10XX_lib_finish();
		return false;
	}
	
	CRD_UT10XX_session_open(ut);
	
	if (CRD_UT10XX_cmd_try_pin(ut, mUserPin.toUtf8().data(), CRD_UT10XX_USER_PIN_ID) < 0)
	{
		CRD_UT10XX_cmd_read_dev_info(ut);
		QMessageBox::critical(parent, QObject::tr("Error"),
			QObject::tr("Incorrect password.<br>Number of wrong entered password: <b>%1</b> "
			"(limit %2).").arg(ut->info.user_pin_trys).arg(ut->info.user_pin_limit));
		CRD_UT10XX_session_close(ut);
		CRD_UT10XX_lib_finish();
		return false;
	}
	
	CRD_UT10XX_session_close(ut);
	CRD_UT10XX_lib_finish();
#endif
	
	return true;
}

#ifdef STOKEN_SOFT
bool
CA::generatePIN(QWidget *parent)
{
	bool ok;
	QString pin1, pin2;
	
	do
	{
		pin1 = QInputDialog::getText(parent, QObject::tr("Enter password for CA's private key"),
			QObject::tr("Password:"), QLineEdit::Password, QString(), &ok);
		if (!ok)
			return false;
		
		if(pin1.isEmpty())
			QMessageBox::warning(parent, QObject::tr("Information"),
				QObject::tr("Password for key cannot be empty."));
	}
	while(pin1.isEmpty());
	
	pin2 = QInputDialog::getText(parent, QObject::tr("Re-enter password"),
		QObject::tr("Re-entered password:"), QLineEdit::Password, QString(), &ok);
	
	if (!ok)
		return false;
	
	if(pin1 != pin2)
	{
		QMessageBox::information(parent, QObject::tr("Information"), QObject::tr("Entered "
			"passwords are not equal."));
		return false;
	}
	else
	{
		mUserPin = pin1;	
		return true;
	}
}
#endif

void
CA::updateCertificateTree()
{
	if (!mCertTreeWidget)
		return;
	
	while (0 < mCertTreeWidget->topLevelItemCount())
		mCertTreeWidget->takeTopLevelItem(0);
	
	for (int i = 0; i < mCertList.size(); i++)
	{
		if (mCertOnlyValidDisplay && (mCertList[i].state() != QString("V")))
			continue;
		
		mCertTreeWidget->addTopLevelItem(&mCertList[i]);
	}
}

void
CA::updateCertificateInfo()
{
	Certificate *cert = 0;
	
	if (!mCertTreeWidget)
		return;
	
	if (!mCertInfoTextEdit)
		return;
	
	mCertInfoTextEdit->clear();
	mCertInfoTextEdit->setReadOnly(true);
	mCertInfoTextEdit->setLineWrapMode(QTextEdit::NoWrap);
	
	cert = static_cast<Certificate*>(mCertTreeWidget->currentItem());
	
	if (!cert)
		return;
	
	if (cert->revocationDate() != QDateTime())
	{
		mCertInfoTextEdit->append(QObject::tr("Date of revoke:"));
		mCertInfoTextEdit->append(QObject::tr("        %1 GMT").arg(cert->revocationDate().
			toString(QString("yyyy-MM-dd (hh:mm:ss)"))));
	}
	
	mCertInfoTextEdit->append(QObject::tr("Validity date:"));
	mCertInfoTextEdit->append(QObject::tr("        %1 GMT").arg(cert->notAfter().
		toString(QString("yyyy-MM-dd (hh:mm:ss)"))));
	mCertInfoTextEdit->append(QObject::tr("Client data:"));
	mCertInfoTextEdit->append(QObject::tr("        Country: %1").arg(cert->countryName()));
	mCertInfoTextEdit->append(QObject::tr("        Region: %1").arg(cert->stateOrProvinceName()));
	mCertInfoTextEdit->append(QObject::tr("        City: %1").arg(cert->localityName()));
	mCertInfoTextEdit->append(QObject::tr("        Organization: %1").arg(cert->organizationName()));
	mCertInfoTextEdit->append(QObject::tr("        Department: %1").arg(cert->organizationalUnitName()));
	mCertInfoTextEdit->append(QObject::tr("        Common name: %1").arg(cert->commonName()));
	mCertInfoTextEdit->append(QObject::tr("        E-mail: %1").arg(cert->emailAddress()));
	mCertInfoTextEdit->append(QObject::tr("Signature algorithm:"));
	mCertInfoTextEdit->append(QObject::tr("        %1").arg(cert->signatureAlgorithm()));
}

void
CA::updateRequestTree()
{
	if (!mReqTreeWidget)
		return;
	
	while (0 < mReqTreeWidget->topLevelItemCount())
		mReqTreeWidget->takeTopLevelItem(0);
	
	for (int i = 0; i < mReqList.size(); i++)
		mReqTreeWidget->addTopLevelItem(&mReqList[i]);
}

void
CA::updateRequestInfo()
{
	Request *req = 0;
	
	if (!mReqTreeWidget)
		return;
	
	if (!mReqInfoTextEdit)
		return;
	
	mReqInfoTextEdit->clear();
	mReqInfoTextEdit->setReadOnly(true);
	mReqInfoTextEdit->setLineWrapMode(QTextEdit::NoWrap);
	
	req = static_cast<Request*>(mReqTreeWidget->currentItem());
	
	if (!req)
		return;
	
	mReqInfoTextEdit->append(QObject::tr("Subject data:"));
	mReqInfoTextEdit->append(QObject::tr("        Country: %1").arg(req->countryName()));
	mReqInfoTextEdit->append(QObject::tr("        Region: %1").arg(req->stateOrProvinceName()));
	mReqInfoTextEdit->append(QObject::tr("        City: %1").arg(req->localityName()));
	mReqInfoTextEdit->append(QObject::tr("        Organization: %1").arg(req->organizationName()));
	mReqInfoTextEdit->append(QObject::tr("        Department: %1").arg(req->organizationalUnitName()));
	mReqInfoTextEdit->append(QObject::tr("        Common name: %1").arg(req->commonName()));
	mReqInfoTextEdit->append(QObject::tr("        E-mail: %1").arg(req->emailAddress()));
	mReqInfoTextEdit->append(QObject::tr("Signature algorithm:"));
	mReqInfoTextEdit->append(QObject::tr("        %1").arg(req->signatureAlgorithm()));
}

QDateTime
CA::readOpensslDateTime(QString dateTime)
{
	dateTime.remove(QChar('\n'));
	dateTime.remove(QString(" GMT"));
	dateTime.remove(0, dateTime.indexOf(QChar('=')) + 1);
	dateTime.replace(QRegExp("\\s+"), QString(" "));
	
	dateTime.replace(QString("Jan"), QString("1"));
	dateTime.replace(QString("Feb"), QString("2"));
	dateTime.replace(QString("Mar"), QString("3"));
	dateTime.replace(QString("Apr"), QString("4"));
	dateTime.replace(QString("May"), QString("5"));
	dateTime.replace(QString("Jun"), QString("6"));
	dateTime.replace(QString("Jul"), QString("7"));
	dateTime.replace(QString("Aug"), QString("8"));
	dateTime.replace(QString("Sep"), QString("9"));
	dateTime.replace(QString("Oct"), QString("10"));
	dateTime.replace(QString("Nov"), QString("11"));
	dateTime.replace(QString("Dec"), QString("12"));
	
	return QDateTime::fromString(dateTime, QString("M d hh:mm:ss yyyy"));
}

bool
CA::runOpenssl()
{
	QProcess openssl;
	
	openssl.setWorkingDirectory(mConfDirPath);
	openssl.start(mOpensslCmd, mOpensslArgs);
	
	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
	
	while (openssl.state() != QProcess::NotRunning)
		QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
	
	QApplication::restoreOverrideCursor();
	
	mOpensslOut.clear();
	mOpensslError.clear();
	
	mOpensslOut = QString(openssl.readAllStandardOutput());
	mOpensslError = QString(openssl.readAllStandardError());
	
	if (openssl.exitCode() != 0)
		return false;
	
	return true;
}
