/***************************************************************************
 *   Copyright (C) 2013 - 2015 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 <csignal>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netdb.h>
#include <pwd.h>

#include <tdeapplication.h>
#include <tdestartupinfo.h>
#include <tdecmdlineargs.h>
#include <tdeaboutdata.h>

#include <tdesimpleconfig.h>

#include <tqdatetime.h>
#include <tqfile.h>
#include <tqdir.h>

#include <libtdeldap.h>

// FIXME
// Connect this to CMake/Automake
#define KDE_CONFDIR "/etc/trinity"

static const char description[] =
	I18N_NOOP("TDE utility for updating realm certificates");

static const char version[] = "v0.0.2";

static TDECmdLineOptions options[] = {
	{ "immediate", I18N_NOOP("Force immediate update"), 0 },
	TDECmdLineLastOption
};

bool received_sighup = false;

void signalHandler(int signum)
{
	printf("[INFO] Got signal %d\n", signum);
	if (signum == SIGHUP) {
		received_sighup = true;
	}
	else if (signum == SIGTERM) {
		unlink(TDE_LDAP_CERT_UPDATER_PID_FILE);
		exit(0);
	}
	else if (signum == SIGINT) {
		unlink(TDE_LDAP_CERT_UPDATER_PID_FILE);
		exit(0);
	}
}

int get_certificate_from_server(TQString certificateName, TQString certificateFileName, LDAPRealmConfig realmcfg)
{
	int retcode = 0;
	TQString errorstring;

	// Bind anonymously to LDAP
	LDAPCredentials* credentials = new LDAPCredentials;
	credentials->username = "";
	credentials->password = "";
	credentials->realm = realmcfg.name.upper();
	credentials->use_tls = true;
	LDAPManager* ldap_mgr = new LDAPManager(realmcfg.name.upper(), TQString("ldaps://%1").arg(realmcfg.admin_server).ascii(), credentials);

	// Add the domain-wide computer local admin group to local sudoers
	ldap_mgr->writeSudoersConfFile(&errorstring);

	// Get and install the CA root certificate from LDAP
	printf("[INFO] Updating certificate %s from LDAP\n", certificateFileName.ascii());
	if (ldap_mgr->getTDECertificate(certificateName, certificateFileName, &errorstring) != 0) {
		printf("[ERROR] Unable to obtain root certificate for realm %s: %s\n", realmcfg.name.upper().ascii(), errorstring.ascii());
		retcode = 1;
	}

	delete ldap_mgr;
	delete credentials;

	return retcode;
}

int main(int argc, char *argv[])
{
	// Register signal handler for SIGHUP
	signal(SIGHUP, signalHandler);
	// Register signal handler for SIGINT
	signal(SIGINT, signalHandler);
	// Register signal handler for SIGTERM
	signal(SIGTERM, signalHandler);

	TQDir pidDir(TDE_LDAP_PID_DIR);
	if (!pidDir.exists()) {
		mkdir(TDE_LDAP_PID_DIR, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
	}
	TQFile pidFile(TDE_LDAP_CERT_UPDATER_PID_FILE);
	if (pidFile.open(IO_WriteOnly)) {
		TQTextStream stream(&pidFile);
		stream << getpid();
		pidFile.close();
	}

	// Seed random number generator
	struct timeval time;
	gettimeofday(&time,NULL);
	srand((time.tv_sec * 1000) + (time.tv_usec / 1000));

	// Initialize TDE application libraries
	TDEAboutData aboutData( "tdeldapcertupdater", I18N_NOOP("Realm Certificate Updater"),
		version, description, TDEAboutData::License_GPL,
		"(c) 2013 - 2015, Timothy Pearson");
		aboutData.addAuthor("Timothy Pearson",0, "kb9vqf@pearsoncomputing.net");
	TDECmdLineArgs::init( argc, argv, &aboutData );
	TDECmdLineArgs::addCmdLineOptions(options);
	TDEApplication::disableAutoDcopRegistration();

	TDEApplication app(false, false);

	TDEStartupInfo::appStarted();

	bool immediate = TDECmdLineArgs::parsedArgs()->isSet("immediate");

	//======================================================================================================================================================
	//
	// Updater code follows
	//
	//======================================================================================================================================================

	TDESimpleConfig* systemconfig = new TDESimpleConfig( TQString::fromLatin1( KDE_CONFDIR "/ldap/ldapconfigrc" ));
	LDAPRealmConfigList realms = LDAPManager::readTDERealmList(systemconfig, false);
	TQString m_defaultRealm = systemconfig->readEntry("DefaultRealm");

	int prevSecondsToExpiry = (7*24*60*60);

	while (1) {
		bool newCertDownloaded = false;
		bool allDownloadsOK = true;
		TQDateTime now = TQDateTime::currentDateTime();
		TQDateTime earliestCertExpiry = now.addDays(14);	// Recheck every 7 days regardless of last expiry check results

		LDAPRealmConfigList::Iterator it;
		for (it = realms.begin(); it != realms.end(); ++it) {
			LDAPRealmConfig realmcfg = it.data();
			TQString certificateFileName = KERBEROS_PKI_PUBLICDIR + realmcfg.admin_server + ".ldap.crt";
			TQString crlFileName = KERBEROS_PKI_PUBLICDIR + realmcfg.admin_server + ".ldap.crl";

			TQDateTime certExpiry;
			TQDateTime soon = now.addDays(7);		// Keep in sync with src/ldapcontroller.cpp

			if (TQFile::exists(certificateFileName)) {
				certExpiry = LDAPManager::getCertificateExpiration(certificateFileName);
				if (certExpiry >= now) {
					printf("[INFO] Certificate %s expires %s\n", certificateFileName.ascii(), certExpiry.toString().ascii()); fflush(stdout);
				}
				if (immediate || (certExpiry < now) || ((certExpiry >= now) && (certExpiry < soon))) {
					if (get_certificate_from_server("publicRootCertificate", certificateFileName, realmcfg) == 0) {
						newCertDownloaded = true;
					}
					else {
						allDownloadsOK = false;
					}
				}
				if (certExpiry < earliestCertExpiry) {
					earliestCertExpiry = certExpiry;
				}
			}
			else {
				mkdir(TDE_CERTIFICATE_DIR, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
				mkdir(KERBEROS_PKI_PUBLICDIR, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
				if (get_certificate_from_server("publicRootCertificate", certificateFileName, realmcfg) == 0) {
					newCertDownloaded = true;
				}
				else {
					allDownloadsOK = false;
				}
			}

			if (TQFile::exists(crlFileName)) {
				certExpiry = LDAPManager::getCertificateExpiration(crlFileName);
				if (certExpiry >= now) {
					printf("[INFO] CRL %s expires %s\n", crlFileName.ascii(), certExpiry.toString().ascii()); fflush(stdout);
				}
				if (immediate || (certExpiry < now) || ((certExpiry >= now) && (certExpiry < soon))) {
					if (get_certificate_from_server("publicRootCertificateRevocationList", crlFileName, realmcfg) == 0) {
						newCertDownloaded = true;
					}
					else {
						allDownloadsOK = false;
					}
				}
				if (certExpiry < earliestCertExpiry) {
					earliestCertExpiry = certExpiry;
				}
			}
			else {
				mkdir(TDE_CERTIFICATE_DIR, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
				mkdir(KERBEROS_PKI_PUBLICDIR, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
				if (get_certificate_from_server("publicRootCertificateRevocationList", crlFileName, realmcfg) == 0) {
					newCertDownloaded = true;
				}
				else {
					allDownloadsOK = false;
				}
			}

			if (newCertDownloaded) {
				if (LDAPManager::rehashClientPKCSCertificates() != 0) {
					printf("[WARNING] Unable to rehash client PKCS certificates\n"); fflush(stdout);
				}
			}
		}
		immediate = false;

		earliestCertExpiry = earliestCertExpiry.addDays(-7);	// Keep in sync with now.addDays above (use negative of value given above)
		int secondsToExpiry = now.secsTo(earliestCertExpiry);
		secondsToExpiry = secondsToExpiry + (rand()%(5*60));	// Nothing worse than thousands of clients hammering the LDAP server all at once...
		if (secondsToExpiry < 1) {
			secondsToExpiry = 1;
		}
		if ((prevSecondsToExpiry == 1) && (allDownloadsOK)) {
			// The server has not yet updated its certificate, even though our copy is close to expiration
			// Therefore, do not hammer the server with useless requests!
			prevSecondsToExpiry = (15*60) + (rand()%(5*60));
		}
		prevSecondsToExpiry = secondsToExpiry;
		printf("[INFO] Will recheck certificates in %d seconds (%d days)\n", secondsToExpiry, secondsToExpiry/60/60/24); fflush(stdout);
		if (sleep(secondsToExpiry) != 0) {
			// Signal caught
			if (!received_sighup) {
				break;
			}
		}
	}

	unlink(TDE_LDAP_CERT_UPDATER_PID_FILE);
	delete systemconfig;

	//======================================================================================================================================================

	return 0;
}
