//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000-2018 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
//
// This program is free software.  The contents of this file are subject
// to 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.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
//
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please 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.
//
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
//
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
///////////////////////////////////////////////////////////////////////////////
// twutil.cpp
//

#include "stdtw.h"
#include "twutil.h"

#include "twcrypto/crypto.h"
#include "core/archive.h"
#include "core/serializerimpl.h"
#include "twcrypto/cryptoarchive.h"
#include "fcoreport.h"
#include "twcrypto/keyfile.h"
#include "fco/fcospeclist.h"
#include "fco/genreswitcher.h"
#include "core/errorbucketimpl.h"
#include "core/cmdlineparser.h"
#include "configfile.h"
#include "core/fileheader.h"
#include "core/usernotify.h"
#include "policyfile.h"
#include "core/serstring.h"
#include "headerinfo.h"
#include "systeminfo.h"
#include "core/errorgeneral.h"
#include "fco/parsergenreutil.h"
#include "fco/twfactory.h"
#include "fco/fconame.h"
#include "fcodatabasefile.h"
#include "util/fileutil.h"
#include "core/stringutil.h"
#include "tw/twstrings.h"
#include "tw/twerrors.h"
#include "core/ntmbs.h"
#include "core/displayencoder.h"
#include "core/tw_signal.h"

#ifdef TW_PROFILE
#include "core/tasktimer.h"
#endif

#include <unistd.h>
#include <fcntl.h>
#if SUPPORTS_TERMIOS
#include <termios.h>
#include <sys/ioctl.h>
int _getch(void);
#endif


// constants
static const char*  POLICY_FILE_MAGIC_8BYTE = "#POLTXT\n";
static const char*  CONFIG_FILE_MAGIC_8BYTE = "#CFGTXT\n";
static const uint32 CURRENT_FIXED_VERSION   = 0x02020000;


///////////////////////////////////////////////////////////////////////////////
// WriteObjectToArchive -- called from WriteObject, does most of the work
///////////////////////////////////////////////////////////////////////////////
static void WriteObjectToArchive(cArchive&                 arch,
                                 const TCHAR*              filename, // filename is used only for issuing error messages
                                 const iTypedSerializable* pObjHeader,
                                 const iTypedSerializable& obj,
                                 cFileHeader&              fileHeader,
                                 bool                      bEncrypt,
                                 const cElGamalSigPrivateKey* pPrivateKey)
{
    try
    {
        // Set file version.
        // If we in the future we wish to support reading databases of different versions,
        // we will have to move this set to outside WriteObject().
        fileHeader.SetVersion(CURRENT_FIXED_VERSION);

        fileHeader.SetEncoding(bEncrypt ? cFileHeader::ASYM_ENCRYPTION : cFileHeader::COMPRESSED);

        {
            cSerializerImpl fhSer(arch, cSerializerImpl::S_WRITE, filename);
            fileHeader.Write(&fhSer);
        }

        if (bEncrypt)
        {
            cElGamalSigArchive cryptoArchive;
            cryptoArchive.SetWrite(&arch, pPrivateKey);
            cSerializerImpl ser(cryptoArchive, cSerializerImpl::S_WRITE, filename);
            ser.Init();
            if (pObjHeader)
                ser.WriteObject(pObjHeader);
            ser.WriteObject(&obj);
            ser.Finit();
            cryptoArchive.FlushWrite();
        }
        else
        {
            // not encrypted
            cNullCryptoArchive cryptoArchive;
            cryptoArchive.Start(&arch);
            cSerializerImpl ser(cryptoArchive, cSerializerImpl::S_WRITE, filename);
            ser.Init();
            if (pObjHeader)
                ser.WriteObject(pObjHeader);
            ser.WriteObject(&obj);
            ser.Finit();
            cryptoArchive.Finish();
        }
    }
    catch (eError& e)
    {
        throw ePoly(e.GetID(), cErrorUtil::MakeFileError(e.GetMsg(), filename), e.GetFlags());
    }
}

///////////////////////////////////////////////////////////////////////////////
// WriteObject -- template class that writes an object to disk (ie -- report,
//      db, etc) either encrypted or unencrypted.
//      The only requrement on the object is that it is typed serializable.
//      errorMsg is a number that can be passed to iUserString to indicate an
//      appropriate error message if the object fails to load
//
// 10/30 -- this function has been expanded to take two objects -- a header and
//      an object. Both are typed serializable, but the header can be NULL if
//      none is desired.
///////////////////////////////////////////////////////////////////////////////
static void WriteObject(const TCHAR*                 filename,
                        const iTypedSerializable*    pObjHeader,
                        const iTypedSerializable&    obj,
                        cFileHeader&                 fileHeader,
                        bool                         bEncrypt,
                        const cElGamalSigPrivateKey* pPrivateKey)
{
    cDebug d("WriteObject");
    d.TraceDebug(_T("Writing %s to file %s\n"), obj.GetType().AsString(), filename);

    ASSERT(pPrivateKey || (!bEncrypt));

    cFileArchive arch;

    if (!cFileUtil::IsRegularFile(filename) && cFileUtil::FileExists(filename))
        throw eArchiveNotRegularFile(filename);

    try
    {
        arch.OpenReadWrite(filename);
    }
    catch (eArchive&)
    {
        // probably better to rethrow this as a write failed exception
        throw eArchiveWrite(filename, iFSServices::GetInstance()->GetErrString());
    }

    WriteObjectToArchive(arch, filename, pObjHeader, obj, fileHeader, bEncrypt, pPrivateKey);

    arch.Close();
}


///////////////////////////////////////////////////////////////////////////////
// WriteObjectToArchive -- called from WriteObject, does most of the work
///////////////////////////////////////////////////////////////////////////////
static void ReadObjectFromArchive(cArchive&                   arch,
                                  const TCHAR*                objFileName,
                                  iTypedSerializable*         pObjHeader,
                                  iTypedSerializable&         obj,
                                  const cFileHeaderID&        fhid,
                                  const cElGamalSigPublicKey* pPublicKey,
                                  bool&                       bEncrypted)
{
    cFileHeader fileHeader;

    {
        cSerializerImpl fhSer(arch, cSerializerImpl::S_READ, objFileName);
        fileHeader.Read(&fhSer);
    }

    // check for a mismatched header
    if (fileHeader.GetID() != fhid)
        ThrowAndAssert(eSerializerInputStreamFmt(_T(""), objFileName, eSerializer::TY_FILE));

    // Check file version.
    // If we in the future we wish to support reading objects of different versions,
    // we will have to move this check to outside ReadObject().
    if (fileHeader.GetVersion() != CURRENT_FIXED_VERSION)
        ThrowAndAssert(eSerializerVersionMismatch(_T(""), objFileName, eSerializer::TY_FILE));

    try
    {
        // switch on the type of encoding...
        if (fileHeader.GetEncoding() == cFileHeader::ASYM_ENCRYPTION)
        {
            // tell the user the db is encrypted
            iUserNotify::GetInstance()->Notify(iUserNotify::V_VERBOSE,
                                               TSS_GetString(cTW, tw::STR_FILE_ENCRYPTED).c_str());
            bEncrypted = true;

            if (pPublicKey == 0)
                ThrowAndAssert(eSerializerEncryption(_T("")));

            cElGamalSigArchive cryptoArchive;
            cryptoArchive.SetRead(&arch, pPublicKey);

            cSerializerImpl ser(cryptoArchive, cSerializerImpl::S_READ, objFileName);
            ser.Init();
            if (pObjHeader)
                ser.ReadObject(pObjHeader);
            ser.ReadObject(&obj);
            ser.Finit();
        }
        else if (fileHeader.GetEncoding() == cFileHeader::COMPRESSED)
        {
            //not encrypted db...
            bEncrypted = false;

            cNullCryptoArchive cryptoArchive;
            cryptoArchive.Start(&arch);

            cSerializerImpl ser(cryptoArchive, cSerializerImpl::S_READ, objFileName);
            ser.Init();
            if (pObjHeader)
                ser.ReadObject(pObjHeader);
            ser.ReadObject(&obj);
            ser.Finit();
        }
        else
            // unknown encoding...
            ThrowAndAssert(eSerializerInputStreamFmt(_T("")));
    }
    catch (eError& e)
    {
        // include filename in error msg
        throw ePoly(e.GetID(), cErrorUtil::MakeFileError(e.GetMsg(), objFileName), e.GetFlags());
    }
}

///////////////////////////////////////////////////////////////////////////////
// ReadObject -- writes an object from disk, either encrypted or not, that was
//      written using WriteObject above.
///////////////////////////////////////////////////////////////////////////////
static void ReadObject(const TCHAR*                objFileName,
                       iTypedSerializable*         pObjHeader,
                       iTypedSerializable&         obj,
                       const cFileHeaderID&        fhid,
                       const cElGamalSigPublicKey* pPublicKey,
                       bool&                       bEncrypted)
{
    cDebug d("ReadObject");
    d.TraceDebug(_T("Reading %s from file %s\n"), obj.GetType().AsString(), objFileName);

    cFileArchive arch;
    arch.OpenRead(objFileName);

    ReadObjectFromArchive(arch, objFileName, pObjHeader, obj, fhid, pPublicKey, bEncrypted);
}

///////////////////////////////////////////////////////////////////////////////
// IsObjectEncrypted
///////////////////////////////////////////////////////////////////////////////
bool cTWUtil::IsObjectEncrypted(const TCHAR* objFileName, const cFileHeaderID& fhid, const TSTRING& errorMsg)
{
    bool   fEncrypted = false;
    cDebug d("IsObjectEncrypted");
    d.TraceDebug(_T("Reading from file %s\n"), objFileName);

    try
    {
        cFileArchive arch;
        arch.OpenRead(objFileName);

        cFileHeader     fileHeader;
        cSerializerImpl fhSer(arch, cSerializerImpl::S_READ, objFileName);
        fileHeader.Read(&fhSer);

        // check for a mismatched header
        if (fileHeader.GetID() != fhid)
            ThrowAndAssert(eSerializerInputStreamFmt(_T(""), objFileName, eSerializer::TY_FILE));

        // switch on the type of encoding...
        if (fileHeader.GetEncoding() == cFileHeader::ASYM_ENCRYPTION)
        {
            fEncrypted = true;
        }
        else if (fileHeader.GetEncoding() == cFileHeader::COMPRESSED)
        {
            fEncrypted = false;
        }
        else
            // unknown encoding...
            ThrowAndAssert(eSerializerInputStreamFmt(_T(""), objFileName, eSerializer::TY_FILE));
    }
    catch (eArchive& e)
    {
        // Note: Output to TCERR is O.K. here, it is documented that this is what this function does
        TSTRING msg = e.GetMsg();
        if (!msg.empty())
            msg += _T("\n");
        msg += errorMsg;

        cTWUtil::PrintErrorMsg(ePoly(e.GetID(), msg, e.GetFlags()));
        ThrowAndAssert(ePoly());
    }
    catch (eSerializer& e)
    {
        // Note: Output to TCERR is O.K. here, it is documented that this is what this function does
        TSTRING msg = e.GetMsg();
        if (!msg.empty())
            msg += _T("\n");
        msg += errorMsg;

        cTWUtil::PrintErrorMsg(ePoly(e.GetID(), msg, e.GetFlags()));
        ThrowAndAssert(ePoly());
    }

    return (fEncrypted);
}


///////////////////////////////////////////////////////////////////////////////
// WriteDatabase
///////////////////////////////////////////////////////////////////////////////
void cTWUtil::WriteDatabase(const TCHAR*                 filename,
                            cFCODatabaseFile&            db,
                            bool                         bEncrypt,
                            const cElGamalSigPrivateKey* pPrivateKey)
{
    cFileHeader fileHeader;
    fileHeader.SetID(db.GetFileHeaderID());

    // I am almost positive that this does nothing, WriteObject() sets the version in the cFileHeader - Jun 8, 1999 - dmb
    //fileHeader.SetVersion(1);

#ifdef TW_PROFILE
    cTaskTimer timer(_T("Write Database"));
    timer.Start();
#endif

    WriteObject(filename, 0, db, fileHeader, bEncrypt, pPrivateKey);

#ifdef TW_PROFILE
    timer.Stop();
#endif

    iUserNotify::GetInstance()->Notify(iUserNotify::V_NORMAL,
                                       _T("%s%s\n"),
                                       TSS_GetString(cTW, tw::STR_WRITE_DB_FILE).c_str(),
                                       cDisplayEncoder::EncodeInline(filename).c_str());
}

///////////////////////////////////////////////////////////////////////////////
// ReadDatabase
///////////////////////////////////////////////////////////////////////////////
void cTWUtil::ReadDatabase(const TCHAR*                filename,
                           cFCODatabaseFile&           db,
                           const cElGamalSigPublicKey* pPublicKey,
                           bool&                       bEncrypted)
{
    iUserNotify::GetInstance()->Notify(iUserNotify::V_VERBOSE,
                                       _T("%s%s\n"),
                                       TSS_GetString(cTW, tw::STR_OPEN_DB_FILE).c_str(),
                                       cDisplayEncoder::EncodeInline(filename).c_str());

#ifdef TW_PROFILE
    cTaskTimer timer("cTWUtil::ReadDatabase");
    timer.Start();
#endif

    ReadObject(filename, 0, db, cFCODatabaseFile::GetFileHeaderID(), pPublicKey, bEncrypted);

#ifdef TW_PROFILE
    timer.Stop();
#endif
}

///////////////////////////////////////////////////////////////////////////////
// WriteReport
///////////////////////////////////////////////////////////////////////////////
void cTWUtil::WriteReport(const TCHAR*                 filename,
                          const cFCOReportHeader&      reportHeader,
                          const cFCOReport&            r,
                          bool                         bEncrypt,
                          const cElGamalSigPrivateKey* pPrivateKey)
{
    cFileHeader fileHeader;
    fileHeader.SetID(cFCOReport::GetFileHeaderID());

    WriteObject(filename, &reportHeader, r, fileHeader, bEncrypt, pPrivateKey);

    iUserNotify::GetInstance()->Notify(iUserNotify::V_NORMAL,
                                       _T("%s%s\n"),
                                       TSS_GetString(cTW, tw::STR_WRITE_REPORT_FILE).c_str(),
                                       cDisplayEncoder::EncodeInline(filename).c_str());
}


///////////////////////////////////////////////////////////////////////////////
// ReadReport
///////////////////////////////////////////////////////////////////////////////
void cTWUtil::ReadReport(const TCHAR*                reportFileName,
                         cFCOReportHeader&           reportHeader,
                         cFCOReport&                 r,
                         const cElGamalSigPublicKey* pPublicKey,
                         bool                        silent,
                         bool&                       bEncrypted)
{
    iUserNotify::GetInstance()->Notify(iUserNotify::V_VERBOSE,
                                       _T("%s%s\n"),
                                       TSS_GetString(cTW, tw::STR_OPEN_REPORT_FILE).c_str(),
                                       cDisplayEncoder::EncodeInline(reportFileName).c_str());

    ReadObject(reportFileName, &reportHeader, r, cFCOReport::GetFileHeaderID(), pPublicKey, bEncrypted);
}


///////////////////////////////////////////////////////////////////////////////
// UpdatePolicyFile
///////////////////////////////////////////////////////////////////////////////
void cTWUtil::UpdatePolicyFile(const TCHAR*                 destFileName,
                               const TCHAR*                 srcFileName,
                               bool                         bEncrypt,
                               const cElGamalSigPrivateKey* pPrivateKey)
{
    cFileArchive src;
    src.OpenRead(srcFileName); // note: eArchive may float up

    std::string policyText;
    policyText.resize(src.Length());

    src.ReadBlob((void*)policyText.data(), src.Length());

    // NOTE -- this method will emit a message to stdout when the file has been written
    cTWUtil::WritePolicyText(destFileName, policyText, bEncrypt, pPrivateKey);
}

TSTRING cTWUtil::GetCfgFilePath(const cCmdLineParser& cmdLine, int cfgFileID)
{

    TSTRING cfgFilePath;
    bool    fConfigOnCmdLine = false;

    // let's see if this was specified on the command line...
    cCmdLineIter iter(cmdLine);
    for (iter.SeekBegin(); !iter.Done(); iter.Next())
    {
        if (iter.ArgId() == cfgFileID)
        {
            ASSERT(iter.NumParams() > 0);
            cfgFilePath      = iter.ParamAt(0);
            fConfigOnCmdLine = true;
        }
    }

    // OK, now we need to figure out where to find the config file.
    // If the location was specified on the command line, get the full path to it.
    // otherwise, the location is the dir that this exe is in.
    if (fConfigOnCmdLine)
    {
        TSTRING pathOut;
        iFSServices::GetInstance()->FullPath(pathOut, cfgFilePath);
        cfgFilePath = pathOut;
    }
    else
    {
        iFSServices::GetInstance()->FullPath(
            cfgFilePath, TSS_GetString(cTW, tw::STR_DEF_CFG_FILENAME), cSystemInfo::GetExeDir());
    }


    cFileUtil::TestFileExists(cfgFilePath);


    return (cfgFilePath);
}

///////////////////////////////////////////////////////////////////////////////
// OpenConfigFile -- opens the config file, either from a known location or
//      from the location specified on the command line. Returns false if it fails.
//      cfgFileID is the ID of the config file switch
//      TODO -- should this guy spit the error to cerr or fill up an error bucket?
/////////////////////////////////////////////////////////////////////////////////
void cTWUtil::OpenConfigFile(
    cConfigFile& cf, cCmdLineParser& cmdLine, int cfgFileID, cErrorBucket& errorBucket, TSTRING& configFileOut)
{

    configFileOut = GetCfgFilePath(cmdLine, cfgFileID);

    // ok, now let's load it!
    TSTRING configText;
    cTWUtil::ReadConfigText(configFileOut.c_str(), configText);
    cf.ReadString(configText);
}

///////////////////////////////////////////////////////////////////////////////
// WriteConfigText
//      eArchive is thrown if filename can not be opened
//      eSerializer is thrown if reading or writing fails

void cTWUtil::WriteConfigText(const TCHAR*                 filename,
                              const TSTRING                configText,
                              bool                         bEncrypt,
                              const cElGamalSigPrivateKey* pPrivateKey)
{
    cSerializableNString nstring;

    nstring.mString = CONFIG_FILE_MAGIC_8BYTE;

    std::string ns;
    cStringUtil::Convert(ns, configText);
    nstring.mString += ns;

    cFileHeader fileHeader;
    fileHeader.SetID(cConfigFile::GetFileHeaderID());

    fileHeader.SetVersion(CURRENT_FIXED_VERSION);

    if (bEncrypt)
    {
        ASSERT(pPrivateKey != 0);
        cElGamalSigPublicKey publicKey(*pPrivateKey);
        fileHeader.GetBaggage().MapArchive(0, publicKey.GetWriteLen());
        publicKey.Write(fileHeader.GetBaggage().GetMap());
    }

    WriteObject(filename, NULL, nstring, fileHeader, bEncrypt, pPrivateKey);

    iUserNotify::GetInstance()->Notify(iUserNotify::V_NORMAL,
                                       _T("%s%s\n"),
                                       TSS_GetString(cTW, tw::STR_WRITE_CONFIG_FILE).c_str(),
                                       cDisplayEncoder::EncodeInline(filename).c_str());
}


///////////////////////////////////////////////////////////////////////////////
// ReadConfigText
//      eArchive is thrown if filename can not be opened
//      eSerializer is thrown if reading or writing fails
//      eConfigFile is thrown if config file does not parse correctly during reading

void cTWUtil::ReadConfigText(const TCHAR* filename, TSTRING& configText, cArchive* pBaggage)
{
    // TODO -- neat up this function; try to use LoadObject() above...

    cSerializableNString nstring;

    // This was coppied from ReadObject().  We need to use the baggage of the
    // file header to obtain the public key, thus the special casing.
    cDebug d("ReadConfigText");
    d.TraceDebug(_T("Reading %s from file %s\n"), nstring.GetType().AsString(), filename);

    iUserNotify::GetInstance()->Notify(iUserNotify::V_VERBOSE,
                                       _T("%s%s\n"),
                                       TSS_GetString(cTW, tw::STR_OPEN_CONFIG_FILE).c_str(),
                                       cDisplayEncoder::EncodeInline(filename).c_str());

    cFileArchive arch;
    arch.OpenRead(filename);

    cFileHeader fileHeader;

    try
    {
        cSerializerImpl fhSer(arch, cSerializerImpl::S_READ);
        fileHeader.Read(&fhSer);
    }
    catch (eError&)
    {
        throw eSerializerInputStreamFmt(_T(""), filename, eSerializer::TY_FILE);
    }

#if 0 // XXX: This is broken, what the h*ll are they trying to write here? -PH
    d.TraceDebug("Found a file header of type %d.\n", fileHeader.GetEncoding());
#endif

    // check for a mismatched header
    if (fileHeader.GetID() != cConfigFile::GetFileHeaderID())
        throw eSerializerInputStreamFmt(_T(""), filename, eSerializer::TY_FILE);

    // check the version
    if (fileHeader.GetVersion() != CURRENT_FIXED_VERSION)
        throw eSerializerVersionMismatch(_T(""), filename, eSerializer::TY_FILE);

    // switch on the type of encoding...
    if (fileHeader.GetEncoding() == cFileHeader::ASYM_ENCRYPTION)
    {
        d.TraceDebug("Config file is compressed, public key len %d.\n", fileHeader.GetBaggage().Length());

        // tell the user the db is encrypted
        iUserNotify::GetInstance()->Notify(iUserNotify::V_VERBOSE, TSS_GetString(cTW, tw::STR_FILE_ENCRYPTED).c_str());
        iUserNotify::GetInstance()->Notify(iUserNotify::V_VERBOSE, TSS_GetString(cTW, tw::STR_NEWLINE).c_str());

        ASSERT(fileHeader.GetBaggage().Length() > 0);
        if (fileHeader.GetBaggage().Length() <= 0)
            ThrowAndAssert(eSerializerInputStreamFmt(_T(""), filename, eSerializer::TY_FILE));

        fileHeader.GetBaggage().MapArchive(0, fileHeader.GetBaggage().Length());

        cElGamalSigPublicKey publicKey(fileHeader.GetBaggage().GetMap());

        cElGamalSigArchive cryptoArchive;
        cryptoArchive.SetRead(&arch, &publicKey);

        cSerializerImpl ser(cryptoArchive, cSerializerImpl::S_READ);
        ser.Init();
        ser.ReadObject(&nstring);
        ser.Finit();

        // copy the baggage into the archive, if it was passed in
        // Note: We rely in VerifySiteKey that we only fill out pBaggage if
        // the config file is encrypted.
        //
        if (pBaggage)
        {
            fileHeader.GetBaggage().Seek(0, cBidirArchive::BEGINNING);
            pBaggage->Copy(&fileHeader.GetBaggage(), fileHeader.GetBaggage().Length());
        }
    }
    else if (fileHeader.GetEncoding() == cFileHeader::COMPRESSED)
    {
        d.TraceDebug("Config file is not compressed.\n");

        //not encrypted db...
        cNullCryptoArchive cryptoArchive;
        cryptoArchive.Start(&arch);

        cSerializerImpl ser(cryptoArchive, cSerializerImpl::S_READ);
        ser.Init();
        ser.ReadObject(&nstring);
        ser.Finit();
    }
    else
        // unknown encoding...
        throw eSerializerInputStreamFmt(_T(""), filename, eSerializer::TY_FILE);

    // check 8 byte header
    if (nstring.mString.compare(0, 8 * sizeof(byte), CONFIG_FILE_MAGIC_8BYTE) != 0)
        ThrowAndAssert(eSerializerInputStreamFmt(_T(""), filename, eSerializer::TY_FILE));

    // remove 8 byte header
    nstring.mString.assign(nstring.mString.substr(8));

    cStringUtil::Convert(configText, nstring.mString);
}


///////////////////////////////////////////////////////////////////////////////
// Given a filename and the text of a policy file, write an encrypted version
// of the policy file text to disk.
// Will throw eError on failure.

void cTWUtil::WritePolicyText(const TCHAR*                 filename,
                              const std::string&           polText,
                              bool                         bEncrypt,
                              const cElGamalSigPrivateKey* pPrivateKey)
{
    cSerializableNString nstring;

    // add a 8 byte header we can use to verify decryption
    nstring.mString = POLICY_FILE_MAGIC_8BYTE;

    nstring.mString += polText;

    cFileHeader fileHeader;
    fileHeader.SetID(cPolicyFile::GetFileHeaderID());

    WriteObject(filename, NULL, nstring, fileHeader, bEncrypt, pPrivateKey);

    iUserNotify::GetInstance()->Notify(iUserNotify::V_NORMAL,
                                       _T("%s%s\n"),
                                       TSS_GetString(cTW, tw::STR_WRITE_POLICY_FILE).c_str(),
                                       cDisplayEncoder::EncodeInline(filename).c_str());
}


///////////////////////////////////////////////////////////////////////////////
// ReadPolicyText
//
// Read the policy file.  Read the text of a policy language into configText
// Will throw eError on failure.
///////////////////////////////////////////////////////////////////////////////
void cTWUtil::ReadPolicyText(const TCHAR* filename, std::string& polText, const cElGamalSigPublicKey* pPublicKey)
{
    cSerializableNString nstring;

    cFileUtil::TestFileExists(filename);

    bool bEncrypted;
    ReadObject(filename, NULL, nstring, cPolicyFile::GetFileHeaderID(), pPublicKey, bEncrypted);

    // check 8 byte header
    if (nstring.mString.compare(0, 8 * sizeof(byte), POLICY_FILE_MAGIC_8BYTE) != 0)
        ThrowAndAssert(eSerializerInputStreamFmt(_T(""), filename, eSerializer::TY_FILE));

    // remove 8 byte header
    nstring.mString.assign(nstring.mString.substr(8));

    polText = nstring.mString;
}


///////////////////////////////////////////////////////////////////////////////
// OpenKeyFile
///////////////////////////////////////////////////////////////////////////////
void cTWUtil::OpenKeyFile(cKeyFile& keyFile, TSTRING fileName)
{
    // first, make sure the file exists...
    cFileUtil::TestFileExists(fileName);

    iUserNotify::GetInstance()->Notify(iUserNotify::V_VERBOSE,
                                       _T("%s%s\n"),
                                       TSS_GetString(cTW, tw::STR_OPEN_KEYFILE).c_str(),
                                       cDisplayEncoder::EncodeInline(fileName).c_str());

    keyFile.ReadFile(fileName.c_str());
}

///////////////////////////////////////////////////////////////////////////////
// CreatePrivateKey
//      we will attempt to get the correct passphrase three times before we
//      give up.
///////////////////////////////////////////////////////////////////////////////
const cElGamalSigPrivateKey*
cTWUtil::CreatePrivateKey(cKeyFile& keyFile, const WCHAR16* usePassphrase, KeyType keyType, int nSecs)
{
    ASSERT(keyType == KEY_SITE || keyType == KEY_LOCAL || keyType == KEY_PROVIDED);

    const cElGamalSigPrivateKey* pPrivateKey = NULL;
    wc16_string                  passphrase;

    if (usePassphrase)
    {
        // sleep to hinder brute force (dictionary, etc.) attacks
        iFSServices::GetInstance()->Sleep(nSecs);

        passphrase = usePassphrase;

#ifndef WORDS_BIGENDIAN
        passphrase.swapbytes();
#endif

        pPrivateKey = keyFile.GetPrivateKey((int8*)passphrase.data(), passphrase.length() * sizeof(WCHAR16));

        if (pPrivateKey)
            return pPrivateKey;

        // if we got here, then a passphrase was provided on the command line that
        // was not correct; this is an error condition.
        //
        if (keyType == KEY_LOCAL)
            throw eTWUtilBadPassLocal();
        else
            throw eTWUtilBadPassSite();
    }

    int count = 0;
    while (count < 3)
    {
        cTWUtil::NoEcho noEcho;
        switch (keyType)
        {
        case KEY_LOCAL:
            TCOUT << TSS_GetString(cTW, tw::STR_ENTER_LOCAL_PASSPHRASE);
            break;
        case KEY_SITE:
            TCOUT << TSS_GetString(cTW, tw::STR_ENTER_SITE_PASSPHRASE);
            break;
        case KEY_PROVIDED:
        default:
            TCOUT << TSS_GetString(cTW, tw::STR_ENTER_PROVIDED_PASSPHRASE);
            break;
        }

        cTWUtil::GetString(passphrase);
        TCOUT << std::endl;

        // sleep to hinder brute force (dictionary, etc.) attacks
        iFSServices::GetInstance()->Sleep(nSecs);

#ifndef WORDS_BIGENDIAN
        passphrase.swapbytes();
#endif

        pPrivateKey = keyFile.GetPrivateKey((int8*)passphrase.data(), passphrase.length() * sizeof(WCHAR16));

        if (pPrivateKey)
            break;

        // tell the user that they entered the wrong passphrase
        int strId = (keyType == KEY_LOCAL) ? tw::STR_ERR_WRONG_PASSPHRASE_LOCAL : tw::STR_ERR_WRONG_PASSPHRASE_SITE;
        TCOUT << TSS_GetString(cTW, strId) << std::endl;
        passphrase.resize(0);
        count++;
    }

    if (!pPrivateKey)
        throw ePoly();

    return pPrivateKey;
}

void cTWUtil::CreatePrivateKey(
    cPrivateKeyProxy& proxy, cKeyFile& keyFile, const WCHAR16* usePassphrase, KeyType keyType, int nSecs)
{
    ASSERT(keyType == KEY_SITE || keyType == KEY_LOCAL || keyType == KEY_PROVIDED);

    wc16_string passphrase;

    if (usePassphrase != 0)
    {
        // sleep to hinder brute force (dictionary, etc.) attacks
        iFSServices::GetInstance()->Sleep(nSecs);

        passphrase = usePassphrase;

#ifndef WORDS_BIGENDIAN
        passphrase.swapbytes();
#endif

        if (proxy.AquireKey(keyFile, (int8*)passphrase.data(), passphrase.length() * sizeof(WCHAR16)))
            return;

        // if we got here, then a passphrase was provided on the command line that
        // was not correct; this is an error condition.
        //
        if (keyType == KEY_LOCAL)
            throw eTWUtilBadPassLocal();
        else
            throw eTWUtilBadPassSite();
    }

    int count = 0;
    while (count < 3)
    {
        cTWUtil::NoEcho noEcho;
        switch (keyType)
        {
        case KEY_LOCAL:
            TCOUT << TSS_GetString(cTW, tw::STR_ENTER_LOCAL_PASSPHRASE);
            break;
        case KEY_SITE:
            TCOUT << TSS_GetString(cTW, tw::STR_ENTER_SITE_PASSPHRASE);
            break;
        case KEY_PROVIDED:
        default:
            TCOUT << TSS_GetString(cTW, tw::STR_ENTER_PROVIDED_PASSPHRASE);
            break;
        }

        cTWUtil::GetString(passphrase);
        TCOUT << std::endl;

        // sleep to hinder brute force (dictionary, etc.) attacks
        iFSServices::GetInstance()->Sleep(nSecs);

#ifndef WORDS_BIGENDIAN
        passphrase.swapbytes();
#endif

        if (proxy.AquireKey(keyFile, (int8*)passphrase.data(), passphrase.length() * sizeof(WCHAR16)))
            return;

        // tell the user that they entered the wrong passphrase
        int strId = (keyType == KEY_LOCAL) ?
                        tw::STR_ERR_WRONG_PASSPHRASE_LOCAL :
                        (keyType == KEY_SITE) ? tw::STR_ERR_WRONG_PASSPHRASE_SITE :
                                                tw::STR_ERR_WRONG_PASSPHRASE_LOCAL; // TODO: make this provided

        TCOUT << TSS_GetString(cTW, strId) << std::endl;
        passphrase.resize(0);
        count++;
    }

    throw ePoly();
}

///////////////////////////////////////////////////////////////////////////////
// GetStringNoEcho -- Get a string from the user without echoing it
///////////////////////////////////////////////////////////////////////////////

static void (*old_SIGINT)(int);
static void (*old_SIGQUIT)(int);

#if SUPPORTS_TERMIOS
static struct termios Otty;
#endif


static void RestoreEcho(int sig)
{
#if SUPPORTS_TERMIOS
#    ifdef DEBUG
    std::cout << "Caught signal, resetting echo." << std::endl;
    sleep(2);
#    endif

    tcsetattr(0, TCSAFLUSH, &Otty);
    tw_signal(SIGINT, old_SIGINT);
    tw_signal(SIGQUIT, old_SIGQUIT);
#endif
    tw_raise(sig);
}

void cTWUtil::GetString(wc16_string& ret)
{
#ifdef _UNICODE
#    error We depend on Unix not being Unicode
#endif

// tcsetattr doesn't seem to work on AROS, so tweak ANSI terminal settings to hide passphrases.
#if IS_AROS
    printf("\e[8m"); // set the 'concealed' flag
    fflush(stdout);
#endif

    // get the string
    const int MAX_STRING = 1024;
    char      buf[MAX_STRING];
    int       len;

    TCOUT.flush();
    len = read(0, buf, MAX_STRING);

#if IS_AROS
    printf("\e[0m"); // reset back to normal text
    fflush(stdout);
#endif

    // TODO:BAM -- mb-enable this!
    if (len < MAX_STRING - 1)
        buf[len] = 0;

    char* psz = tss::strdec(buf, buf + len);
    if (*psz == '\n')
        *psz = 0;

    ret = cStringUtil::TstrToWstr(buf);
}


cTWUtil::NoEcho::NoEcho()
{
#if SUPPORTS_TERMIOS
    // set the terminal to no echo mode
    static struct termios Ntty;

    fflush(stdout);
    tcgetattr(0, &Otty);
    Ntty = Otty;

    Ntty.c_lflag &= ~ECHO;

    // catch SIGINT and SIGQUIT
    old_SIGINT  = tw_signal(SIGINT, RestoreEcho);
    old_SIGQUIT = tw_signal(SIGQUIT, RestoreEcho);

    if (tcsetattr(0, TCSAFLUSH, &Ntty) != 0 && isatty(0))
    {
        ThrowAndAssert(eTWUtilEchoModeSet());
    }
#endif
}

cTWUtil::NoEcho::~NoEcho()
{
#if SUPPORTS_TERMIOS
    tcsetattr(0, TCSAFLUSH, &Otty);
    tw_signal(SIGINT, old_SIGINT);
    tw_signal(SIGQUIT, old_SIGQUIT);
#endif
}

void cTWUtil::GetStringNoEcho(wc16_string& ret)
{
    NoEcho noEcho;
    GetString(ret);
}

///////////////////////////////////////////////////////////////////////////////
// ParseObjectList
///////////////////////////////////////////////////////////////////////////////
void cTWUtil::ParseObjectList(cTWUtil::GenreObjList& listOut, const cTWUtil::ObjList& listIn)
{

    cGenre::Genre curGenre = cGenreSwitcher::GetInstance()->GetDefaultGenre();

    ASSERT(listIn.size() > 0);
    listOut.clear();
    GenreObjList::iterator curIter   = listOut.end();
    cGenre::Genre          iterGenre = cGenre::GENRE_INVALID;

    // iterate over all of the input...
    //
    for (ObjList::const_iterator i = listIn.begin(); i != listIn.end(); ++i)
    {
        // first, try to interperate the current string as a genre name...
        // 17 Mar 99 mdb -- we now only do this if the string ends in a ':'
        //
        cGenre::Genre g = cGenre::GENRE_INVALID;

        if (i->at(i->length() - 1) == _T(':'))
        {
            TSTRING genreStr;
            genreStr.assign(i->begin(), i->end() - 1);
            g = cGenreSwitcher::GetInstance()->StringToGenre(genreStr.c_str());

            //
            // if it is not a valid genre name, then test to see if it could
            // be an fconame.  If it is not, then it is a badly formed genre.
            //
            if (g == cGenre::GENRE_INVALID)
            {
                TW_UNIQUE_PTR<iParserGenreUtil> pParseUtil(iTWFactory::GetInstance()->CreateParserGenreUtil());
                if (!pParseUtil->IsAbsolutePath(*i))
                    throw eTWUnknownSectionName(*i);
            }
        }
        if (g == cGenre::GENRE_INVALID)
        {
            // assume that we are in the correct genre; this is a fully qualified object name
            // TODO -- throw here if cGenreParserHelper says it isn't a fully qualified name.
            //
            if (iterGenre != curGenre)
            {
                // seek to right list; create it if it is not there...
                //
                for (curIter = listOut.begin(); curIter != listOut.end(); ++curIter)
                {
                    if (curIter->first == curGenre)
                        break;
                }
                if (curIter == listOut.end())
                {
                    // it doesn't exist; we will have to create a new one.
                    //
                    listOut.push_back(GenreObjs());
                    listOut.back().first = curGenre;
                    curIter              = listOut.end() - 1;
                }

                iterGenre = curGenre;
            }
            ASSERT(curIter != listOut.end());
            //
            // add this to the list; assert that it has not been added yet.
            //
            ObjList::iterator oi;
            for (oi = curIter->second.begin(); oi != curIter->second.end(); ++oi)
            {
                if (*oi == *i)
                {
                    //TODO -- what should I do here? probably warn and continue...
                    ASSERT(false);
                    break;
                }
            }
            if (oi == curIter->second.end())
                curIter->second.push_back(*i);
        }
        else
        {
            // set the current genre to this and continue...
            //
            curGenre = g;
        }
    }
}


///////////////////////////////////////////////////////////////////////////////
// ParseObjectName
///////////////////////////////////////////////////////////////////////////////
cFCOName cTWUtil::ParseObjectName(const TSTRING& fcoName)
{
    TW_UNIQUE_PTR<iParserGenreUtil> pParseUtil(iTWFactory::GetInstance()->CreateParserGenreUtil());
    cFCOName                        name(iTWFactory::GetInstance()->GetNameInfo());
    //
    // make sure the fco name is a full path...
    //
    if (!pParseUtil->IsAbsolutePath(fcoName))
        throw eTWUtilNotFullPath(fcoName);
    //
    // construct the list that InterpretFCOName needs....
    std::list<TSTRING> inputNameList;
    //
    // dice up the string into a list
    // currently, we only slice it up based on "|" (for nt registry entries)
    //
    TSTRING::size_type pos = fcoName.find_first_of(_T('|'));
    if (pos != TSTRING::npos)
    {
        // if the input string is "foo|bar" then we want the list to
        // look like this: "foo", "|", "bar" (three entries)
        //
        TSTRING str;
        str.assign(fcoName, 0, pos);
        inputNameList.push_back(str);
        inputNameList.push_back(_T("|"));
        str.assign(fcoName, pos + 1, fcoName.length() - pos);
        inputNameList.push_back(str);
    }
    else
    {
        inputNameList.push_back(fcoName);
    }

    pParseUtil->InterpretFCOName(inputNameList, name);

    return name;
}


///////////////////////////////////////////////////////////////////////////////
// Database and report header helpers
///////////////////////////////////////////////////////////////////////////////

TSTRING cTWUtil::GetSystemName()
{
    TSTRING ret;
    iFSServices::GetInstance()->GetMachineName(ret);
    return ret;
}

TSTRING cTWUtil::GetIPAddress()
{
    uint32 ipaddress;
    if (iFSServices::GetInstance()->GetIPAddress(ipaddress) == false)
        return TSS_GetString(cTW, tw::STR_IP_UNKNOWN);

    // convert to host byte-order
    ipaddress = tw_ntohl(ipaddress);

    TOSTRINGSTREAM ret;
    ret << (int)(ipaddress >> 24) << _T(".") << (int)((ipaddress >> 16) & 0xff) << _T(".")
        << (int)((ipaddress >> 8) & 0xff) << _T(".") << (int)(ipaddress & 0xff);

    return ret.str();
}

TSTRING cTWUtil::GetCurrentUser()
{
    TSTRING ret;
    iFSServices::GetInstance()->GetCurrentUserName(ret);
    return ret;
}

TSTRING cTWUtil::GetHostID()
{
    TSTRING ret;
    iFSServices::GetInstance()->GetHostID(ret);
    return ret;
}

///////////////////////////////////////////////////////////////////////////////
// PrintErrorMsg
///////////////////////////////////////////////////////////////////////////////
void cTWUtil::PrintErrorMsg(const eError& e, const TSTRING& strExtra)
{
    ASSERT(e.GetID() != 0); // NOTE: BAM  5/9/99 -- there should no longer be an ID of ZERO

    /* BAM 5/9/99 -- this is old stuff
    if((e.GetID() == 0) && (e.GetMsg().empty()))
    {
        // this should only occur at the top level of a program (ie -- twcmdline.cpp) and
        // indicates that an error occurred and an error message has already been printed out.
        // Therefore, we will do nothing here but return.
        return;
    }
    */

    cErrorReporter::PrintErrorMsg(e, strExtra);
}

///////////////////////////////////////////////////////////////////////////////
// ConfirmYN(const TCHAR* prompt)
//
// prompt the user with given string and wait for a 'Y' or 'N' response.
// returns true if 'Y' entered.
///////////////////////////////////////////////////////////////////////////////
bool cTWUtil::ConfirmYN(const TCHAR* prompt)
{
    wc16_string reply;
    TSTRING     s;
    int         x;

    while (1)
    {
        TCOUT << prompt;
        GetString(reply);

        cStringUtil::Convert(s, reply);

#ifdef UNICODE
        for (x = 0; s[x] && iswctype(s[x], wctype("space")); x++)
            ;
#else
        for (x = 0; s[x] && std::isspace<TCHAR>(s[x], std::locale()); x++)
            ;
#endif

        if (_totupper(s[x]) == _T('Y'))
            return true;
        else if (_totupper(s[x]) == _T('N'))
            return false;
    }
}

///////////////////////////////////////////////////////////////////////////////
// VerifySiteKey
///////////////////////////////////////////////////////////////////////////////
bool cTWUtil::VerifyCfgSiteKey(const TSTRING& strConfigFile, const TSTRING& siteKeyPath)
{
    // open the config file
    //
    cMemoryArchive memArch;
    try
    {
        TSTRING dummyString;
        cTWUtil::ReadConfigText(strConfigFile.c_str(), dummyString, &memArch);
    }
    catch (eArchive& e)
    {
        // if the file is corrupted, eArchive may be thrown.
        // For sanity sake we need to re-throw this as eTWUtil
        TSTRING estr;
        estr.assign(TSS_GetString(cCore, core::STR_ERROR_FILENAME));
        estr.append(strConfigFile);
        estr.append(TSS_GetString(cCore, core::STR_NEWLINE));
        estr.append(e.GetMsg());

        throw eTWUtilCorruptedFile(estr);
    }

    // only do the test if there is baggage (indicating the cfg file is encrypted)
    //
    ASSERT(memArch.Length() >= 0);
    if (memArch.Length() <= 0)
        return false;

    // get site public key
    //
    cKeyFile siteKeyfile;
    cTWUtil::OpenKeyFile(siteKeyfile, siteKeyPath);

    // create the two public keys...
    //
    memArch.Seek(0, cBidirArchive::BEGINNING);
    cElGamalSigPublicKey pubKey(memArch.GetMemory());

    // compare the two ....
    //
    if (!pubKey.IsEqual(*siteKeyfile.GetPublicKey()))
    {
        TSTRING estr;
        estr.assign(TSS_GetString(cTW, tw::STR_ERR2_CFG_KEY_MISMATCH1));
        estr.append(strConfigFile);
        estr.append(TSS_GetString(cTW, tw::STR_ERR2_CFG_KEY_MISMATCH2));
        estr.append(siteKeyPath);
        estr.append(TSS_GetString(cTW, tw::STR_ERR2_CFG_KEY_MISMATCH3));

        throw eTWUtilCfgKeyMismatch(estr);
    }

    return true;
}
