// /file SendmailIntegrate.cpp
//
// Copyright (C) 2013, ARM Research Labs, LLC.
// See www.armresearch.com for the copyright terms.
//
// This file contains the functions for SendmailIntegrate.
//
// $Id$
//
///////////////////////////////////////////////////////////////////////////////////////////////////

#include <cstdlib>
#include <cerrno>
#include <cstring>

#include <iostream>
#include <exception>
#include <stdexcept>
#include <sstream>
#include <fstream>

#include "SendmailIntegrate.hpp"

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Configuration. ////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////

const std::string SendmailLdaKey("FEATURE(`local_procmail'"); ///< Line in sendmail.cf that specifies the LDA.

const std::string MtaIsRunningCommand("ps axl | grep -v grep | grep -q ' sendmail: '");

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// End of configuration. /////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////

void
SendmailIntegrate::SetOperatingSystem(std::string OperatingSystemType) {

    ProcmailRcFileName = "/etc/procmailrc";

    if ("OpenBSD" == OperatingSystemType) {

	IntegrationIsSupported = false;

    } else if ("FreeBSD" == OperatingSystemType) {

	IntegrationIsSupported = false;

    } else if ("Ubuntu" == OperatingSystemType) {

	IntegrationIsSupported = true;
	SendmailSendmailMcPath = "/etc/mail/sendmail.mc";
	SendmailSendmailCfPath = "/etc/mail/sendmail.cf";

	SnfSnifferDirName = "/var/spool/postfix/snf-server";
	SnfSnifferFileName = SnfSnifferDirName + "/snfSnifferFilter";
	SnfSnifferSampleFileName = "/usr/sbin/snfSnifferFilter.sample";

	BuildInstallSendmailCfFile = "(cd /etc/mail && make)";
	ReloadMtaCommand = "/etc/init.d/sendmail reload";

	FileToBackup.push_back(SendmailSendmailMcPath);
	FileToBackup.push_back(SendmailSendmailCfPath);

    } else if ("RedHat" == OperatingSystemType) {

	IntegrationIsSupported = true;
	SendmailSendmailMcPath = "/etc/mail/sendmail.mc";
	SendmailSendmailCfPath = "/etc/mail/sendmail.cf";

	SnfSnifferDirName = "/usr/sbin";
	SnfSnifferFileName = SnfSnifferDirName + "/snfSnifferFilter";
	SnfSnifferSampleFileName = SnfSnifferDirName + "/snfSnifferFilter.sample";

	BuildInstallSendmailCfFile = "(cd /etc/mail && make)";
	ReloadMtaCommand = "/etc/init.d/sendmail reload";

	FileToBackup.push_back(SendmailSendmailMcPath);
	FileToBackup.push_back(SendmailSendmailCfPath);

    } else if ("Suse" == OperatingSystemType) {

	IntegrationIsSupported = true;
	SendmailSendmailMcPath = "/etc/mail/linux.mc";
	SendmailSendmailCfPath = "/etc/mail/sendmail.cf";

	SnfSnifferDirName = "/usr/sbin";
	SnfSnifferFileName = SnfSnifferDirName + "/snfSnifferFilter";
	SnfSnifferSampleFileName = SnfSnifferDirName + "/snfSnifferFilter.sample";

	BuildInstallSendmailCfFile = "(cd /etc/mail && rm -f sendmail.cf && m4 /etc/mail/linux.mc > sendmail.cf)";
	ReloadMtaCommand = "/etc/init.d/sendmail reload";

	FileToBackup.push_back(SendmailSendmailMcPath);
	FileToBackup.push_back(SendmailSendmailCfPath);

    } else {

	std::ostringstream Temp;

	Temp << "***Error from SendmailIntegrate::SetOperatingSystem:  Invalid value of OperatingSystemType:  "
	     << OperatingSystemType;

	throw std::runtime_error(Temp.str());

    }

    ProcmailRcSnifferIntegration = ":0 fw\n| " + SnfSnifferFileName;
    ProcmailRcSnifferIntegration += "\n";

}

void
SendmailIntegrate::Integrate(FileBackup *SaveFile) {

    if (!IntegrationIsSupported) {


	std::ostringstream Temp;

	Temp << "Integration with sendmail is not supported on this platform.  "
	     << "Please see " << DOC_DIR << "/INSTALL for instructions for manual "
	     << "integration with sendmail.";

	throw std::runtime_error(Temp.str());

    }

    if (IsIntegrated()) {

	return;

    }

    // Check whether the configuration has procmail as the LDA.
    if (!MtaConfigurationHasProcmailForLda()) {

	std::string Temp;

	Temp = "Error--sendmail must be configured to use procmail as the LDA.";
	throw std::runtime_error(Temp);

    }

    if (Verbose()) {

	std::cout << "Create " << SnfSnifferFileName << " if it doesn't exist...";

    }

    if (!Explain()) {

        if (!FileExists(SnfSnifferFileName)) {                                  // Create SnfSnifferFilter script
                                                                                // if it doesn't exist.

	    SaveFile->CreateBackupFile(SnfSnifferFileName);

	    if (!FileExists(SnfSnifferDirName)) {

		MkDir(SnfSnifferDirName);

	    }

	    SetMode(SnfSnifferDirName, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
	    SetOwnerGroup(SnfSnifferDirName);

	    Copy(SnfSnifferSampleFileName, SnfSnifferFileName);
	    SetMode(SnfSnifferFileName, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
	    SetOwnerGroup(SnfSnifferFileName);

	}

    }

    OutputVerboseEnd();

    if (Verbose()) {

	std::cout << "Add\n\n" << ProcmailRcSnifferIntegration << "\n\nto " << ProcmailRcFileName << "...";

    }

    std::string ProcmailRcFileContent;

    if (!Explain()) {

        if (FileExists(ProcmailRcFileName)) {                                   // Read any existing procmail configuration.

	    std::ifstream Input;

	    Input.open(ProcmailRcFileName.c_str());
	    if (!Input) {
		std::string Temp;

		Temp = "Error opening the procmail configuration file " + ProcmailRcFileName;
		Temp += " for reading:  ";
		Temp += strerror(errno);
		throw std::runtime_error(Temp);
	    }

	    if (!Input.eof()) {

		std::ostringstream Buffer;

		Buffer << Input.rdbuf();
		ProcmailRcFileContent = Buffer.str();

	    }

	    Input.close();
	    if (!Input) {
		std::string Temp;

		Temp = "Error closing the procmail configuration file " + ProcmailRcFileName;
		Temp += " after reading:  ";
		Temp += strerror(errno);
		throw std::runtime_error(Temp);
	    }

	}

	ProcmailRcFileContent = ProcmailRcSnifferIntegration + ProcmailRcFileContent;

	SaveFile->CreateBackupFile(ProcmailRcFileName);

	std::ofstream Output;                                                   // Write the updated contents.

	Output.open(ProcmailRcFileName.c_str(), std::ios::trunc);
	if (!Output) {
	    std::string Temp;

	    Temp = "Error opening the procmail configuration file " + ProcmailRcFileName;
	    Temp += " for writing:  ";
	    Temp += strerror(errno);
	    throw std::runtime_error(Temp);
	}

	Output << ProcmailRcFileContent;
	if (!Output) {
	    std::string Temp;

	    Temp = "Error writing the procmail configuration file " + ProcmailRcFileName;
	    Temp += ":  ";
	    Temp += strerror(errno);
	    throw std::runtime_error(Temp);
	}

	Output.close();
	if (!Output) {
	    std::string Temp;

	    Temp = "Error closing the procmail configuration file " + ProcmailRcFileName;
	    Temp += " after writing:  ";
	    Temp += strerror(errno);
	    throw std::runtime_error(Temp);
	}

    }

    OutputVerboseEnd();

    if (!ReloadMta()) {

        std::cerr << "Unable to reload the sendmail configuration.  Please reload "
		  << " the sendmail configuration for the integration with SNFServer to take effect.";

    }

}

void
SendmailIntegrate::Unintegrate(FileBackup *SaveFile) {

    if (!IntegrationIsSupported) {

	return;

    }

    if (!IsIntegrated()) {

	return;

    }

    std::ifstream Input;

    if (Verbose()) {

	std::cout << "Remove integration in procmail file " << ProcmailRcFileName << "--\n";

    }

    if (!Explain()) {

        Input.open(ProcmailRcFileName.c_str());
	if (!Input) {
	    std::string Temp;

	    Temp = "Error opening the procmail configuration file " + ProcmailRcFileName;
	    Temp += " for reading:  ";
	    Temp += strerror(errno);
	    throw std::runtime_error(Temp);
	}

	std::ostringstream ContentStream;

	ContentStream << Input.rdbuf();

	Input.close();
	if (!Input) {
	    std::string Temp;

	    Temp = "Error closing the procmail configuration file " + ProcmailRcFileName;
	    Temp += ":  ";
	    Temp += strerror(errno);
	    throw std::runtime_error(Temp);
	}

	std::string Content;

	Content = ContentStream.str();

	if (Verbose()) {

	    std::cout << "  Remove all occurances of\n\n" << ProcmailRcSnifferIntegration << "\n\n"
		      << "  from" << ProcmailRcFileName << "...\n";

	}

	std::string::size_type IntegrationBegin = std::string::npos;

	while ((IntegrationBegin = Content.find(ProcmailRcSnifferIntegration)) != std::string::npos) {

	    Content.erase(IntegrationBegin, ProcmailRcSnifferIntegration.length());

	}

	SaveFile->CreateBackupFile(ProcmailRcFileName);

        std::ofstream Output;                                                   // Write the updated contents.

	Output.open(ProcmailRcFileName.c_str(), std::ios::trunc);
	if (!Output) {
	    std::string Temp;

	    Temp = "Error opening the procmail configuration file " + ProcmailRcFileName;
	    Temp += " for writing:  ";
	    Temp += strerror(errno);
	    throw std::runtime_error(Temp);
	}

	Output << Content;
	if (!Output) {
	    std::string Temp;

	    Temp = "Error writing the procmail configuration file " + ProcmailRcFileName;
	    Temp += ":  ";
	    Temp += strerror(errno);
	    throw std::runtime_error(Temp);
	}

	Output.close();
	if (!Output) {
	    std::string Temp;

	    Temp = "Error closing the procmail configuration file " + ProcmailRcFileName;
	    Temp += " after writing:  ";
	    Temp += strerror(errno);
	    throw std::runtime_error(Temp);
	}

    }

    OutputVerboseEnd();

    if (!ReloadMta()) {

        std::cerr << "Unable to reload the sendmail configuration.  Please run "
		  << "'sendmail reload' for the integration with SNFServer to take effect.";

    }

}

bool
SendmailIntegrate::MtaIsRunningDetected() {

    if (Verbose()) {

	std::cout << "Checking whether sendmail is detected to be running...";

    }

    bool IsRunningDetected;

    IsRunningDetected = (std::system(MtaIsRunningCommand.c_str()) == 0);

    if (Verbose()) {

	std::cout << (IsRunningDetected ? "yes..." : "no...");

    }

    OutputVerboseEnd();

    return IsRunningDetected;

}

bool
SendmailIntegrate::ReloadMta() {

    if (!MtaIsRunningDetected()) {

        return true;

    }

    if (Verbose()) {

	std::cout << "Reloading sendmail with the command '"
		  << ReloadMtaCommand << "'...\n";
	std::cout.flush();

    }

    bool Succeeded;

    if (!Explain()) {

	Succeeded = (std::system(ReloadMtaCommand.c_str()) == 0);

	if (Verbose()) {

	    std::cout << (Succeeded ? "succeeded..." : "failed...");

	}

    }

    OutputVerboseEnd();

    return Succeeded;

}

bool
SendmailIntegrate::IsIntegrated() {

    if (Verbose()) {

	std::cout << "Checking for any SNFServer integration in the procmail file " << ProcmailRcFileName << "...";

    }

    if (!FileExists(ProcmailRcFileName)) {

	if (Verbose()) {

	    std::cout << "file doesn't exist; sendmail is not integrated...";

	}

	OutputVerboseEnd();

	return false;

    }

    bool Integrated = false;

    std::ifstream Input;

    Input.open(ProcmailRcFileName.c_str());                                     // Read the contents.
    if (!Input) {
	std::string Temp;

	Temp = "Error opening the procmail configuration file " + ProcmailRcFileName;
	Temp += " for reading:  ";
	Temp += strerror(errno);
	throw std::runtime_error(Temp);
    }

    std::string ProcmailRcFileContent;

    if (!Input.eof()) {

	std::ostringstream Buffer;

	Buffer << Input.rdbuf();
	ProcmailRcFileContent = Buffer.str();

    }

    Input.close();
    if (!Input) {
	std::string Temp;

	Temp = "Error closing the procmail configuration file " + ProcmailRcFileName;
	Temp += " after reading:  ";
	Temp += strerror(errno);
	throw std::runtime_error(Temp);

    }

    Integrated = (ProcmailRcFileContent.find(ProcmailRcSnifferIntegration) != std::string::npos);

    if (Verbose()) {

	if (Integrated) {

	    std::cout << "found\n\n" << ProcmailRcSnifferIntegration << "\n\n...";

	} else {

	    std::cout << "none found...";

	}

    }

    OutputVerboseEnd();

    return Integrated;

}

bool
SendmailIntegrate::MtaConfigurationHasProcmailForLda() {

    std::string File;
    std::ifstream Input;

    File = SendmailSendmailMcPath;

    if (Verbose()) {

	std::cout << "Checking sendmail configuraton file " + File
		  << " to verify that procmail is the Local Delivery Agent.\n";

    }

    Input.open(File.c_str());
    if (!Input) {
	std::string Temp;

	Temp = "Error opening sendmail configuration file " + File;
	Temp += " for reading:  ";
	Temp += strerror(errno);
	throw std::runtime_error(Temp);
    }

    std::string Line;

    bool LdaIsProcmail = true;
    bool FoundLdaLine = false;

    while (getline(Input, Line)) {

	if (Line.substr(0, SendmailLdaKey.length()) == SendmailLdaKey) {        // Check for LDA line.

	    FoundLdaLine = true;

	    if (Line.find(",", SendmailLdaKey.length()) != std::string::npos) { // Additional arguments?

		if (Line.find("procmail", SendmailLdaKey.length()) == std::string::npos) { // Yes.

                    LdaIsProcmail = false;                                      // procmail not specified in the config line.

		    if (Verbose()) {

			std::cout << "The following line indicates that the sendmail LDA is not procmail:\n\n"
				  << Line << "\n";

		    }

		    break;

                } else {                                                        // procmail is specified in the config line.

		    if (Verbose()) {

			std::cout << "The following line indicates that the sendmail LDA is procmail, "
				  << "as required to integrate with SNFServer:\n\n"
				  << Line << "\n\n";

		    }

		    break;

		}

            } else {                                                            // LDA line uses default, which is procmail.

		if (Verbose()) {

		    std::cout << "The following line indicates that the sendmail LDA is procmail, "
			      << "as required to integrate with SNFServer:\n\n"
			      << Line << "\n\n";

		}

		break;

	    }

	}

    }

    Input.close();
    if (Input.bad()) {
	std::string Temp;

	Temp = "Error closing the sendmail configuration file " + File;
	Temp += " after reading:  ";
	Temp += strerror(errno);
	throw std::runtime_error(Temp);
    }

    if (Verbose() && !FoundLdaLine) {

	std::cout << "The absence of \"" << SendmailLdaKey << "\" indicates that the LDA is procmail, "
		  << "as required to integrate with SNFServer.\n";

    }

    return LdaIsProcmail;

}
