//
// 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.
//
//siggencmdline.cpp
#include "stdsiggen.h"
#include "siggencmdline.h"
#include "core/cmdlineparser.h"

#include "core/archive.h"         // cArchive and friends
#include "fco/signature.h"        // cSignature
#include "fs/fsstrings.h"         // file system related strings
#include "core/usernotify.h"      // for notifying the user of events
#include "core/errorbucketimpl.h" // for the error table
#include "core/fsservices.h"
#include "tw/twstrings.h"
#include "core/displayencoder.h"
#include "siggenstrings.h"

#include <fstream> // for the FileExists() stuff

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

using namespace std;

///////////////////////////////////////////////////////////////////////////////
// Static Functions, Variables --
///////////////////////////////////////////////////////////////////////////////
static void PrintHeader(TSTRING filename);
static bool util_FileExists(const TSTRING& fileName);

//=========================================================================
// GLOBALS
//=========================================================================

const TCHAR* g_szEightyDashes = _T("--------------------------------------------------------------------------------");

///////////////////////////////////////////////////////////////////////////////
//Insulated implementation for cSiggenCmdLine
struct cSiggen_i
{
    cSiggen_i() : mPrintHex(false), mTerseOutput(false)
    {
    }
    ~cSiggen_i();

    //Data Members:
    bool mPrintHex;
    //If this is true, the signatures will be output in hex rather than Base64
    bool mTerseOutput;
    //If this is true, only the signatures will be printed, and the output will only use one line.

    typedef std::list<std::pair<iSignature*, TSTRING> > ListType;
    ListType                                            mSignatures;
    std::list<TSTRING>                                  mFilesToCheck;
    //A list of the files that need signatures generated for them.
};

//Dtor:
cSiggen_i::~cSiggen_i()
{
    cSiggen_i::ListType::iterator i;
    //Delete all the dynamically allocated signature objects.
    for (i = mSignatures.begin(); i != mSignatures.end(); ++i)
    {
        if (((*i).first) != NULL)
            delete ((*i).first);
    }
}

//#############################################################################
// cSiggenCmdLine
//#############################################################################

cSiggenCmdLine::cSiggenCmdLine()
{
    mpData = new cSiggen_i;
}

cSiggenCmdLine::~cSiggenCmdLine()
{
    delete mpData;
}


///////////////////////////////////////////////////////////////////////////////
// InitCmdLineParser -- Make the command line cognizant of siggen's arguments
///////////////////////////////////////////////////////////////////////////////
void cSiggenCmdLine::InitCmdLineParser(cCmdLineParser& parser)
{

    parser.AddArg(HELP, TSTRING(_T("?")), TSTRING(_T("help")), cCmdLineParser::PARAM_NONE);
    //Signatures:
    parser.AddArg(CRC32, TSTRING(_T("C")), TSTRING(_T("CRC32")), cCmdLineParser::PARAM_NONE);
    parser.AddArg(MD5, TSTRING(_T("M")), TSTRING(_T("MD5")), cCmdLineParser::PARAM_NONE);
    parser.AddArg(SHA, TSTRING(_T("S")), TSTRING(_T("SHA")), cCmdLineParser::PARAM_NONE);
    parser.AddArg(HAVAL, TSTRING(_T("H")), TSTRING(_T("HAVAL")), cCmdLineParser::PARAM_NONE);

    //Output switches
    parser.AddArg(ALL, TSTRING(_T("a")), TSTRING(_T("all")), cCmdLineParser::PARAM_NONE);
    parser.AddArg(HEX, TSTRING(_T("h")), TSTRING(_T("hexadecimal")), cCmdLineParser::PARAM_NONE);
    parser.AddArg(TERSE, TSTRING(_T("t")), TSTRING(_T("terse")), cCmdLineParser::PARAM_NONE);

    //file parameters
    parser.AddArg(PARAMS, TSTRING(_T("")), TSTRING(_T("")), cCmdLineParser::PARAM_MANY);
}

int cSiggenCmdLine::Execute()
{
    cFileArchive arch;
    //archive for reading in files
    TCOUT.flags((TCOUT.flags() & ~std::ios::adjustfield) | std::ios::left);
    //align all output to the left.
    int rtn = 0;
    // return value -- by default, it is set to 0 (OK)

    //Check to see if files have been specified.
    if (mpData->mFilesToCheck.empty())
        return 0;

    //Iterate over file list and generate each signature:
    std::list<TSTRING>::iterator fileIter;
    for (fileIter = mpData->mFilesToCheck.begin(); fileIter != mpData->mFilesToCheck.end(); ++fileIter)
    {
        cDisplayEncoder e;
        TSTRING         displayStr = *fileIter;
        e.Encode(displayStr);

        if (!mpData->mTerseOutput)
            PrintHeader(displayStr);

        if (!util_FileExists(*fileIter))
        {
            TCOUT << TSS_GetString(cSiggen, siggen::STR_ERR_NO_FILE) << _T(": ") << displayStr.c_str() << endl;
            rtn = 1;
            continue;
        }

        //
        // ignore this if it is not a plain file...
        //
        cFSStatArgs fileInfo;
        try
        {
            iFSServices::GetInstance()->Stat(*fileIter, fileInfo);
            if (fileInfo.mFileType != cFSStatArgs::TY_FILE)
            {
                // Not a regular file; warn and skip this file.
                //
                TCOUT << displayStr << TSS_GetString(cSiggen, siggen::STR_SIGGEN_NOT_REG_FILE) << std::endl;
                rtn = 1;
                continue;
            }
        }
        catch (eFSServices& e)
        {
            cErrorReporter::PrintErrorMsg(e);
            rtn = 1;
            continue;
        }

        //Prepare the archive for reading from current file (in loop).
        try
        {
            arch.OpenRead((*fileIter).c_str());
        }
        catch (eArchive&)
        {
            TCOUT << TSS_GetString(cSiggen, siggen::STR_ERR_OPEN_FAILED) << _T(": ") << displayStr.c_str() << endl;
            rtn = 1;
            continue;
        }

        //
        // Iterate over the <signature, signature name> list and add each signature to the
        // sig generator
        //
        cArchiveSigGen                                        asg;
        std::list<std::pair<iSignature*, TSTRING> >::iterator sigIter;
        for (sigIter = mpData->mSignatures.begin(); sigIter != mpData->mSignatures.end(); ++sigIter)
            asg.AddSig((*sigIter).first);

        //
        // calculate signatures
        //
        arch.Seek(0, cBidirArchive::BEGINNING);
        asg.CalculateSignatures(arch);
        arch.Close();

        //
        // Iterate over the <signature, signature name> list and output each signature:
        //
        for (sigIter = mpData->mSignatures.begin(); sigIter != mpData->mSignatures.end(); ++sigIter)
        {
            if (!mpData->mTerseOutput)
                TCOUT.width(20);

            //Set the output string to Hex or Base64, depending on the value of mPrintHex
            TSTRING sigStringOut;
            if (mpData->mPrintHex)
                sigStringOut = ((*sigIter).first)->AsStringHex();
            else
                sigStringOut = ((*sigIter).first)->AsString();

            //Output the signatures, include identifiers and newlines only if mTerseOutput is false.
            if (!mpData->mTerseOutput)
                TCOUT << (*sigIter).second.c_str();

            TCOUT << sigStringOut;

            if (!mpData->mTerseOutput)
                TCOUT << endl;
            else
                TCOUT << _T(" ");
            //Output finished for iteration

        } //end for


        //Seperate lines of signatures (for multiple files) with a newline (if terse output)
        if (mpData->mTerseOutput)
            TCOUT << endl;
    }

    if (!mpData->mTerseOutput)
        TCOUT << g_szEightyDashes << _T("\n");

    return rtn;
}

//Interprets the parsed command line and sets the member variables necessary for correct output.
//See cSiggen_i.
//Returns 0 if no file parameters have been passed, otherwise, returns 1.
int cSiggenCmdLine::Init(cCmdLineParser& parser)
{
    cCmdLineIter iter(parser); //iterator for traversing command line
    iter.SeekBegin();
    int  i          = 0; //loop variable
    bool crc_select = false, md5_select = false, sha_select = false, haval_select = false;
    //boolean locals for dealing with ALL switch. (temp.?) fix -DA
    bool switch_present = false;
    int  ret            = 0; //return value. will be false unless some file is specified.

    for (iter.SeekBegin(); !iter.Done(); iter.Next())
    {
        switch (iter.ArgId())
        {
        case HELP:
        {
            return 0;
        }
        case CRC32:
        {
            crc_select = switch_present = true;
            break;
        }
        case MD5:
        {
            md5_select = switch_present = true;
            break;
        }
        case SHA:
        {
            sha_select = switch_present = true;
            break;
        }
        case HAVAL:
        {
            haval_select = switch_present = true;
            break;
        }
        case ALL:
        {
            crc_select = md5_select = sha_select = haval_select = switch_present = true;
            break;
        }
        case HEX:
        {
            mpData->mPrintHex = true;
            break;
        }
        case TERSE:
        {
            mpData->mTerseOutput = true;
            break;
        }
        case PARAMS:
        {
            ret |= 1;
            for (; i < iter.NumParams(); ++i)
            {
                mpData->mFilesToCheck.push_back(TSTRING(iter.ParamAt(i)));
            }
        }
        default:
            break;
        }
    }

    //Default behavior is to print all signatures if no switch is specified.
    if (!switch_present)
        crc_select = md5_select = sha_select = haval_select = true;

    //Push the signatures and their output identifiers onto the mSignature list:
    if (crc_select)
    {
        iSignature* sig_ptr = new cCRC32Signature;
        TSTRING     str     = TSS_GetString(cFS, fs::STR_PROP_CRC32);
        mpData->mSignatures.push_back(std::pair<iSignature*, TSTRING>(sig_ptr, str));
    }
    if (md5_select)
    {
        iSignature* sig_ptr = new cMD5Signature;
        TSTRING     str     = TSS_GetString(cFS, fs::STR_PROP_MD5);
        mpData->mSignatures.push_back(std::pair<iSignature*, TSTRING>(sig_ptr, str));
    }
    if (sha_select)
    {
        iSignature* sig_ptr = new cSHASignature;
        TSTRING     str     = TSS_GetString(cFS, fs::STR_PROP_SHA);
        mpData->mSignatures.push_back(std::pair<iSignature*, TSTRING>(sig_ptr, str));
    }
    if (haval_select)
    {
        iSignature* sig_ptr = new cHAVALSignature;
        TSTRING     str     = TSS_GetString(cFS, fs::STR_PROP_HAVAL);
        mpData->mSignatures.push_back(std::pair<iSignature*, TSTRING>(sig_ptr, str));
    }

    return ret;
}

//Prints a header for each signature.
void PrintHeader(TSTRING filename)
{
    TCOUT << g_szEightyDashes << _T( "\n" );
    TCOUT << _T("Signatures for file: ") << filename.c_str() << _T("\n\n");
}

bool util_FileExists(const TSTRING& fileName)
{
    return _taccess(fileName.c_str(), F_OK) == 0;
}
