/***************************************************************************
 *   Copyright (C) 2012 - 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 <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <pwd.h>

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

#include <ksimpleconfig.h>

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

#include <libtdeldap.h>

#ifndef KDE_CONFDIR
#define KDE_CONFDIR "/etc/trinity"
#endif

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

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

static const TDECmdLineOptions options[] =
{
	{ "force", I18N_NOOP("Force certificate update"), 0 },
	TDECmdLineLastOption // End of options.
};

void chown_safe(const char * file, uid_t user, gid_t group) {
	if (chown(file, user, group) < 0) {
		printf("[ERROR] Chown call to '%s' for %d:%d failed!\n\r", file, user, group);
	}
}

int uploadKerberosCAFileToLDAP(LDAPManager* ldap_mgr, TQString* errstr) {
	// Upload the contents of KERBEROS_PKI_PEM_FILE to the LDAP server
	TQFile cafile(KERBEROS_PKI_PEM_FILE);
	if (cafile.open(IO_ReadOnly)) {
		TQByteArray cafiledata = cafile.readAll();
		if (ldap_mgr->writeCertificateFileIntoDirectory(cafiledata, "publicRootCertificate", errstr) != 0) {
			return -1;
		}
		return 0;
	}
	return -1;
}

int main(int argc, char *argv[])
{
	TDEAboutData aboutData( "primaryrccertupdater", I18N_NOOP("Realm Certificate Updater"),
		version, description, TDEAboutData::License_GPL,
		"(c) 2012-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();

	TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs();

	bool force_update = false;
	if (args->isSet("force")) {
		force_update = true;
	}

	bool ca_modified = false;

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

	// FIXME
	// This assumes Debian!
	TQString m_ldapUserName = "openldap";
	TQString m_ldapGroupName = "openldap";

	KSimpleConfig* m_systemconfig = new KSimpleConfig( TQString::fromLatin1( KDE_CONFDIR "/ldap/ldapconfigrc" ));
	LDAPRealmConfigList m_realmconfig = LDAPManager::readTDERealmList(m_systemconfig, false);
	// Load cert config
	m_systemconfig->setGroup("Certificates");
	LDAPCertConfig m_certconfig;
	m_certconfig.countryName = m_systemconfig->readEntry("countryName");
	m_certconfig.stateOrProvinceName = m_systemconfig->readEntry("stateOrProvinceName");
	m_certconfig.localityName = m_systemconfig->readEntry("localityName");
	m_certconfig.organizationName = m_systemconfig->readEntry("organizationName");
	m_certconfig.orgUnitName = m_systemconfig->readEntry("orgUnitName");
	m_certconfig.commonName = m_systemconfig->readEntry("commonName");
	m_certconfig.emailAddress = m_systemconfig->readEntry("emailAddress");
	// Load other defaults
	m_systemconfig->setGroup(NULL);
	TQString m_defaultRealm = m_systemconfig->readEntry("DefaultRealm");

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

	TQString kdc_certfile = KERBEROS_PKI_KDC_FILE;
	kdc_certfile.replace("@@@KDCSERVER@@@", m_realmconfig[m_defaultRealm].name.lower());
	TQString ldap_certfile = LDAP_CERT_FILE;
	ldap_certfile.replace("@@@ADMINSERVER@@@", m_realmconfig[m_defaultRealm].name.lower());

	// Certificate Authority
	TQString fqdn = LDAPManager::getMachineFQDN();
	TQString defaultRealm = m_systemconfig->readEntry("DefaultRealm");

	// Connect to LDAP
	TQString realmname = defaultRealm.upper();
	LDAPCredentials* credentials = new LDAPCredentials;
	credentials->username = "";
	credentials->password = "";
	credentials->realm = realmname;
	LDAPManager* ldap_mgr = new LDAPManager(realmname, "ldapi://", credentials);
	TQString errorstring;

	TQString basedn = ldap_mgr->basedn();

	// Get certificate settings from LDAP
	TQString realmCAMaster = ldap_mgr->getRealmCAMaster(&errorstring);

	delete ldap_mgr;
	delete credentials;

	if (realmCAMaster == "") {
		printf("[WARNING] Unable to determine the realm CA master!  CA will not be updated\n"); fflush(stdout);
	}
	else {
		if (realmCAMaster == fqdn) {
			printf("This server is the realm CA master\n"); fflush(stdout);

			TQString realmname = m_defaultRealm.upper();
			LDAPCredentials* credentials = new LDAPCredentials;
			credentials->username = "";
			credentials->password = "";
			credentials->realm = realmname;
			LDAPManager* ldap_mgr = new LDAPManager(realmname, "ldapi://", credentials);

			if (TQFile::exists(KERBEROS_PKI_PEM_FILE)) {
				certExpiry = LDAPManager::getCertificateExpiration(KERBEROS_PKI_PEM_FILE);
				if (certExpiry >= now) {
					printf("Certificate %s expires %s\n", TQString(KERBEROS_PKI_PEM_FILE).ascii(), certExpiry.toString().ascii()); fflush(stdout);
				}
				if (force_update || (certExpiry < now) || ((certExpiry >= now) && (certExpiry < soon))) {
					printf("Regenerating certificate %s...\n", TQString(KERBEROS_PKI_PEM_FILE).ascii()); fflush(stdout);
					LDAPManager::generatePublicKerberosCACertificate(m_certconfig, m_realmconfig[m_defaultRealm]);

					// Upload the contents of KERBEROS_PKI_PEM_FILE to the LDAP server
					TQString errorstring;
					if (uploadKerberosCAFileToLDAP(ldap_mgr, &errorstring) != 0) {
						printf("[ERROR] Unable to upload new certificate to LDAP server!\n%s\n", errorstring.ascii()); fflush(stdout);
					}

					ca_modified = true;
				}

				// Set permissions
				chmod(KERBEROS_PKI_PEMKEY_FILE, S_IRUSR|S_IWUSR);
				chown_safe(KERBEROS_PKI_PEMKEY_FILE, 0, 0);
				chmod(KERBEROS_PKI_PEM_FILE, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
				chown_safe(KERBEROS_PKI_PEM_FILE, 0, 0);
			}
			else {
				printf("[WARNING] Certificate file %s not found!\n", TQString(KERBEROS_PKI_PEM_FILE).ascii()); fflush(stdout);
			}

			// Check CRL expiry
			TQByteArray certificateContents;
			if (ldap_mgr->getTDECertificate("publicRootCertificateRevocationList", &certificateContents, NULL) == 0) {
				certExpiry = LDAPManager::getCertificateExpiration(certificateContents);
				if (certExpiry >= now) {
					printf("CRL expires %s\n", certExpiry.toString().ascii()); fflush(stdout);
				}
				if (force_update || (certExpiry < now) || ((certExpiry >= now) && (certExpiry < soon))) {
					printf("Regenerating CRL...\n"); fflush(stdout);
					LDAPManager::generatePublicKerberosCACertificate(m_certconfig, m_realmconfig[m_defaultRealm]);

					// Upload the new CRL to the LDAP server
					if (ldap_mgr->generatePKICRL(m_certconfig.caCrlExpiryDays, m_realmconfig[m_defaultRealm], KERBEROS_PKI_CRL_FILE, KERBEROS_PKI_PEMKEY_FILE, KERBEROS_PKI_CRLDB_FILE, &errorstring) != 0) {
						printf("[ERROR] Unable to generate CRL!\n%s\n", errorstring.ascii()); fflush(stdout);
					}

					ca_modified = true;
				}
			}

			delete ldap_mgr;
		}
		else {
			printf("This server is a realm CA slave\n"); fflush(stdout);

			// Connect to LDAP
			TQString realmname = defaultRealm.upper();
			LDAPCredentials* credentials = new LDAPCredentials;
			credentials->username = "cn=admin," + basedn;
			m_systemconfig->setGroup("Replication");
			credentials->password = m_systemconfig->readEntry("Password");
			m_systemconfig->setGroup(NULL);
			credentials->realm = realmname;
			LDAPManager* ldap_mgr = new LDAPManager(realmname, TQString("ldaps://%1/").arg(realmCAMaster), credentials);
			TQString errorstring;

			if (ldap_mgr->getTDECertificate("privateRootCertificateKey", KERBEROS_PKI_PEMKEY_FILE ".tmp", &errorstring) != 0) {
				printf("[ERROR] Unable to get private CA certificate key from LDAP server!\n%s\n", errorstring.ascii()); fflush(stdout);
			}
			if (ldap_mgr->getTDECertificate("publicRootCertificate", KERBEROS_PKI_PEM_FILE ".tmp", &errorstring) != 0) {
				printf("[ERROR] Unable to get public CA certificate from LDAP server!\n%s\n", errorstring.ascii()); fflush(stdout);
			}

			delete ldap_mgr;
			delete credentials;

			TQByteArray originalPemKeyFile;
			TQByteArray originalPemFile;
			TQByteArray newPemKeyFile;
			TQByteArray newPemFile;

			TQFile* cafile;
			cafile = new TQFile(KERBEROS_PKI_PEMKEY_FILE);
			if (cafile->open(IO_ReadOnly)) {
				originalPemKeyFile = cafile->readAll();
			}
			delete cafile;
			cafile = new TQFile(KERBEROS_PKI_PEM_FILE);
			if (cafile->open(IO_ReadOnly)) {
				originalPemFile = cafile->readAll();
			}
			delete cafile;
			cafile = new TQFile(KERBEROS_PKI_PEMKEY_FILE ".tmp");
			if (cafile->open(IO_ReadOnly)) {
				newPemKeyFile = cafile->readAll();
			}
			delete cafile;
			cafile = new TQFile(KERBEROS_PKI_PEM_FILE ".tmp");
			if (cafile->open(IO_ReadOnly)) {
				newPemFile = cafile->readAll();
			}
			delete cafile;

			if ((originalPemKeyFile == newPemKeyFile) && (originalPemFile == newPemFile)) {
				unlink(KERBEROS_PKI_PEMKEY_FILE ".tmp");
				unlink(KERBEROS_PKI_PEM_FILE ".tmp");
				printf("Certificates have not changed since last update\n");
			}
			else {
				unlink(KERBEROS_PKI_PEMKEY_FILE);
				unlink(KERBEROS_PKI_PEM_FILE);
				rename(KERBEROS_PKI_PEMKEY_FILE ".tmp", KERBEROS_PKI_PEMKEY_FILE);
				rename(KERBEROS_PKI_PEM_FILE ".tmp", KERBEROS_PKI_PEM_FILE);
				force_update = true;
				printf("Certificates have changed, forcing certificate regeneration\n");
			}

			// Set permissions
			chmod(KERBEROS_PKI_PEMKEY_FILE, S_IRUSR|S_IWUSR);
			chown_safe(KERBEROS_PKI_PEMKEY_FILE, 0, 0);
			chmod(KERBEROS_PKI_PEM_FILE, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
			chown_safe(KERBEROS_PKI_PEM_FILE, 0, 0);
		}
	}

	if (ca_modified) {
		force_update = true;
	}

	// Kerberos
	if (TQFile::exists(kdc_certfile)) {
		certExpiry = LDAPManager::getCertificateExpiration(kdc_certfile);
		if (certExpiry >= now) {
			printf("Certificate %s expires %s\n", kdc_certfile.ascii(), certExpiry.toString().ascii()); fflush(stdout);
		}
		if (force_update || (certExpiry < now) || ((certExpiry >= now) && (certExpiry < soon))) {
			printf("Regenerating certificate %s...\n", kdc_certfile.ascii()); fflush(stdout);
			LDAPManager::generatePublicKerberosCertificate(m_certconfig, m_realmconfig[m_defaultRealm]);
		}
	}
	else {
		printf("[WARNING] Certificate file %s not found!\n", kdc_certfile.ascii()); fflush(stdout);
	}

	// LDAP
	if (TQFile::exists(ldap_certfile)) {
		certExpiry = LDAPManager::getCertificateExpiration(ldap_certfile);
		if (certExpiry >= now) {
			printf("Certificate %s expires %s\n", ldap_certfile.ascii(), certExpiry.toString().ascii()); fflush(stdout);
		}
		if (force_update || (certExpiry < now) || ((certExpiry >= now) && (certExpiry < soon))) {
			printf("Regenerating certificate %s...\n", ldap_certfile.ascii()); fflush(stdout);
			uid_t slapd_uid = 0;
			gid_t slapd_gid = 0;

			// Get LDAP user uid/gid
			struct passwd *pwd;
			pwd = getpwnam(m_ldapUserName.local8Bit());
			slapd_uid = pwd->pw_uid;
			slapd_gid = pwd->pw_gid;

			LDAPManager::generatePublicLDAPCertificate(m_certconfig, m_realmconfig[m_defaultRealm], slapd_uid, slapd_gid);
		}
	}
	else {
		printf("[WARNING] Certificate file %s not found!\n", ldap_certfile.ascii()); fflush(stdout);
	}

	delete m_systemconfig;

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

	return 0;
}
