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

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

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

#include "PostfixIntegrate.hpp"

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


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

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

    MtaIsRunningCommand = "ps axl | grep -v grep | grep -q 'postfix/master'";

    PostfixDefaultIsChrooted  = false;                                          // Overwritten if postfix is
    SnfSnifferDirName = "/usr/sbin";                                            // by default chrooted.
    SnfSnifferFileName = SnfSnifferDirName + "/snfSniffer";
    SnfSnifferSampleFileName = SnfSnifferFileName + ".sample";

    ContentFilterLine = "     -o content_filter=snfilter:dummy\n";
    ContentFilterSpec = "snfilter  unix  -       n       n       -       10      pipe\n";

    if ("OpenBSD" == OperatingSystemType) {

	PostfixDefaultIsChrooted  = true;
	SnfSnifferDirName = "/usr/local/sbin";
	SnfSnifferFileName = SnfSnifferDirName + "/snfSniffer";
	SnfSnifferSampleFileName = "/usr/local/sbin/snfSniffer.sample";

	ContentFilterSpec += "     flags=Rq user=snfuser argv=/usr/local/sbin/snfSniffer\n";

        PostfixMainCfPath = "/etc/postfix/main.cf";
        PostfixMasterCfPath = "/etc/postfix/master.cf";
        ReloadMtaCommand = "/usr/local/sbin/postfix reload";

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

	ContentFilterSpec += "     flags=Rq user=snfuser argv=/usr/local/sbin/snfSniffer\n";

	SnfSnifferDirName = "/usr/local/sbin";
	SnfSnifferFileName = SnfSnifferDirName + "/snfSniffer";
	SnfSnifferSampleFileName = "/usr/local/sbin/snfSniffer.sample";
        PostfixMainCfPath = "/usr/local/etc/postfix/main.cf";
        PostfixMasterCfPath = "/usr/local/etc/postfix/master.cf";
        ReloadMtaCommand = "/usr/local/sbin/postfix reload";

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

	PostfixDefaultIsChrooted  = true;
	SnfSnifferFileName = SnfSnifferDirName + "/snfSniffer";
	SnfSnifferSampleFileName = "/usr/sbin/snfSniffer.sample";

	ContentFilterSpec += "     flags=Rq user=snfuser argv=/usr/sbin/snfSniffer\n";

        PostfixMainCfPath = "/etc/postfix/main.cf";
        PostfixMasterCfPath = "/etc/postfix/master.cf";
        ReloadMtaCommand = "/usr/sbin/postfix reload";

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

	ContentFilterSpec += "     flags=Rq user=snfuser argv=/usr/sbin/snfSniffer\n";

        PostfixMainCfPath = "/etc/postfix/main.cf";
        PostfixMasterCfPath = "/etc/postfix/master.cf";
        ReloadMtaCommand = "/usr/sbin/postfix reload";

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

	ContentFilterSpec += "     flags=Rq user=snfuser argv=/usr/sbin/snfSniffer\n";

        PostfixMainCfPath = "/etc/postfix/main.cf";
        PostfixMasterCfPath = "/etc/postfix/master.cf";
        ReloadMtaCommand = "/usr/sbin/postfix reload";

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

	ContentFilterSpec += "     flags=Rq user=snfuser argv=/usr/sbin/snfSniffer\n";

        PostfixMainCfPath = "/etc/postfix/main.cf";
        PostfixMasterCfPath = "/etc/postfix/master.cf";
        ReloadMtaCommand = "/usr/sbin/postfix reload";

    } else {

        std::ostringstream Temp;

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

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

    }

    ContentFilterSpec += "     -f ${sender} -- ${recipient}\n";

}

void
PostfixIntegrate::Integrate(FileBackup *SaveFile) {

    if (IsIntegrated()) {

        return;

    }

    // Check whether the chroot configuration is as expected.
    bool IsChrooted;

    IsChrooted = MtaConfigurationIsChrooted();

    if (IsChrooted != PostfixDefaultIsChrooted) {

	std::string Temp;

	Temp = "Error--postfix must be configured to run ";
	Temp += (PostfixDefaultIsChrooted ? "" : "not ");
	Temp += "chrooted, which is the default for this operating system.  ";
	Temp += "postfix was detected to be configured to run ";
	Temp += (IsChrooted ? "" : "not ");
	Temp += "chrooted.";
	throw std::runtime_error(Temp);

    }

    if (Verbose()) {

        std::cout << "Integrate with postfix...\n";

    }

    std::string Content;

    if (!Explain()) {

        if (!FileExists(SnfSnifferFileName)) {                                  // Create SnfSniffer 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);

	}

        SaveFile->CreateBackupFile(PostfixMasterCfPath);

	std::ifstream Input;

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

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

        std::string Line;

        while (getline(Input, Line)) {

            Content += Line + "\n";                                             // Copy this line.

	    if ( (Line.find("smtp") == 0) && (Line.find("inet") != std::string::npos) ) {

                if (Verbose()) {

                    std::cout << " Add\n\n  "
			      << ContentFilterLine
			      << "\n\n after\n\n"
			      << Line
			      << "\n\n in "
			      << PostfixMasterCfPath << "...\n";

                }

		Content += ContentFilterLine;

            }

        }

        if (!Input.eof()) {                                                     // Should be at end-of-file.
            std::string Temp;

            Temp = "Error reading the postfix configuration file " + PostfixMasterCfPath;
            Temp += ":  ";
            Temp += strerror(errno);
            throw std::runtime_error(Temp);
        }

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

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

	if (Verbose()) {

	    std::cout << " Add\n\n" << ContentFilterSpec << "\n\n to the end of "
		      << PostfixMasterCfPath << "...\n";

	}
	Content += ContentFilterSpec;

        if (!Explain()) {

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

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

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

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

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

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

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

        }

    }

    OutputVerboseEnd();

    if (!ReloadMta()) {

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

    }

}

void
PostfixIntegrate::Unintegrate(FileBackup *SaveFile) {

    if (!IsIntegrated()) {

        return;

    }

    std::ifstream Input;

    if (Verbose()) {

        std::cout << "Remove integration in postfix file " << PostfixMasterCfPath << "--\n";

    }

    if (!Explain()) {

        SaveFile->CreateBackupFile(PostfixMasterCfPath);                        // Save any existing file.

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

            Temp = "Error opening the postfix configuration file " + PostfixMasterCfPath;
            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 postfix configuration file " + PostfixMasterCfPath;
	    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" << ContentFilterLine << "\n\n"
		      << "  from" << PostfixMasterCfPath << "...\n";

	}

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

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

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

	}

	if (Verbose()) {

	    std::cout << "  Remove all occurances of\n\n" << ContentFilterSpec << "\n\n"
		      << "  from" << PostfixMasterCfPath << "...\n";

	}

	IntegrationBegin = std::string::npos;

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

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

	}

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

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

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

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

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

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

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

    }

    OutputVerboseEnd();

    if (!ReloadMta()) {

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

    }

}

bool
PostfixIntegrate::MtaIsRunningDetected() {

    if (Verbose()) {

        std::cout << "Checking whether postfix 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
PostfixIntegrate::ReloadMta() {

    if (!MtaIsRunningDetected()) {

        return true;

    }

    if (Verbose()) {

        std::cout << "Reloading postfix...\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
PostfixIntegrate::IsIntegrated() {

    if (Verbose()) {

        std::cout << "Checking for any SNFServer integration in the postfix file " << PostfixMasterCfPath << "...";

    }

    if (!FileExists(PostfixMasterCfPath)) {

        if (Verbose()) {

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

        }

        OutputVerboseEnd();

        return false;

    }

    std::ifstream Input;

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

        Temp = "Error opening the postfix configuration file " + PostfixMasterCfPath;
        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 postfix configuration file " + PostfixMasterCfPath;
	Temp += ":  ";
	Temp += strerror(errno);
	throw std::runtime_error(Temp);
    }

    std::string Content;

    Content = ContentStream.str();

    bool FoundContentFilterLine = (Content.find(ContentFilterLine) != std::string::npos);
    bool FoundContentFilterSpec = (Content.find(ContentFilterSpec) != std::string::npos);
    bool Integrated = (FoundContentFilterLine || FoundContentFilterSpec);

    if (Verbose()) {

        if (FoundContentFilterLine) {

            std::cout << "found\n\n" << ContentFilterLine << "\n\n";

        }

        if (FoundContentFilterSpec) {

            std::cout << "found\n\n" << ContentFilterSpec << "\n\n";

        }

	if (!Integrated) {

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

	}

    }

    OutputVerboseEnd();

    return Integrated;

}

bool
PostfixIntegrate::DefaultIsChrooted() {

    return PostfixDefaultIsChrooted;

}

bool
PostfixIntegrate::MtaConfigurationIsChrooted() {

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

    File = PostfixMasterCfPath;

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

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

    std::string Line;

    bool ConfigurationIsChrooted = false;

    while (getline(Input, Line)) {

	if (CheckForString(Line, "smtp")) {                                    // Check for smtp line.

	    std::istringstream Buffer(Line);                                   // Parse buffer line.
	    std::string Token[8];

	    for (unsigned int iToken = 0; iToken < 8; iToken++) {

		Buffer >> Token[iToken];

	    }

	    if ( ("y" == Token[4]) || ("-" == Token[4]) ) {

		ConfigurationIsChrooted = true;
		break;

	    }

	}

    }

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

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

    return ConfigurationIsChrooted;

}
