/***************************************************************************
 *
 * knetworkmanager-openvpn.cpp - A NetworkManager frontend for KDE 
 *
 * 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 <klocale.h>
#include <qmessagebox.h>
#include <qbutton.h>
#include <kcombobox.h>
#include <klineedit.h>
#include <kurlrequester.h>
#include <qobjectlist.h>
#include <qobject.h>
#include <qcheckbox.h>
#include <kpassdlg.h>
#include <kgenericfactory.h>
#include <qwidgetstack.h>
#include <qfileinfo.h>
#include <qhostaddress.h>
#include <dbus/qdbusdata.h>

#include "knetworkmanager-openvpn.h"

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

/************************************
* OpenVPNPlugin
************************************/
OpenVPNPlugin::OpenVPNPlugin(QObject* parent, const char* name, const QStringList& args)
	: VPNPlugin(parent, name, args)
{

}

OpenVPNPlugin::~OpenVPNPlugin()
{

}

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

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

/************************************
* OpenVPNConnectionType
************************************/
OpenVPNConnectionType::CONNECTIONTYPE OpenVPNConnectionType::mapString2ConnectionType(int prop)
{
	if (prop == 0)
		return X509;
	else if (prop == 1)
		return SHARED_KEY;
	else if (prop == 2)
		return PASSWORD;
	else if (prop == 3)
		return X509USERPASS;
	return UNKNOWN;
}

int OpenVPNConnectionType::mapConnectionType2String(CONNECTIONTYPE connType)
{
	switch(connType)
	{
		case X509:
			return 0;
		case SHARED_KEY:
			return 1;
		case PASSWORD:
			return 2;
		case X509USERPASS:
			return 3;
		default:
			return -1;
	}
	return -1;
}

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

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

	connect(_openvpnWidget->chkUseCipher, SIGNAL(toggled(bool)), _openvpnWidget->cboCipher, SLOT(setEnabled(bool)));
	connect(_openvpnWidget->chkUseTLS, SIGNAL(toggled(bool)), _openvpnWidget->cboDirection, SLOT(setEnabled(bool)));
	connect(_openvpnWidget->chkUseTLS, SIGNAL(toggled(bool)), _openvpnWidget->editTLSAuth, SLOT(setEnabled(bool)));
	connect(_openvpnWidget->chkIPAdresses, SIGNAL(toggled(bool)), _openvpnWidget->routes, 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, SIGNAL( activated(int)), _openvpnWidget->widgetStack, 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"));
}

QString 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 ( QFileInfo(*openvpn_binary).exists())
			break;
		openvpn_binary++;
	}
	
	return *openvpn_binary;
}


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

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

}

void OpenVPNConfig::setVPNData(const QStringList& routes, const QMap<QString, QString>& properties)
{
	// fill up our inputfields 
	for(QMap<QString, QString>::ConstIterator it = properties.begin(); it != properties.end(); ++it)
	{
		QString entry = it.key();
		QString value = it.data();

		if (entry == "connection-type")
		{
			OpenVPNConnectionType::CONNECTIONTYPE type = OpenVPNConnectionType::mapString2ConnectionType(value.toInt());
			_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" || 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 == "true");
		}
		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 == "dev" || entry == "tap-dev") {
			_openvpnWidget->chkUseTAP->setChecked(value == "true");
		}
		else if (entry == "ta")
		{
			_openvpnWidget->chkUseTLS->setChecked(true);
			_openvpnWidget->editTLSAuth->setURL(value);
		}
		else if (entry == "ta-dir")
		{
			_openvpnWidget->cboDirection->setCurrentItem(value);
		}
		else
		{
			kdDebug() << QString("OpenVPN: Property '%1' not handled").arg(entry) << endl;
		}
	}

	// set routes
	if (!routes.empty())
	{
		_openvpnWidget->chkIPAdresses->setChecked(true);
		_openvpnWidget->routes->setText(routes.join(" "));
	}
}

QMap<QString, QString>OpenVPNConfig::getVPNProperties()
{
	// build a StingList of properties
	QMap<QString, QString> strlist;

	strlist.insert("connection-type", QString::number(OpenVPNConnectionType::mapConnectionType2String((OpenVPNConnectionType::CONNECTIONTYPE)_openvpnWidget->cboConnectionType->currentItem())));
	strlist.insert("remote", QString(_openvpnWidget->gateway->text()));

	// port is not necessary
	if (!_openvpnWidget->port->text().isEmpty() && !_openvpnWidget->chkDefaultPort->isChecked())
		strlist.insert("port", _openvpnWidget->port->text());

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

	if (_openvpnWidget->chkUseCipher->isChecked())
		strlist.insert("cipher", QString(_openvpnWidget->cboCipher->currentText()));

	if (_openvpnWidget->chkUseLZO->isChecked())
		strlist.insert("comp-lzo", QString("true"));
	else
		strlist.insert("comp-lzo", QString("false"));

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

	if (_openvpnWidget->chkUseTAP->isChecked()) {
			strlist.insert("tap-dev", "true");
			strlist.insert("proto-tcp", "true");
	} else {
			strlist.insert("tap-dev", "false");
			strlist.insert("proto-tcp", "false");
	}

	if (_openvpnWidget->chkUseTLS->isChecked())
		strlist.insert("ta", QString(_openvpnWidget->editTLSAuth->url()));

	strlist.insert("ta-dir", QString(_openvpnWidget->cboDirection->currentText()));

	return strlist;
}

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

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

bool OpenVPNConfig::isValid(QStringList& 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 (!QFileInfo(_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 (!QFileInfo(_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 (!QFileInfo(_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 (!QFileInfo(_openvpnWidget->editSharedKey->url()).exists())
			{
				retval = false;
				err_msg.append(i18n("Please provide a valid shared key"));
			}
			
			// check if local ip is valid
			if (!QHostAddress().setAddress(_openvpnWidget->editLocalIP->text()))
			{
				retval = false;
				err_msg.append(i18n("local IP is invalid"));
			}
			// check if remote ip is valid
			if (!QHostAddress().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 (!QFileInfo(_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 (!QFileInfo(_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 (!QFileInfo(_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 (!QFileInfo(_openvpnWidget->editKey->url()).isFile())
			{
				retval = false;
				err_msg.append(i18n("Key file not valid"));
			}
			break;
	}


	return retval;
}

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

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

OpenVPNAuthentication::~OpenVPNAuthentication()
{

}

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

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

	return pwds;
}

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

