/* 
 * Copyright (c) 2001 Secure Software Solutions
 *
 * 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.
 *
 */
// Modified February 17th, 2002 by Mike Ellison (www.code-rock.com)
//              Porting to Win32

#include <stdio.h>
#include <stdlib.h>
#ifndef _MSC_VER
#include <unistd.h>
#else
/* needed for Win32 - mae 02/17/02 */
#include <windows.h>
#include "getopt.h"
#endif

#include <sys/stat.h>
#include <ctype.h>
#include <string.h>
#include "report.h"
#include "version.h"

int     flags     = 0;
int     forcelang = 0;
char *  progname  = "a.out";

#ifndef _MSC_VER
#define XML_DB_BASE LIBDIR "/"
#else
#define XML_DB_BASE
#endif

char *default_files[] =
{
    XML_DB_BASE "rats.xml",
    XML_DB_BASE "rats-c.xml",
    XML_DB_BASE "rats-python.xml",
    XML_DB_BASE "rats-perl.xml",
    XML_DB_BASE "rats-php.xml"
};

#ifdef _MSC_VER
#define strcasecmp  _stricmp

/* forward declare the scanfiles_win32 function under win32 - mae */
int scanfiles_win32(char *filename);
#endif

static
int load_database(char *filename, int silent)
{
    int         result;
    FILE *      fd;
    char *      buf;
    struct stat st;

    /* Carriage returns seem to kill the XMLParser under win32, so just
     * load it in binary mode. - mae
     */
#ifdef _MSC_VER
    if ((fd = fopen(filename, "rb")) == (FILE *)NULL)
#else
    if ((fd = fopen(filename, "r")) == (FILE *)NULL)
#endif
    {
        if (!silent)
        {
            fprintf(stderr, "Unable to open '%s' for reading.\n", filename);
        }
        return 0;
    }

    fstat(fileno(fd), &st);
    buf = (char *)malloc(st.st_size + 1);
    fread(buf, st.st_size, 1, fd);
    *(buf + st.st_size) = '\0';

    result = (ParseVulnDb(buf, &database) != NULL);
    defaultdb = HashGet(database, "default");
    free(buf);
    return result;
}

static
void usage(void)
{
    printf("RATS v%d.%d - Rough Auditing Tool for Security\n", VERSION_MAJOR, VERSION_MINOR);
    printf("Copyright 2001, 2002 Secure Software Solutions\nhttp://www.securesw.com\n\n");
    /* Adding modification tag and new switch -R for recursion */
#ifdef _MSC_VER
    printf("Modified for Win32 02/17/02 by Mike Ellison (www.code-rock.com)\n\n");
    printf("usage: %s [-adhilrwxR] name1 name2 ... namen\n\n", progname);
#else
    printf("usage: %s [-adhilrwx] name1 name2 ... namen\n\n", progname);
#endif
    printf("    -a <fun>       report any occurence of function 'fun' in the source file(s)\n");
    printf("    -d <filename>  specify an alternate vulnerability database.\n");
    printf("    -h             display usage information (what you\'re reading)\n");
    printf("    -i             report functions that accept external input\n");
    printf("    -l <language>  force the specified langauge to be used\n");
    printf("    -r             include references that are not function calls\n");
    printf("    -w <1,2,3>     set warning level (default %d)\n", warning_level);
    printf("    -x             do not load default databases\n");
#ifdef _MSC_VER
    printf("    -R             recurse subdirectories scanning for matching files\n");
#endif
}

int main(int argc, char **argv)
{
    int     dbloaded = 0, i, load_default = 1;
    Vuln_t *uservul = (Vuln_t *)NULL;
    Hash    defh = (Hash)NULL;
#ifdef _MSC_VER
    char *  tslash;
#endif

    progname = argv[0];
#ifdef _MSC_VER
    while ((i = getopt(argc, argv, "a:d:hil:Rrw:x")) != -1)
#else
    while ((i = getopt(argc, argv, "a:d:hil:rw:x")) != -1)
#endif
    {
        switch (i)
        {
            case 'a':
                if (!database)
                    database = HashInit();
                if (!(defh = (Hash)HashGet(database, "default")))
                {
                    defh = HashInit();
                    HashInsert(database, defh, "default");
                }
                uservul = (Vuln_t *)malloc(sizeof(Vuln_t));
                InitVuln(uservul);
                uservul->Name = (char *)malloc(strlen(optarg) + 1);
                strncpy(uservul->Name, optarg, strlen(optarg));
                uservul->Name[strlen(optarg)] = '\0';
                uservul->Info = (Info_t *)malloc(sizeof(Info_t));
                InitInfo(uservul->Info);
                uservul->Info->Severity = Medium;
                uservul->Info->Description = (char *)malloc(34);
                strcpy(uservul->Info->Description, "Reporting user specified function");
 
                HashInsert(defh, uservul, optarg);
                if (!defaultdb)
                    defaultdb = (Hash)HashGet(database, "default");
                break;

            case 'd':
                if (load_database(optarg, 0))
                    dbloaded++;
                break;

            case 'h':
                usage();
                exit(1);
                return 1;

            case 'i':
                flags |= INPUT_MODE;
                break;

            case 'l':
                if (!strcasecmp(optarg, "python"))
                    forcelang = LANG_PYTHON;
                else if (!strcasecmp(optarg, "c"))
                    forcelang = LANG_C;
                else if (!strcasecmp(optarg, "perl"))
                    forcelang = LANG_PERL;
                else if (!strcasecmp(optarg, "php"))
                    forcelang = LANG_PHP;
                else
                    fprintf(stderr, "Language %s unknown, using filename extensions instead\n", optarg);
                break;

            case 'r':
                flags |= INCLUDE_ALL_REFERENCES;
                break;

            case 'w':
                warning_level = 4 - atoi(optarg);
                if (warning_level < 1)
                    warning_level = 1;
                if (warning_level > 3)
                    warning_level = 3;
                break;

            case 'x':
                load_default = 0;
                break;

            case 'R':
                flags |= RECURSIVE_FILE_SCAN;
                break;

            default:
                exit(1);
                return 1;
        }
    }

    if (load_default)
    {
        /* Load the vulnerability database into memory */
        int i;

#ifdef _MSC_VER
        /* Under win32, instead of using LIBDIR, we find the executable path
         * and just use its directory to look for the .xml files. - mae
         */
        char    actualPath[_MAX_PATH + 1];

        if (!GetModuleFileName(GetModuleHandle(NULL), actualPath, _MAX_PATH))
        {
            fprintf(stderr,"Error getting module path under win32?\n");
            exit(1);
            return 1;
        }

        if (!(tslash = strrchr(actualPath, '\\')))
        {
            fprintf(stderr,"Error getting current path under win32?\n");
            exit(1);
            return 1;
        }
        *(tslash + 1) = '\0';
#endif

        for (i = 0;  i < sizeof(default_files) / sizeof(char *);  i++)
        {
#ifdef _MSC_VER
            /* under win32, use the path of the executeable and append the
             * default file to it - mae
             */
            char * curxml;

            curxml = (char *)malloc(strlen(actualPath) +
                                    strlen(default_files[i]) + 1);
            if (!curxml)
            {
                fprintf(stderr,"Out of memory allocating path.\n");
                exit(1);
                return 1;
            }
            strcpy(curxml, actualPath);
            strcat(curxml,default_files[i]);

            if (load_database(curxml, 1))
                dbloaded++;
            free(curxml);
#else
            if (load_database(default_files[i], 1))
                dbloaded++;
#endif
        }
    }

    if (!dbloaded)  
    {
        fprintf(stderr, "No database able to be loaded, exiting.\n");
        exit(1);
        return 1;
    }

    if (optind >= argc)
    {
        process_file("<stdin>", stdin, forcelang);
    }
    else
    {
        while (optind < argc)
        {
            char *  filename;
#ifndef _MSC_VER
            FILE *  fd;
#endif

            filename = argv[optind++];

#ifdef _MSC_VER
            /* Under win32, wildcards for files aren't translated automatically
             * on the command line, so we have to do it ourselves.  I also want
             * to add in a recursive feature to this, so I'm just calling a new
             * function. - mae
             */
            if (!scanfiles_win32(filename))
            {
                fprintf(stderr,"%s: unable to open '%s' for reading.\n", progname, filename);
                continue;
            }
#else
            if ((fd = fopen(filename, "r")) == (FILE *)NULL)
            {
                fprintf(stderr, "%s: unable to open '%s' for reading.\n", progname, filename);
                continue;
            }
            process_file(filename, fd, forcelang);
            fclose(fd);
#endif
        }
    }

    generate_report();

    exit(0);
    return 0;
}

#ifdef _MSC_VER
int scanfiles_win32(char* filename)
{
    int             foundsome = 0;
    HANDLE          findHandle = INVALID_HANDLE_VALUE;
    WIN32_FIND_DATA finddata;
    FILE *          fd;

    char currentDir[_MAX_PATH+1];
    char drive[_MAX_DRIVE];
    char dir[_MAX_DIR];
    char fname[_MAX_PATH];
    char extension[_MAX_EXT];
    char setpath[_MAX_PATH];
    char scanpath[_MAX_PATH];

    /* Save current directory */
    GetCurrentDirectory(_MAX_PATH, currentDir);

    /* Split the incoming filename path */
    _splitpath(filename, drive, dir, fname, extension);

    /* rejoin just the directory */
    strcpy(setpath, drive);
    strcat(setpath, dir);

    /* rejoin extension and name */
    if (extension[0] != '\0')
        strcat(fname, extension);

    /* Go there */
    if (*setpath)
    {
        if (!SetCurrentDirectory(setpath))
        {
            fprintf(stderr, "Invalid path: %s\n", setpath);
            return 0;
        }
    }

    /* Scan for any matching files */
    findHandle = FindFirstFile(fname, &finddata);
    if (findHandle != INVALID_HANDLE_VALUE)
    {
        do
        {
            /* Make sure it's not a directory here */
            if (!(finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
            {
                if ((fd = fopen(finddata.cFileName, "r")) == (FILE *)NULL)
                {
                    fprintf(stderr, "%s: unable to open '%s' for reading.\n", progname, finddata.cFileName);
                    continue;
                }

                process_file(finddata.cFileName, fd, forcelang);
                fclose(fd);
                foundsome++;
            }
        } while (FindNextFile(findHandle,&finddata));

        CloseHandle(findHandle);
    }

    /* Scan for directories if requested */
    if (flags & RECURSIVE_FILE_SCAN)
    {
        findHandle = FindFirstFile("*.*", &finddata);
        if (findHandle)
        {
            do
            {
                /* Only looking at directories now */
                if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
                {
                    /* If it's not '.' or '..' */
                    if ((strcmp(finddata.cFileName, ".")) && (strcmp(finddata.cFileName, "..")))
                    {
                        strcpy(scanpath, finddata.cFileName);
                        strcat(scanpath, "\\");
                        strcat(scanpath, fname);

                        foundsome += scanfiles_win32(scanpath);
                    }
                }
            } while (FindNextFile(findHandle,&finddata));

            CloseHandle(findHandle);
        }
    }

    /* release current directory */
    SetCurrentDirectory(currentDir);
    return foundsome;
}
#endif
