/***************************************************************************
 *
 * tdenetman-openvpn.cpp - A NetworkManager frontend for TDE
 *
 * Copyright (C) 2006 Novell, Inc.
 *
 * Author: Helmut Schaa <hschaa@suse.de>, <helmut.schaa@gmx.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 **************************************************************************/

#include <tdelocale.h>
#include <tqmessagebox.h>
#include <tqbutton.h>
#include <kcombobox.h>
#include <klineedit.h>
#include <kurlrequester.h>
#include <tqobjectlist.h>
#include <tqobject.h>
#include <tqcheckbox.h>
#include <kpassdlg.h>
#include <kgenericfactory.h>
#include <tqwidgetstack.h>
#include <tqfileinfo.h>
#include <tqhostaddress.h>

#include "tdenetman-openvpn.h"

typedef KGenericFactory<OpenVPNPlugin> OpenVPNPluginFactory;
K_EXPORT_COMPONENT_FACTORY( tdenetman_openvpn, OpenVPNPluginFactory("tdenetman_openvpn"));

/************************************
* OpenVPNPlugin
************************************/
OpenVPNPlugin::OpenVPNPlugin(TQObject* parent, const char* name, const TQStringList& args)
	: VPNPlugin(parent, name, args)
{

}

OpenVPNPlugin::~OpenVPNPlugin()
{

}

VPNConfigWidget* OpenVPNPlugin::CreateConfigWidget(TQWidget* parent)
{
	return new OpenVPNConfig(parent);
}

VPNAuthenticationWidget* OpenVPNPlugin::CreateAuthenticationWidget(TQWidget* parent)
{
	return new OpenVPNAuthentication(parent);
}

/************************************
* OpenVPNConnectionType
************************************/
OpenVPNConnectionType::CONNECTIONTYPE OpenVPNConnectionType::mapString2ConnectionType(TQString prop)
{
	if (prop == "tls")
		return X509;
	else if (prop == "static-key")
		return SHARED_KEY;
	else if (prop == "password")
		return PASSWORD;
	else if (prop == "password-tls")
		return X509USERPASS;
	return UNKNOWN;
}

TQString OpenVPNConnectionType::mapConnectionType2String(CONNECTIONTYPE connType)
{
	switch(connType)
	{
		case X509:
			return "tls";
		case SHARED_KEY:
			return "static-key";
		case PASSWORD:
			return "password";
		case X509USERPASS:
			return "password-tls";
		default:
			return TQString::null;
	}
	return TQString::null;
}

/************************************
* OpenVPNConfig
************************************/

OpenVPNConfig::OpenVPNConfig(TQWidget* parent)
	: VPNConfigWidget(parent)
{
	TQVBoxLayout* layout = new TQVBoxLayout(this, 1, 1);
	_openvpnWidget = new OpenVPNConfigWidget(this);
	layout->addWidget(_openvpnWidget);

	connect(_openvpnWidget->chkUseCipher, TQ_SIGNAL(toggled(bool)), _openvpnWidget->cboCipher, TQ_SLOT(setEnabled(bool)));
	connect(_openvpnWidget->chkUseTLS, TQ_SIGNAL(toggled(bool)), _openvpnWidget->cboDirection, TQ_SLOT(setEnabled(bool)));
	connect(_openvpnWidget->chkUseTLS, TQ_SIGNAL(toggled(bool)), _openvpnWidget->editTLSAuth, TQ_SLOT(setEnabled(bool)));
	connect(_openvpnWidget->chkIPAdresses, TQ_SIGNAL(toggled(bool)), _openvpnWidget->routes, TQ_SLOT(setEnabled(bool)));

	// add all Cipher modes to the Combobox
	getCipherModes();

	// switch to the right configuration interface when selecting the connection type
	connect(_openvpnWidget->cboConnectionType, TQ_SIGNAL( activated(int)), _openvpnWidget->widgetStack, TQ_SLOT(raiseWidget(int)));
	this->languageChange();
}

OpenVPNConfig::~OpenVPNConfig()
{

}

void OpenVPNConfig::languageChange()
{
	_openvpnWidget->cboConnectionType->insertItem(i18n("X.509 Certificates"), OpenVPNConnectionType::X509 );
	_openvpnWidget->cboConnectionType->insertItem(i18n("Pre-shared key") , OpenVPNConnectionType::SHARED_KEY );
	_openvpnWidget->cboConnectionType->insertItem(i18n("Password Authentication") , OpenVPNConnectionType::PASSWORD );
	_openvpnWidget->cboConnectionType->insertItem(i18n("X.509 with Password Authentication") , OpenVPNConnectionType::X509USERPASS );

	_openvpnWidget->cboDirection->insertItem(i18n("none"));
	_openvpnWidget->cboDirection->insertItem(i18n("0"));
	_openvpnWidget->cboDirection->insertItem(i18n("1"));
}

TQString OpenVPNConfig::findOpenVPNBinary()
{
	static const char *openvpn_binary_paths[] =
	{
	  "/usr/sbin/openvpn",
	  "/sbin/openvpn",
	  NULL
	};

	const char  **openvpn_binary = openvpn_binary_paths;

	while (*openvpn_binary != NULL) {
		if ( TQFileInfo(*openvpn_binary).exists())
			break;
		openvpn_binary++;
	}

	return *openvpn_binary;
}


void OpenVPNConfig::receiveCipherData(TDEProcess*, char* buffer, int len)
{
	// add possible cipher modes to the combobox
	TQStringList cipherModes = TQStringList::split("\n", TQString::fromLatin1(buffer, len), false );
	for (TQStringList::ConstIterator it = cipherModes.begin(); it != cipherModes.end(); ++it)
	{
		_openvpnWidget->cboCipher->insertItem((*it));
	}
}

void OpenVPNConfig::getCipherModes()
{
	// get all possible cipher modes
	TQString openvpn = findOpenVPNBinary();
	if (!openvpn.isNull()) {
		TDEProcess* cipherHelper = new TDEProcess();
		cipherHelper->setUseShell(true, "/bin/sh");
		*cipherHelper << TQString::fromLatin1("%1 --show-ciphers | awk '/^[A-Z][A-Z0-9]+-/ { print $1 }'").arg(openvpn);
		connect (cipherHelper, TQ_SIGNAL(receivedStdout(TDEProcess*, char*, int)), this, TQ_SLOT(receiveCipherData(TDEProcess*, char*, int)));
		kdDebug() << "starting openvpn to get cipher modes" << endl;
		if (!cipherHelper->start(TDEProcess::Block, TDEProcess::Stdout)) {
			kdDebug() << "error starting openvpn" << endl;
		}
	}
}

void OpenVPNConfig::setVPNData(TDENetworkSingleRouteConfigurationList& routes, TDENetworkSettingsMap& properties, TDENetworkSettingsMap& secrets)
{
	m_vpnProperties = properties;
	m_vpnSecrets = secrets;

	// fill up our inputfields
	for(TQMap<TQString, TQString>::ConstIterator it = properties.begin(); it != properties.end(); ++it)
	{
		TQString entry = it.key();
		TQString value = it.data();

		if (entry == "connection-type")
		{
			OpenVPNConnectionType::CONNECTIONTYPE type = OpenVPNConnectionType::mapString2ConnectionType(value);
			_openvpnWidget->cboConnectionType->setCurrentItem(type);
			_openvpnWidget->widgetStack->raiseWidget(type);
		}
		else if (entry == "remote")
		{
			_openvpnWidget->gateway->setText(value);
		}
		else if (entry == "port")
		{
			if (value.toInt() > 0)
			{
				_openvpnWidget->port->setText(value);
				_openvpnWidget->chkDefaultPort->setChecked(false);
			}
			else
			{
				_openvpnWidget->chkDefaultPort->setChecked(true);
			}
		}
		else if (entry == "proto")
		{
			_openvpnWidget->chkUseTCP->setChecked(value == "yes");
		}
		else if (entry == "proto-tcp")
		{
			_openvpnWidget->chkUseTCP->setChecked(value == "yes");
		}
		else if (entry == "ca")
		{
			_openvpnWidget->editCA->setURL(value);
		}
		else if (entry == "cert")
		{
			_openvpnWidget->editCert->setURL(value);
		}
		else if (entry == "key")
		{
			_openvpnWidget->editKey->setURL(value);
		}
		else if (entry == "cipher")
		{
			_openvpnWidget->chkUseCipher->setChecked(true);
			_openvpnWidget->cboCipher->setCurrentItem(value);
		}
		else if (entry == "comp-lzo")
		{
			_openvpnWidget->chkUseLZO->setChecked(value == "yes");
		}
		else if (entry == "shared-key" || entry == "static-key")
		{
			_openvpnWidget->editSharedKey->setURL(value);
		}
		else if (entry == "username")
		{
			_openvpnWidget->editUsername->setText(value);
		}
		else if (entry == "local-ip")
		{
			_openvpnWidget->editLocalIP->setText(value);
		}
		else if (entry == "remote-ip")
		{
			_openvpnWidget->editRemoteIP->setText(value);
		}
		else if (entry == "tap-dev") {
			_openvpnWidget->chkUseTAP->setChecked(value == "yes");
		}
		else if (entry == "ta")
		{
			_openvpnWidget->chkUseTLS->setChecked(true);
			_openvpnWidget->editTLSAuth->setURL(value);
		}
		else if (entry == "ta-dir")
		{
			_openvpnWidget->cboDirection->setCurrentItem(value);
		}
		else
		{
			kdDebug() << TQString("OpenVPN: Property '%1' not handled").arg(entry) << endl;
		}
	}

	// set routes
	if (!routes.empty())
	{
		_openvpnWidget->chkIPAdresses->setChecked(true);
		TQStringList routesText;
		for (TDENetworkSingleRouteConfigurationList::Iterator it = routes.begin(); it != routes.end(); ++it) {
			routesText.append(TQString("%1/%2").arg((*it).ipAddress.toString()).arg((*it).networkMask.toCIDRMask()));
		}
		_openvpnWidget->routes->setText(routesText.join(" "));
	}
}

TDENetworkSettingsMap OpenVPNConfig::getVPNProperties()
{
	// Build a list of properties
	m_vpnProperties.insert("connection-type", OpenVPNConnectionType::mapConnectionType2String((OpenVPNConnectionType::CONNECTIONTYPE)_openvpnWidget->cboConnectionType->currentItem()));
	m_vpnProperties.insert("remote", TQString(_openvpnWidget->gateway->text()));

	// port is not necessary
	if (!_openvpnWidget->port->text().isEmpty() && !_openvpnWidget->chkDefaultPort->isChecked()) {
		m_vpnProperties.insert("port", _openvpnWidget->port->text());
	}
	else {
		m_vpnProperties.remove("port");
	}

	m_vpnProperties.insert("ca", TQString(_openvpnWidget->editCA->url()));
	m_vpnProperties.insert("cert",TQString(_openvpnWidget->editCert->url() ));
	m_vpnProperties.insert("key", TQString(_openvpnWidget->editKey->url()));

	if (_openvpnWidget->chkUseCipher->isChecked()) {
		m_vpnProperties.insert("cipher", TQString(_openvpnWidget->cboCipher->currentText()));
	}
	else {
		m_vpnProperties.remove("cipher");
	}

	if (_openvpnWidget->chkUseLZO->isChecked()) {
		m_vpnProperties.insert("comp-lzo", TQString("yes"));
	}
	else {
		m_vpnProperties.insert("comp-lzo", TQString("no"));
	}

	m_vpnProperties.insert("static-key", TQString(_openvpnWidget->editSharedKey->url()));
	m_vpnProperties.insert("username", TQString(_openvpnWidget->editUsername->text()));
	m_vpnProperties.insert("local-ip", TQString(_openvpnWidget->editLocalIP->text()));
	m_vpnProperties.insert("remote-ip", TQString(_openvpnWidget->editRemoteIP->text()));

	if (_openvpnWidget->chkUseTAP->isChecked()) {
		m_vpnProperties.insert("tap-dev", "yes");
	}
	else {
		m_vpnProperties.insert("tap-dev", "no");
	}

	if (_openvpnWidget->chkUseTCP->isChecked()) {
		m_vpnProperties.insert("proto-tcp", "yes");
	}
	else {
		m_vpnProperties.insert("proto-tcp", "no");
	}

	if (_openvpnWidget->chkUseTLS->isChecked()) {
		m_vpnProperties.insert("ta", TQString(_openvpnWidget->editTLSAuth->url()));
	}
	else {
		m_vpnProperties.remove("ta");
	}

	m_vpnProperties.insert("ta-dir", TQString(_openvpnWidget->cboDirection->currentText()));

	return m_vpnProperties;
}

TDENetworkSettingsMap OpenVPNConfig::getVPNSecrets() {
	// Build a list of secrets
	// FIXME

	return m_vpnSecrets;
}

TDENetworkSingleRouteConfigurationList OpenVPNConfig::getVPNRoutes()
{
	TDENetworkSingleRouteConfigurationList ret;
	TQStringList strlist;
	if(_openvpnWidget->chkIPAdresses->isChecked()) {
		strlist = TQStringList::split(" ", _openvpnWidget->routes->text());
	}

	for (TQStringList::Iterator it = strlist.begin(); it != strlist.end(); ++it) {
		TQStringList pieces = TQStringList::split("/", (*it));
		TDENetworkSingleRouteConfiguration routeconfig;
		routeconfig.ipAddress.setAddress(pieces[0]);
		if (pieces.count() > 1) {
			routeconfig.networkMask.fromCIDRMask(pieces[1].toUInt());
		}
		ret.append(routeconfig);
	}

	return ret;
}

bool OpenVPNConfig::hasChanged()
{
	return true;
}

bool OpenVPNConfig::isValid(TQStringList& err_msg)
{
	bool retval = true;

	// check gateway
	if (_openvpnWidget->gateway->text().isEmpty())
	{
		err_msg.append(i18n("You have to specify a gateway"));
		retval = false;
	}

	bool ok = false;
	_openvpnWidget->port->text().toULong(&ok);
	if (!ok && !_openvpnWidget->port->text().isEmpty() )
	{
		err_msg.append(i18n("The port number has to be numeric"));
		retval = false;
	}

	switch(_openvpnWidget->cboConnectionType->currentItem())
	{
		case OpenVPNConnectionType::X509:
			// check if ca file is correct
			if (_openvpnWidget->editCA->url().isEmpty())
			{
				retval = false;
				err_msg.append(i18n("no CA file provided"));
			}
			else if (!TQFileInfo(_openvpnWidget->editCA->url()).isFile())
			{
				retval = false;
				err_msg.append(i18n("CA file not valid"));
			}

			// check if cert file is correct
			if (_openvpnWidget->editCert->url().isEmpty())
			{
				retval = false;
				err_msg.append(i18n("no CERT file provided"));
			}
			else if (!TQFileInfo(_openvpnWidget->editCert->url()).isFile())
			{
				retval = false;
				err_msg.append(i18n("CERT file not valid"));
			}

			// check if key file is correct
			if (_openvpnWidget->editKey->url().isEmpty())
			{
				retval = false;
				err_msg.append(i18n("no Key file provided"));
			}
			else if (!TQFileInfo(_openvpnWidget->editKey->url()).isFile())
			{
				retval = false;
				err_msg.append(i18n("Key file not valid"));
			}

			break;


		case OpenVPNConnectionType::SHARED_KEY:
			// check if a shared key is selected
			if (_openvpnWidget->editSharedKey->url().isEmpty())
			{
				retval = false;
				err_msg.append(i18n("Please provide a valid shared key"));
			}
			// check if the shared key file exists
			else if (!TQFileInfo(_openvpnWidget->editSharedKey->url()).exists())
			{
				retval = false;
				err_msg.append(i18n("Please provide a valid shared key"));
			}

			// check if local ip is valid
			if (!TQHostAddress().setAddress(_openvpnWidget->editLocalIP->text()))
			{
				retval = false;
				err_msg.append(i18n("local IP is invalid"));
			}
			// check if remote ip is valid
			if (!TQHostAddress().setAddress(_openvpnWidget->editRemoteIP->text()))
			{
				retval = false;
				err_msg.append(i18n("remote IP is invalid"));
			}

			break;
		case OpenVPNConnectionType::PASSWORD:
			// check if username is suplied
			if (_openvpnWidget->editUsername->text().isEmpty())
			{
				retval = false;
				err_msg.append(i18n("no username provided"));
			}

			// check if ca file is correct
			if (_openvpnWidget->editCA->url().isEmpty())
			{
				retval = false;
				err_msg.append(i18n("no CA file provided"));
			}
			else if (!TQFileInfo(_openvpnWidget->editCA->url()).isFile())
			{
				retval = false;
				err_msg.append(i18n("CA file not valid"));
			}

			break;


		case OpenVPNConnectionType::X509USERPASS:
			// check if username is suplied
			if (_openvpnWidget->editUsername->text().isEmpty())
			{
				retval = false;
				err_msg.append(i18n("no username provided"));
			}

			// check if ca file is correct
			if (_openvpnWidget->editCA->url().isEmpty())
			{
				retval = false;
				err_msg.append(i18n("no CA file provided"));
			}
			else if (!TQFileInfo(_openvpnWidget->editCA->url()).isFile())
			{
				retval = false;
				err_msg.append(i18n("CA file not valid"));
			}

			// check if cert file is correct
			if (_openvpnWidget->editCert->url().isEmpty())
			{
				retval = false;
				err_msg.append(i18n("no CERT file provided"));
			}
			else if (!TQFileInfo(_openvpnWidget->editCert->url()).isFile())
			{
				retval = false;
				err_msg.append(i18n("CERT file not valid"));
			}

			// check if key file is correct
			if (_openvpnWidget->editKey->url().isEmpty())
			{
				retval = false;
				err_msg.append(i18n("no Key file provided"));
			}
			else if (!TQFileInfo(_openvpnWidget->editKey->url()).isFile())
			{
				retval = false;
				err_msg.append(i18n("Key file not valid"));
			}
			break;
	}


	return retval;
}

/************************************
* OpenVPNAuthentication
************************************/

OpenVPNAuthentication::OpenVPNAuthentication(TQWidget* parent, char* name)
	: VPNAuthenticationWidget(parent, name)
{
	TQVBoxLayout* layout = new TQVBoxLayout(this, 1, 1);
	_openvpnAuth = new OpenVPNAuthenticationWidget(this);
	layout->addWidget(_openvpnAuth);
}

OpenVPNAuthentication::~OpenVPNAuthentication()
{

}

void OpenVPNAuthentication::setVPNData(TDENetworkSingleRouteConfigurationList& /*routes*/, TDENetworkSettingsMap& properties, TDENetworkSettingsMap& secrets)
{
	// find the connection type property
	for(TQMap<TQString, TQString>::ConstIterator it = properties.begin(); it != properties.end(); ++it)
	{
		if (it.key() == "connection-type")
		{
			_connectionType = OpenVPNConnectionType::mapString2ConnectionType(it.data());
			break;
		}
	}
}

TDENetworkSettingsMap OpenVPNAuthentication::getPasswords()
{
	TQMap<TQString, TQString> pwds;
	if ((_connectionType == OpenVPNConnectionType::PASSWORD) || (_connectionType == OpenVPNConnectionType::X509USERPASS))
		pwds.insert("password", _openvpnAuth->editUserPassword->password());
	else
		pwds.insert("no-secret", TQString("true"));

	return pwds;
}

void OpenVPNAuthentication::setPasswords(TDENetworkSettingsMap secrets) {
	if (secrets.contains("password")) {
		_openvpnAuth->editUserPassword->erase();
		_openvpnAuth->editUserPassword->insert(secrets["password"]);
	}
}

bool OpenVPNAuthentication::needsUserInteraction()
{
	if ((_connectionType == OpenVPNConnectionType::PASSWORD) || (_connectionType == OpenVPNConnectionType::X509USERPASS))
		return true;
	return false;
}

#include "tdenetman-openvpn.moc"
