/* 

                          Firewall Builder

                 Copyright (C) 2011 NetCitadel, LLC

  Author:  Vadim Kurland     vadim@fwbuilder.org

  This program is free software which we release under the GNU General Public
  License. You may redistribute and/or modify this program under the terms
  of that 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.
 
  To get a copy of the GNU General Public License, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#include "PreImport.h"

#include <QRegExp>

#include <functional>

using namespace std;


class matchPFDirectionIn : public matchPFDirection
{
public:
    virtual bool operator()(const QString &str)
    {
        return str.contains(" in ");
    }
};

class matchPFDirectionOut : public matchPFDirection
{
public:
    virtual bool operator()(const QString &str)
    {
        return str.contains(" out ");
    }
};

class matchPFDirectionBoth : public matchPFDirection
{
public:
    virtual bool operator()(const QString &str)
    {
        return ! str.contains(" in ") && ! str.contains(" out ");
    }
};


void PreImport::scan()
{
    QList<QRegExp> pix_re;
    pix_re << QRegExp("^ASA Version")
           << QRegExp("^PIX Version")
           << QRegExp("^FWSM Version")
           << QRegExp("^nat \\(\\S+,\\S+\\)")
           << QRegExp("^static \\(\\S+,\\S+\\)")
           << QRegExp("^global \\(")
           << QRegExp("^nameif \\S+")
           << QRegExp("^fixup \\S+");

    QList<QRegExp> fwsm_re;
    fwsm_re << QRegExp("^FWSM Version");

    QList<QRegExp> ios_re;
    ios_re << QRegExp("IOS Version")
           << QRegExp("^[vV]ersion 1[012]\\..*");

    QList<QRegExp> iptables_re;
    iptables_re << QRegExp("# Generated by iptables-save")
                << QRegExp("^:INPUT ")
                << QRegExp("^:OUTPUT ")
                << QRegExp("^:FORWARD ")
                << QRegExp("^-A INPUT ")
                << QRegExp("^-A OUTPUT ")
                << QRegExp("^-A FORWARD ");
            
    QList<QRegExp> iptables_with_counters_re;
    iptables_with_counters_re << QRegExp("^\\[\\d+:\\d+\\] -A INPUT ")
                              << QRegExp("^\\[\\d+:\\d+\\] -A OUTPUT ")
                              << QRegExp("^\\[\\d+:\\d+\\] -A FORWARD ");

    QList<QRegExp> pf_conf_re;
    pf_conf_re << QRegExp("^scrub\\s+\\S+")
               << QRegExp("^set\\s+timeout\\s+\\S+")
               << QRegExp("^pass\\s+")
               << QRegExp("^block\\s+")
               << QRegExp("^nat\\s+(?!\\()")
               << QRegExp("^rdr\\s+(?!\\()")
               << QRegExp("^table\\s+<\\S+>\\s+");


    foreach (QString line, *buffer)
    {
        if (platform == UNKNOWN)
        {
            foreach (QRegExp re, pix_re)
            {
                if (re.indexIn(line) > -1)
                {
                    platform = PIX;
                    break;
                }
            }

            foreach (QRegExp re, fwsm_re)
            {
                if (re.indexIn(line) > -1)
                {
                    platform = FWSM;
                    break;
                }
            }

            foreach (QRegExp re, ios_re)
            {
                if (re.indexIn(line) > -1)
                {
                    platform = IOSACL;
                    break;
                }
            }

            foreach (QRegExp re, iptables_re)
            {
                if (re.indexIn(line) > -1)
                {
                    platform = IPTABLES;
                    break;
                }
            }

            foreach (QRegExp re, pf_conf_re)
            {
                if (re.indexIn(line) > -1)
                {
                    platform = PF;
                    break;
                }
            }
        }

        if (platform == IPTABLES)
        {
            foreach (QRegExp re, iptables_with_counters_re)
            {
                if (re.indexIn(line) > -1)
                {
                    platform = IPTABLES_WITH_COUNTERS;
                    break;
                }
            }
        }
    }

    /*
     * fwbuilder generates PF configuration that always uses "quick"
     * keyword to make the first matching rule stop processing. A lot
     * of existing pf.conf files use the other model where PF commands
     * do not use this keyword, so that all rules inspect the packet
     * and the last matching rule makes the decision. Fwbuilder can
     * not generate PF configuration in this style and can not import
     * it. We look for "block" command without "quick" parameter to
     * determine if the configuration offered for import is written in
     * this style.

     * We refuse to import policies that have "block" line with no
     * "quick" word, unless there are other command(s) with "quick"
     * after it. We should do this comparison keeping direction in
     * mind because it is possible to have "block in all" and then
     * "pass out quick something". It looks like a style with "block
     * all" at the top used to set up default policy is quite
     * popular. Configuration written in this style has "block all
     * log" at the top (or in the middle), followed by a bunch of
     * specific "pass quick" rules. We can import this if "block all
     * log" is the last rule, but not if it is followed by some pass
     * rules with no "quick".
     */

    if (platform == PF)
    {
        matchPFDirectionIn   dir_in;
        matchPFDirectionOut  dir_out;
        matchPFDirectionBoth dir_both;

        if (isReversePFConfigurationStyle(dir_in)  ||
            isReversePFConfigurationStyle(dir_out) ||
            isReversePFConfigurationStyle(dir_both))
        {
            platform = PF_REVERSE;
        }
    }
}

bool PreImport::isReversePFConfigurationStyle(matchPFDirection &dir_op)
{
    bool has_block_no_quick = false;
    bool has_command_with_quick_after_block = false;
    bool has_command_with_no_quick_after_block = false;
    QRegExp cont("\\\\\\s*\n");
    QString line;
    QStringListIterator it(*buffer);
    while (it.hasNext())
    {
        // first, unfold lines ending with "\"
        line = it.next();
        int cont_idx;
        while ( (cont_idx = cont.indexIn(line)) > -1 && it.hasNext())
        {
            line.insert(cont_idx, it.next());
        }

        line = line.trimmed();

        if (line.startsWith("#")) continue;
        if (line.isEmpty()) continue;

        if ( ! dir_op(line)) continue;

        if (line.contains(" quick"))
        {
            // check if after the line with "block" and no "quick"
            // comes a line with action "pass" and "quick" word.
            // This is a mixed-style policy and we can try to
            // import it.

            if (has_block_no_quick &&
                (line.startsWith("pass ") || line.startsWith("block ")))
            {
                has_command_with_quick_after_block = true;
                continue;
            }

        } else
        {
            // check if this is a line with action "block" and no
            // "quick" word
            if (line.startsWith("block "))
            {
                has_block_no_quick = true;
                continue;
            }

            if (has_block_no_quick)
            {
                has_command_with_no_quick_after_block = true;
                break;
            }
        }
    }

    return (has_block_no_quick && has_command_with_no_quick_after_block &&
            ! has_command_with_quick_after_block);
}

QString PreImport::getPlatformAsString()
{
    QString platform_string;

    switch (platform)
    {
    case PreImport::UNKNOWN:
        platform_string = "";
        break;

    case PreImport::PIX:
        platform_string = "pix";
        break;

    case PreImport::FWSM:
        platform_string = "fwsm";
        break;

    case PreImport::IOSACL:
        platform_string = "iosacl";
        break;

    case PreImport::IPTABLES:
    case PreImport::IPTABLES_WITH_COUNTERS:
        platform_string = "iptables";
        break;

    case PreImport::PF:
    case PreImport::PF_REVERSE:
        platform_string = "pf";
        break;
    }

    return platform_string;
}

