/* 
 * 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.
 *
 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "report.h"

int warning_level = 2;

static input_t *            input_head  = (input_t *)NULL;
static input_t *            input_tail  = (input_t *)NULL;
static ignore_t *           ignore_list = (ignore_t *)NULL;
static vulnerability_t *    list_head   = (vulnerability_t *)NULL;
static vulnerability_t *    list_tail   = (vulnerability_t *)NULL;

static int lookup_ignore(char *filename, int lineno, char *token);

static
void insert_vulnerability(vulnerability_t *log)
{
    int                 insert = 0;
    vulnerability_t *   ptr;

    for (ptr = list_head;  ptr != (vulnerability_t *)NULL;  ptr = ptr->next)
    {
        if (ptr->severity < log->severity)
        {
            insert = 1;
            ptr = ptr->prev;
            break;
        }
        if (ptr->type == log->type && ptr->data == log->data)
        {
            for (ptr = ptr->next;  ptr != (vulnerability_t *)NULL;  ptr = ptr->next)
            {
                if (ptr->type != log->type || ptr->data != log->data)
                {
                    ptr = ptr->prev;
                    break;
                }
            }
            break;
        }
    }
    if (ptr == (vulnerability_t *)NULL && !insert)
        ptr = list_tail;

    log->next = (ptr == (vulnerability_t *)NULL ? list_head : ptr->next);
    log->prev = ptr;

    if (log->next != (vulnerability_t *)NULL)
        log->next->prev = log;
    else
        list_tail = log;
    if (log->prev != (vulnerability_t *)NULL)
        log->prev->next = log;
    else
        list_head = log;
}

void log_toctou(toctou_t **table, int first, int last, int check)
{
    int                 i;
    vulnerability_t *   log;

    if (check != -1)
    {
        int count = 0, index = 0;

        for (i = first;  i <= last;  i++)
            count += (table[i]->use);

        log = (vulnerability_t *)malloc(sizeof(vulnerability_t));
        log->filename = current_file;
        log->lineno   = table[check]->lineno;
        log->data     = table[check]->data;
        log->type     = RaceConditionCheck;

        if (count > 0)
        {
            log->severity = Medium;
            log->uses     = (toctou_use_t *)malloc(sizeof(toctou_use_t) * (count + 1));

            for (i = first;  i <= last;  i++)
            {
                if (table[i]->use)
                {
                    log->uses[index].name   = table[i]->data->Name;
                    log->uses[index].lineno = table[i]->lineno;
                    index++;
                }
            }
            log->uses[index].name   = (char *)NULL;
            log->uses[index].lineno = 0;
        }
        else
        {
            log->severity = Low;
            log->uses     = (toctou_use_t *)NULL;
        }
        insert_vulnerability(log);
    }
    else
    {
        for (i = first;  i <= last;  i++)
        {
            log = (vulnerability_t *)malloc(sizeof(vulnerability_t));
            log->filename = current_file;
            log->lineno   = table[i]->lineno;
            log->data     = table[i]->data;
            log->type     = RaceConditionUse;
            log->severity = Low;
            log->uses     = (toctou_use_t *)NULL;

            insert_vulnerability(log);
        }
    }
}

void log_vulnerability(type_t type, Severity_t severity)
{
    vulnerability_t *   log;

    log = (vulnerability_t *)malloc(sizeof(vulnerability_t));
    log->filename = current_file;
    log->lineno   = current_frame->lineno;
    log->data     = current_frame->data;
    log->type     = type;
    log->severity = severity;
    log->uses     = (toctou_use_t *)NULL;

    insert_vulnerability(log);
}

void log_perlbacktick(int lineno, Severity_t severity)
{
    vulnerability_t *   log;

    log = (vulnerability_t *)malloc(sizeof(vulnerability_t));
    log->filename = current_file;
    log->lineno   = lineno;
    log->data     = (Vuln_t *)NULL;
    log->type     = PerlBacktick;
    log->severity = severity;
    log->uses     = (toctou_use_t *)NULL;

    insert_vulnerability(log);
}


void log_phpbacktick(int lineno, Severity_t severity)
{
    vulnerability_t *   log;

    log = (vulnerability_t *)malloc(sizeof(vulnerability_t));
    log->filename = current_file;
    log->lineno   = lineno;
    log->data     = (Vuln_t *)NULL;
    log->type     = PhpBacktick;
    log->severity = severity;
    log->uses     = (toctou_use_t *)NULL;

    insert_vulnerability(log);
}

void log_pythonbacktick(int lineno, Severity_t severity)
{
    vulnerability_t *   log;

    log = (vulnerability_t *)malloc(sizeof(vulnerability_t));
    log->filename = current_file;
    log->lineno   = lineno;
    log->data     = (Vuln_t *)NULL;
    log->type     = PythonBacktick;
    log->severity = severity;
    log->uses     = (toctou_use_t *)NULL;

    insert_vulnerability(log);
}

void log_staticbuffer(type_t type, int lineno, Severity_t severity)
{
    vulnerability_t *   log;

    log = (vulnerability_t *)malloc(sizeof(vulnerability_t));
    log->filename = current_file;
    log->lineno   = lineno;
    log->data     = (Vuln_t *)NULL;
    log->type     = type;
    log->severity = severity;
    log->uses     = (toctou_use_t *)NULL;

    insert_vulnerability(log);
}

void record_input(void)
{
    input_t *   input;

    input = (input_t *)malloc(sizeof(input_t));
    input->filename = current_file;
    input->lineno   = current_frame->lineno;
    input->data     = current_frame->data;
    input->next     = (input_t *)NULL;

    if (input_tail != (input_t *)NULL)
        input_tail->next = input;
    else
        input_head = input;
    input_tail = input;
}

static
void cleanup_string(char *str)
{
    int     len;
    char *  c;

    /* strip off leading a trailing whitespace */
    for (c = str;  *c && isspace(*c);  c++);
    for (len = strlen(c);  len > 0 && isspace(*(c + len - 1));  len--);
    *(c + len) = '\0';
    memmove(str, c, len + 1);

    /* squash occurences of multiple whitespace characters to a single one */
    for (c = str + 1;  *c;  c++)
    {
        if (isspace(*c) && isspace(*(c - 1)))
        {
            char *  start;

            for (start = c++;  isspace(*c);  c++);
            memmove(start, c, (len + 1) - (c - str));
            len -= (c - start);
            *(start - 1) = ' ';
        }
    }
}

static char *severities[] = { "Default", "Low", "Medium", "High" };

static
void report_vulnerability(vulnerability_t *ptr)
{
    int i;

    switch (ptr->type)
    {
        case BOProblem:
            if (ptr->data->BOProblem->FormatArg > 0)
            {
                printf("Check to be sure that the format string passed as argument %d to this function\n", ptr->data->BOProblem->FormatArg);
                printf("call does not come from an untrusted source that could have added formatting\n");
                printf("characters that the code is not prepared to handle.  Additionally, the format\n");
                printf("string could contain `%%s' without precision that could result in a buffer\n");
                printf("overflow.\n");
            }
            if (ptr->data->BOProblem->SrcBufArg > 0)
            {
                printf("Check to be sure that argument %d passed to this function call will not copy\n", ptr->data->BOProblem->SrcBufArg);
                printf("more data than can be handled, resulting in a buffer overflow.\n");
            }
            printf("\n");
            break;

        case FSProblem:
            printf("Check to be sure that the non-constant format string passed as argument %d to\n", ptr->data->FSProblem->Arg);
            printf("this function call does not come from an untrusted source that could have added\n");
            printf("formatting characters that the code is not prepared to handle.\n\n");
            break;

        case InputProblem:
            printf("Argument %d to this function call should be checked to ensure that it does not\n", ptr->data->InputProblem->Arg);
            printf("come from an untrusted source without first verifying that it contains nothing\n");
            printf("dangerous.\n\n");
            break;

        case Info:
            if (ptr->data->Info->Description != (char *)NULL)
            {
                cleanup_string(ptr->data->Info->Description);
                printf("%s\n", ptr->data->Info->Description);
            }
            if (ptr->data->Info->URL != (char *)NULL)
            {
                cleanup_string(ptr->data->Info->URL);
                printf("See also: %s\n", ptr->data->Info->URL);
            }
            printf("\n");
            break;

        case RaceConditionCheck:
            printf("A potential TOCTOU (Time Of Check, Time Of Use) vulnerability exists.  This is\n");
            printf("the first line where a check has occured.");
            if (ptr->uses != (toctou_use_t *)NULL && ptr->uses[0].lineno != 0)
            {
                printf("\nThe following line(s) contain uses that may match up with this check:\n");
                for (i = 0;  ptr->uses[i].lineno != 0;  i++)
                    printf("%s%d (%s)", (i == 0 ? "" : ", "), ptr->uses[i].lineno, ptr->uses[i].name);
                printf("\n");
            }
            else
            {
                printf("  No matching uses were detected.\n");
            }
            printf("\n");
            break;

        case RaceConditionUse:
            printf("A potential race condition vulnerability exists here.  Normally a call to this\n");
            printf("function is vulnerable only when a match check precedes it.  No check was\n");
            printf("detected, however one could still exist that could not be detected.\n\n");
            break;

        case StaticLocalBuffer:
            printf("Extra care should be taken to ensure that character arrays that are allocated\n");
            printf("on the stack are used safely.  They are prime targets for buffer overflow\n");
            printf("attacks.\n\n");
            break;

        case StaticGlobalBuffer:
            printf("Extra care should be taken to ensure that character arrays that are allocated\n");
            printf("with a static size are used safely.  This appears to be a global allocation\n");
            printf("and is less dangerous than a similar one on the stack.  Extra caution is still\n");
            printf("advised, however.\n\n");
            break;

        case Reference:
            printf("A function call is not being made here, but a reference is being made to a name\n");
            printf("that is normally a vulnerable function.  It could be being assigned as a\n");
            printf("pointer to function.\n\n");
            break;

        case PythonBacktick:
            printf("Do not use a variable that has been derived from untrusted sources within a backtick.\n");
            printf("Doing so could allow an attacker to execute arbitrary python code\n\n");
            break;

        case PhpBacktick:
        case PerlBacktick:
            printf("The backtick will act just like an call to exec(), so care should be exercised that the\n");
            printf(" string being backtick evaluated does not come from an untrusted source\n\n");
            break;

        case None:
            printf("Unknown!?!?\n\n");
            break;
    }
}

static
void report_inputs(void)
{
    int         count = 0;
    input_t *   next;
    input_t *   ptr;

    if (!(flags & INPUT_MODE))
        return;

    for (ptr = input_head;  ptr != (input_t *)NULL;  ptr = next)
    {
        next = ptr->next;
        if (!lookup_ignore(ptr->filename, ptr->lineno, ptr->data->Name))
        {
            count++;
            printf("%s: %d: %s\n", ptr->filename, ptr->lineno, ptr->data->Name);
        }
        free(ptr);
    }
    input_head = input_tail = (input_t *)NULL;

    if (count > 0)
    {
        printf("Double check to be sure that all input accepted from an external data source\n");
        printf("does not exceed the limits of the variable being used to hold it.  Also make\n");
        printf("sure that the input cannot be used in such a manner as to alter your program's\n");
        printf("behaviour in an undesirable way.\n\n");
    }
}

void generate_report(void)
{
    char *              lookup;
    char *              name;
    char *              name2 = (char *)NULL;
    ignore_t *          iptr;
    ignore_t *          inext;
    vulnerability_t *   ptr;
    vulnerability_t *   next;

    for (ptr = list_head;  ptr != (vulnerability_t *)NULL;  ptr = ptr->next)
    {
        if (ptr->severity == Default || ptr->severity >= warning_level)
        {
            switch (ptr->type)
            {
                case BOProblem:
                case FSProblem:
                case Info:
                case InputProblem:
                case RaceConditionCheck:
                case RaceConditionUse:
                    name = lookup = ptr->data->Name;
                    break;

                case StaticLocalBuffer:
                    name = "fixed size local buffer";
                    lookup = "$fixed_buffer$";
                    break;

                case StaticGlobalBuffer:
                    name = "fixed size global buffer";
                    lookup = "$global_buffer$";
                    break;

                case Reference:
                    name = "non-function call reference";
                    name2 = lookup = ptr->data->Name;
                    break;
               
                case PythonBacktick: 
                    name = "backtick";
                    lookup = "$python_backtick$";
                    break;

                case PhpBacktick:
                    name = "backtick";
                    lookup = "$php_backtick$";
                    break;

                case PerlBacktick:
                    name = "backtick";
                    lookup = "$perl_backtick$";
                    break;

                case None:
                default:
                    name = "Unknown / Database Error";
                    lookup = (char *)NULL;
                    break;
            }
            if (lookup != (char *)NULL)
            {
                if (lookup_ignore(ptr->filename, ptr->lineno, lookup))
                    continue;
            }
            if (name2 == (char *)NULL)
                printf("%s:%d: %s: %s\n", ptr->filename, ptr->lineno, severities[ptr->severity], name);
            else
                printf("%s:%d: %s: %s: %s\n", ptr->filename, ptr->lineno, severities[ptr->severity], name, name2);
            if (ptr->next == (vulnerability_t *)NULL || ptr->next->type != ptr->type ||
                ptr->type == RaceConditionCheck || ptr->next->data != ptr->data)
            {
                report_vulnerability(ptr);
            }
        }
    }

    report_inputs();

    for (iptr = ignore_list;  iptr != (ignore_t *)NULL;  iptr = inext)
    {
        inext = iptr->next;
        if (iptr->token != (char *)NULL)
            free(iptr->token);
        free(iptr);
    }
    ignore_list = (ignore_t *)NULL;

    for (ptr = list_head;  ptr != (vulnerability_t *)NULL;  ptr = next)
    {
        next = ptr->next;
        free(ptr);
    }
    list_head = list_tail = (vulnerability_t *)NULL;
}

ignore_t *new_ignore(int lineno, char *token)
{
    ignore_t *  ign;

    if ((ign = (ignore_t *)malloc(sizeof(ignore_t))) == (ignore_t *)NULL)
        return (ignore_t *)NULL;
    ign->filename = current_file;
    ign->lineno   = lineno;
    ign->token    = (token == (char *)NULL ? token : strdup(token));
    ign->next     = ignore_list;
    ignore_list   = ign;

    return ign;
}

static
int lookup_ignore(char *filename, int lineno, char *token)
{
    ignore_t *  ptr;

    for (ptr = ignore_list;  ptr != (ignore_t *)NULL;  ptr = ptr->next)
    {
        if (ptr->filename != filename)  /* yes, this is safe and will work */
            continue;
        if (ptr->lineno != lineno)
            continue;
        if (ptr->token == (char *)NULL || !strcmp(ptr->token, token))
            return 1;
    }

    return 0;
}
