/***************************************************************************
 *   Copyright (C) 2012 by Timothy Pearson                                 *
 *   kb9vqf@pearsoncomputing.net                                           *
 *                                                                         *
 *   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 <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <pwd.h>

#include <tqcheckbox.h>
#include <tqlayout.h>
#include <tqhbox.h>
#include <tqvbox.h>
#include <tqlineedit.h>
#include <tqpainter.h>
#include <tqtooltip.h>
#include <tqfile.h>
#include <tqcursor.h>
#include <tqpushbutton.h>
#include <tqgroupbox.h>
#include <tqheader.h>
#include <tqpixmap.h>
#include <tqbitmap.h>

#include <tdeconfig.h>
#include <khelpmenu.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kpassivepopup.h>
#include <knotifyclient.h>
#include <knuminput.h>
#include <kseparator.h>
#include <tdepopupmenu.h>
#include <kdialogbase.h>
#include <tdeaction.h>
#include <knotifydialog.h>
#include <klineeditdlg.h>
#include <libtdeldap.h>

#include <ksslcertificate.h>

#include <tdehardwaredevices.h>
#include <tdecryptographiccarddevice.h>

#include "configdlg.h"

#include "toplevel.h"
#include "toplevel.moc"

TopLevel::TopLevel() : KSystemTray(), ticketWatch(0), m_refreshTimer(0), m_requestUpdateTimer(0), notifyExpiryMinutes(0)
{
	setBackgroundMode(X11ParentRelative);   // what for?

	TDEConfig *config = tdeApp->config();
	config->setGroup("Kerberos");

	getNewTGTAct = new TDEAction(i18n("&Obtain New Ticket Granting Ticket"), "add_user", 0, this, TQ_SLOT(getNewTGT()), actionCollection(), "getnewtgt");
	getNewSTAct = new TDEAction(i18n("&Obtain New Primary Service Ticket"), "add_user", 0, this, TQ_SLOT(getNewServiceTicket()), actionCollection(), "getnewserviceticket");
	getNewStandardSTAct = new TDEAction(i18n("&Obtain Authenticated Service Ticket"), "add_user", 0, this, TQ_SLOT(getNewServiceTicketWithExistingCreds()), actionCollection(), "getstandardserviceticket");
	destroyAllAct = new TDEAction(i18n("&Destroy All Tickets"), "delete_user", 0, this, TQ_SLOT(destroyAllTickets()), actionCollection(), "destroyall");
	changePasswordAct = new TDEAction(i18n("&Change Kerberos Password"), "edit_user", 0, this, TQ_SLOT(changeKerberosPassword()), actionCollection(), "changekerberospassword");
	confAct = new TDEAction(i18n("&Configure..."), "configure", 0, this, TQ_SLOT(config()), actionCollection(), "configure");

	// create app menu (displayed on right-click)
	menu = new TQPopupMenu();
	connect(menu, TQ_SIGNAL(activated(int)), this, TQ_SLOT(menuAction(int)));

	KHelpMenu* help = new KHelpMenu(this, TDEGlobal::instance()->aboutData(), false);
	TDEPopupMenu* helpMnu = help->menu();

	menu->insertSeparator();
	getNewTGTAct->plug(menu);
	getNewSTAct->plug(menu);
	getNewStandardSTAct->plug(menu);
	destroyAllAct->plug(menu);
	menu->insertSeparator();
	changePasswordAct->plug(menu);
	menu->insertSeparator();
	confAct->plug(menu);
	menu->insertItem(SmallIcon("help"), i18n("&Help"), helpMnu);
	menu->insertItem(SmallIcon("system-log-out"), i18n("Quit"), tdeApp, TQ_SLOT(quit()));

	// Set up card monitoring
	TDEGenericDevice *hwdevice;
	TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices();
	TDEGenericHardwareList cardReaderList = hwdevices->listByDeviceClass(TDEGenericDeviceType::CryptographicCard);
	for (hwdevice = cardReaderList.first(); hwdevice; hwdevice = cardReaderList.next()) {
		TDECryptographicCardDevice* cdevice = static_cast<TDECryptographicCardDevice*>(hwdevice);
		cdevice->enableCardMonitoring(true);
	}

	load();

	updateTicketList();

	setupTimers();
}

/* slot: signal shutDown() from TDEApplication */
/* (not currently needed)
void TopLevel::queryExit()
{
	TDEConfig *config = tdeApp->config();
//	config->sync();
}
*/


/** Destructor */
TopLevel::~TopLevel()
{
	if (ticketWatch) delete ticketWatch;
	if (m_refreshTimer) m_refreshTimer->stop();

	delete menu;
	// FIXME: must delete more (like all the TQWidgets in config-window)?
}

void TopLevel::load() {
	TDEConfig* config = TDEGlobal::instance()->config();
	config->setGroup(NULL);
	autostart = config->readBoolEntry("Autostart", true);
	notifyExpiry = config->readBoolEntry("notifyExpiry", true);
	notifyExpiryMinutes = config->readNumEntry("notifyExpiryMinutes", 5);
}

void TopLevel::save() {
	TDEConfig* config = TDEGlobal::instance()->config();
	config->setGroup(NULL);
	config->writeEntry("Autostart", autostart);
	config->writeEntry("notifyExpiry", notifyExpiry);
	config->writeEntry("notifyExpiryMinutes", notifyExpiryMinutes);
	config->sync();

	setupTimers();
}

void TopLevel::setupTimers() {
	// FIXME
	// Better would be to call updateTicketList() notifyExpiryMinutes before first ticket expiration
	// For now this will work, but less efficiently

	if (!m_refreshTimer) {
		m_refreshTimer = new TQTimer(this);
		connect(m_refreshTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT(updateTicketList()));
		m_refreshTimer->start(10*1000, false);
	}

	if (!m_requestUpdateTimer) {
		m_requestUpdateTimer = new TQTimer(this);
		connect(m_requestUpdateTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT(updateTicketList()));
	}
}

void TopLevel::requestTicketListUpdate() {
	m_requestUpdateTimer->start(0, true);
}

void TopLevel::updateTicketList() {
	m_ticketList = LDAPManager::getKerberosTicketList(TQString::null, &m_ticketFile);

	m_ticketFile.replace("FILE:", "");

	if (!ticketWatch) {
		ticketWatch = new KDirWatch();
		connect(ticketWatch, TQ_SIGNAL(dirty(const TQString&)), this, TQ_SLOT(requestTicketListUpdate()));
		connect(ticketWatch, TQ_SIGNAL(created(const TQString&)), this, TQ_SLOT(requestTicketListUpdate()));
		connect(ticketWatch, TQ_SIGNAL(deleted(const TQString&)), this, TQ_SLOT(requestTicketListUpdate()));
		ticketWatch->addFile(m_ticketFile);
		ticketWatch->startScan();
	}
	else {
		ticketWatch->removeFile(m_ticketFile);
		ticketWatch->addFile(m_ticketFile);
		ticketWatch->startScan();
	}

	if (m_ticketList.count() > 0) {
		getNewStandardSTAct->setEnabled(true);
		destroyAllAct->setEnabled(true);
	}
	else {
		getNewStandardSTAct->setEnabled(false);
		destroyAllAct->setEnabled(false);
	}

	repaint();
}

void TopLevel::updateMenu() {
	// First, remove all current ticket entries from the menu; these can be identified by their positive id
	while (menu->idAt(0) >= 0) {
		menu->removeItemAt(0);
	}

	// Add the new ticket entries to the top of the menu
	int id = 0;
	int index = 0;
	if (m_ticketList.count() < 1) {
		menu->insertItem(i18n("No Kerberos Tickets Available"), id++, index++);
		menu->setItemEnabled(0, false);
	}
	else {
		KerberosTicketInfoList::Iterator it;
		for (it = m_ticketList.begin(); it != m_ticketList.end(); ++it) {
			KerberosTicketInfo ticket = *it;
			TQDateTime now = TQDateTime::currentDateTime();

			TQString label = ticket.serverPrincipal;
			if (ticket.validEndTime > now) {
				label = label + i18n("(Active)");
			}
			else {
				label = label + i18n("(Expired)");
			}
			menu->insertItem(label, id++, index++);
		}
	}
}

void TopLevel::getNewTicket(bool requestServiceTicket) {
	bool allow_card = false;
	TDEGenericDevice *hwdevice;
	TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices();
	TDEGenericHardwareList cardReaderList = hwdevices->listByDeviceClass(TDEGenericDeviceType::CryptographicCard);
	for (hwdevice = cardReaderList.first(); hwdevice; hwdevice = cardReaderList.next()) {
		TDECryptographicCardDevice* cdevice = static_cast<TDECryptographicCardDevice*>(hwdevice);
		TQString login_name = TQString::null;
		X509CertificatePtrList certList = cdevice->cardX509Certificates();
		if (certList.count() > 0) {
			KSSLCertificate* card_cert = NULL;
			card_cert = KSSLCertificate::fromX509(certList[0]);
			TQStringList cert_subject_parts = TQStringList::split("/", card_cert->getSubject(), false);
			for (TQStringList::Iterator it = cert_subject_parts.begin(); it != cert_subject_parts.end(); ++it ) {
				TQString lcpart = (*it).lower();
				if (lcpart.startsWith("cn=")) {
					login_name = lcpart.right(lcpart.length() - strlen("cn="));
				}
			}
			delete card_cert;
		}
		if (login_name != "") {
			allow_card = true;
			break;
		}
	}

	LDAPCredentials credentials;
	if (m_ticketList.count() > 0) {
		TQStringList princParts = TQStringList::split("@", m_ticketList[0].cachePrincipal);
		credentials.username = princParts[0];
		credentials.realm = princParts[1];
	}
	else {
		struct passwd* pwd = getpwuid(geteuid());
		if (pwd) {
			credentials.username = TQString(pwd->pw_name);
		}
	}
	int result = LDAPManager::getKerberosPassword(credentials, i18n("Please provide Kerberos credentials"), requestServiceTicket, allow_card, this);
	if (result == KDialog::Accepted) {
		TQString errorstring;
		TQString service;
		if (requestServiceTicket) {
			service = credentials.service;
		}
		if (LDAPManager::obtainKerberosTicket(credentials, service, &errorstring) != 0) {
			KMessageBox::error(this, i18n("<qt>Failed to obtain ticket<p>%1</qt>").arg(errorstring), i18n("Failed to obtain Kerberos ticket"));
		}
	}

	updateTicketList();
}


void TopLevel::getNewTGT() {
	getNewTicket(false);
}

void TopLevel::getNewServiceTicket() {
	getNewTicket(true);
}

void TopLevel::getNewServiceTicketWithExistingCreds() {
	TQString errorstring;
	TQString service;

	bool ok;
	service = KLineEditDlg::getText(i18n("Enter the name of the Kerberos service principal you wish to obtain"), TQString::null, &ok, this);
	if (ok) {
		if (LDAPManager::obtainKerberosServiceTicket(service, &errorstring) != 0) {
			KMessageBox::error(this, i18n("<qt>Failed to obtain service ticket<p>%1</qt>").arg(errorstring), i18n("Failed to obtain Kerberos service ticket"));
		}
	}
}

void TopLevel::destroyAllTickets() {
	if (system("kdestroy --all") != 0) {
		KMessageBox::error(this, i18n("Unable to destroy tickets!"), i18n("Internal Error"));
	}
	updateTicketList();
}

void TopLevel::changeKerberosPassword() {
	bool allow_card = false;
	TDEGenericDevice *hwdevice;
	TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices();
	TDEGenericHardwareList cardReaderList = hwdevices->listByDeviceClass(TDEGenericDeviceType::CryptographicCard);
	for (hwdevice = cardReaderList.first(); hwdevice; hwdevice = cardReaderList.next()) {
		TDECryptographicCardDevice* cdevice = static_cast<TDECryptographicCardDevice*>(hwdevice);
		TQString login_name = TQString::null;
		X509CertificatePtrList certList = cdevice->cardX509Certificates();
		if (certList.count() > 0) {
			KSSLCertificate* card_cert = NULL;
			card_cert = KSSLCertificate::fromX509(certList[0]);
			TQStringList cert_subject_parts = TQStringList::split("/", card_cert->getSubject(), false);
			for (TQStringList::Iterator it = cert_subject_parts.begin(); it != cert_subject_parts.end(); ++it ) {
				TQString lcpart = (*it).lower();
				if (lcpart.startsWith("cn=")) {
					login_name = lcpart.right(lcpart.length() - strlen("cn="));
				}
			}
			delete card_cert;
		}
		if (login_name != "") {
			allow_card = true;
			break;
		}
	}

	LDAPCredentials oldCredentials;
	LDAPCredentials newCredentials;
	if (m_ticketList.count() > 0) {
		TQStringList princParts = TQStringList::split("@", m_ticketList[0].cachePrincipal);
		oldCredentials.username = princParts[0];
		oldCredentials.realm = princParts[1];
	}
	else {
		struct passwd* pwd = getpwuid(geteuid());
		if (pwd) {
			oldCredentials.username = TQString(pwd->pw_name);
		}
	}
	int result = LDAPManager::getNewKerberosPassword(oldCredentials, newCredentials, i18n("Please provide new Kerberos credentials below"), allow_card, this);
	if (result == KDialog::Accepted) {
		TQString errorstring;
		TQString service;
		if (LDAPManager::changeKerberosPassword(oldCredentials, newCredentials, service, &errorstring) == 0) {
			KMessageBox::information(this, i18n("<qt>Password changed successfully</qt>"), i18n("Kerberos password changed"));
		}
		else {
			KMessageBox::error(this, i18n("<qt>Failed to change password<p>%1</qt>").arg(errorstring), i18n("Failed to change Kerberos password"));
		}
	}
}

void TopLevel::resizeEvent (TQResizeEvent *)
{
	activeTicketsPixmap = loadSizedIcon("kerberos_activetickets", width());
	noTicketsPixmap = loadSizedIcon("kerberos_notickets", width());
	expiredTicketsPixmap = loadSizedIcon("kerberos_expiredtickets", width());
	partiallyExpiredTicketsPixmap = loadSizedIcon("kerberos_someexpiredtickets", width());
	timerOverlayPixmap = loadSizedIcon("kerberos_timeroverlay", width());
	warningOverlayPixmap = loadSizedIcon("kerberos_warningoverlay", width());
	repaint();
}

/** Handle mousePressEvent */
void TopLevel::mousePressEvent(TQMouseEvent *event) {
	if (event->button() == TQt::LeftButton) {
		showTicketList();
	}
	else if (event->button() == TQt::RightButton) {
		updateMenu();
		menu->popup(TQCursor::pos());
	}
	else if (event->button() == MidButton) {
		// currently unused
	}
}

/** Handle paintEvent (ie. animate icon) */
void TopLevel::paintEvent(TQPaintEvent *) {
	TQString baseToolTip = i18n("%1 Kerberos ticket(s) listed for principal %2").arg(m_ticketList.count()).arg(m_ticketList[0].cachePrincipal);

	bool has_tickets = false;
	bool tickets_expiring_soon = false;
	bool some_tickets_expired = false;
	bool all_tickets_expired = true;

	int expired_tickets = 0;
	int expiring_tickets = 0;

	KerberosTicketInfoList::Iterator it;
	for (it = m_ticketList.begin(); it != m_ticketList.end(); ++it) {
		KerberosTicketInfo ticket = *it;
		has_tickets = true;
		TQDateTime now = TQDateTime::currentDateTime();
		if (ticket.validEndTime > now) {
			all_tickets_expired = false;
		}
		else {
			expired_tickets++;
			some_tickets_expired = true;
		}
		if ((ticket.validEndTime > now) && (ticket.validEndTime < now.addSecs(notifyExpiryMinutes*60))) {
			expiring_tickets++;
			tickets_expiring_soon = true;
		}
	}

	if (!notifyExpiry) tickets_expiring_soon = false;

	TQPainter p(this);
	if (has_tickets) {
		if (all_tickets_expired) {
			p.drawPixmap(0, 0, expiredTicketsPixmap);
			p.drawPixmap(0, 0, warningOverlayPixmap);
			baseToolTip = baseToolTip + "\n" + i18n("All ticket(s) have expired");
		}
		else if (some_tickets_expired) {
			p.drawPixmap(0, 0, partiallyExpiredTicketsPixmap);
			p.drawPixmap(0, 0, warningOverlayPixmap);
			baseToolTip = baseToolTip + "\n" + i18n("%1 ticket(s) have expired").arg(expired_tickets);
		}
		else {
			p.drawPixmap(0, 0, activeTicketsPixmap);
			if (tickets_expiring_soon) {
				p.drawPixmap(0, 0, timerOverlayPixmap);
				baseToolTip = baseToolTip + "\n" + i18n("All ticket(s) are active\n%1 ticket(s) will expire shortly").arg(expiring_tickets);
			}
			else {
				baseToolTip = baseToolTip + "\n" + i18n("All ticket(s) are active");
			}
		}
	}
	else {
		p.drawPixmap(0, 0, noTicketsPixmap);
		baseToolTip = i18n("No Kerberos tickets are available");
	}
	p.end();

	setToolTip(baseToolTip);
}

void TopLevel::timerEvent(TQTimerEvent *) {
	//
}

/** update ToolTip */
void TopLevel::setToolTip(const TQString &text, bool force) {
	// don't update if text hasn't changed
	if (lastTip == text) {
		return;
	}

	// don't remove Tooltip if (probably - can't know for sure?) currently showing
	// FIXME: this isn't too nice: currently mouse must stay outside for >1s for update to occur
	if (force || !this->hasMouse() || (lastTip == i18n("Kerberos Tickets Manager"))) {
		lastTip = text;
		TQToolTip::remove(this);
		TQToolTip::add(this, text);
	}
}

TQString addTicketInfo(TQString origString, KerberosTicketInfo ticket) {
	origString += i18n("Server: ") + ticket.serverPrincipal + "<br>";
	origString += i18n("Client: ") + ticket.clientPrincipal + "<br>";
	origString += i18n("Encryption Type: ") + ticket.encryptionType + "<br>";
	origString += i18n("Key Version: %1").arg(ticket.keyVersionNumber) + "<br>";
	if (ticket.authenticationTime.isValid()) origString += i18n("Authenticated: ") + ticket.authenticationTime.toString() + "<br>";
	if (ticket.validStartTime.isValid()) origString += i18n("Valid After: ") + ticket.validStartTime.toString() + "<br>";
	if (ticket.validEndTime.isValid()) origString += i18n("Valid Before: ") + ticket.validEndTime.toString() + "<br>";
	return origString;
}

void TopLevel::showTicketList() {
	int i;
	TQString listText = "<qt>";
	updateTicketList();
	if (m_ticketList.count() <= 0) {
		listText += "No Kerberos tickets available";
	}
	else {
		i = 1;
		KerberosTicketInfoList::Iterator it;
		for (it = m_ticketList.begin(); it != m_ticketList.end(); ++it) {
			KerberosTicketInfo ticket = *it;

			listText += i18n("<b>Kerberos Ticket %1</b>").arg(i) + "<br>";
			listText += addTicketInfo("", ticket);

			listText += "<p>";
			i++;
		}
	}
	listText += "</qt>";

	KMessageBox::information(this, listText, i18n("Kerberos Ticket Information"), TQString::null, KMessageBox::Notify);
}

void TopLevel::menuAction(int index) {
	if (index >= 0) {
		TQString listText = "<qt>";
		KerberosTicketInfo ticket = m_ticketList[index];

		listText += i18n("<b>Kerberos Ticket %1</b>").arg(index+1) + "<br>";
		listText += addTicketInfo("", ticket);

		listText += "</qt>";

		if (KMessageBox::warningYesNo(this, listText, i18n("Kerberos Ticket Information"), TQString("Destroy this Ticket"), TQString("Cancel")) == KMessageBox::Yes) {
			TQString errorstring;
			if (LDAPManager::destroyKerberosTicket(ticket.serverPrincipal, &errorstring) != 0) {
				KMessageBox::error(this, i18n("<qt>Failed to destroy ticket<br>%1<p>%2</qt>").arg(ticket.serverPrincipal).arg(errorstring), i18n("Failed to destroy Kerberos ticket"));
			}
		}
	}
}

/* config-slot: "help" button clicked */
void TopLevel::help() {
	tdeApp->invokeHelp();
}

void TopLevel::config() {
	KTMConfigureDialog confdlg(this, this);
	confdlg.exec();
}
