/* 

                          Firewall Builder

                 Copyright (C) 2007 NetCitadel, LLC

  Author:  Vadim Kurland     vadim@fwbuilder.org

  $Id$

  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

*/


/*
Note about negation:

this grammar parses only extrapositioned negation "! -s address" for
all recognized options that can have it. Intrapositioned negation ("-s ! address")
should be converted to extrapositioned in IPTImporter class before running
the parser.

*/

header "pre_include_hpp"
{
    // gets inserted before antlr generated includes in the header
    // file
#include "IPTImporter.h"
}

header "post_include_hpp"
{
    // gets inserted after antlr generated includes in the header file
    // outside any generated namespace specifications

#include <sstream>

class IPTImporter;
}

header "pre_include_cpp"
{
    // gets inserted before the antlr generated includes in the cpp
    // file
}

header "post_include_cpp"
{
    // gets inserted after the antlr generated includes in the cpp
    // file
#include <antlr/Token.hpp>
#include <antlr/TokenBuffer.hpp>

#include "fwbuilder/TCPService.h"
#include "fwbuilder/Logger.h"

#include <algorithm>
}

header
{
    // gets inserted after generated namespace specifications in the
    // header file. But outside the generated class.
}

options
{
	language="Cpp";
}


class IPTCfgParser extends Parser;
options
{
    k = 2;
//    defaultErrorHandler=false;
}
{
// additional methods and members

    public:
    
    std::ostream *dbg;
    IPTImporter *importer;

    /// Parser error-reporting function can be overridden in subclass
    virtual void reportError(const ANTLR_USE_NAMESPACE(antlr)RecognitionException& ex)
    {
        importer->addMessageToLog("Parser error: " + ex.toString());
        std::cerr << ex.toString() << std::endl;
    }

    /// Parser error-reporting function can be overridden in subclass
    virtual void reportError(const ANTLR_USE_NAMESPACE(std)string& s)
    {
        importer->addMessageToLog("Parser error: " + s);
        std::cerr << s << std::endl;
    }

    /// Parser warning-reporting function can be overridden in subclass
    virtual void reportWarning(const ANTLR_USE_NAMESPACE(std)string& s)
    {
        importer->addMessageToLog("Parser warning: " + s);
        std::cerr << s << std::endl;
    }

}

cfgfile :
        (
            comment
        |
            start_table
        |
            create_chain
        |
            add_rule
        |
            commit
        |
            NEWLINE
        )+
    ;

//****************************************************************

// comment can be iptables-save header or a regular comment
// Examples of the header:
//
// # Generated by iptables-save v1.3.5 on Mon Apr 11 15:32:22 2011
// # Generated by iptables-save v1.4.1.1 on Fri Mar  4 12:14:55 2011
//
comment :  
        NUMBER_SIGN
        (
            IPTABLES_SAVE_HEADER
            ( THREE_COMPONENT_VERSION | IPV4 )
            {
                importer->setCurrentLineNumber(LT(0)->getLine());
                std::string version = LT(0)->getText();
                importer->setDiscoveredVersion(version);
                *dbg << "VERSION " << version << std::endl;
                consumeUntil(NEWLINE);
            }
        )*
        {
            consumeUntil(NEWLINE);
        }
    ;

//****************************************************************

//****************************************************************

commit : COMMIT
        {
            // push last rule
            importer->pushRule();
            *dbg << " COMMIT" << std::endl;
            // clear current table
            importer->current_table = "";
        }
    ;

//****************************************************************

start_table : STAR WORD
        {
            if (!importer->current_table.empty())
            {
                // we did not see COMMIT
                *(importer->logger) << "********************************\n";
                *(importer->logger) << "Missing COMMIT for the table " << importer->current_table << "\n";
                *(importer->logger) << "Perhaps the file is broken ?" << "\n";
                *(importer->logger) << "********************************\n";

                *dbg << "Missing COMMIT for the table " << importer->current_table;
                *dbg << "Perhaps the file is broken ?";

                // push last rule
                importer->pushRule();
                // clear current table
                importer->current_table = "";
            }
            importer->registerTable(LT(0)->getText());
            *dbg << "TABLE " << LT(0)->getText() << std::endl;
        }
    ;

//****************************************************************
chain_def : (INPUT | FORWARD | OUTPUT | PREROUTING | POSTROUTING | WORD)
    ;

//****************************************************************
//
// :INPUT DROP [2:104]
// :FORWARD DROP [0:0]
// :OUTPUT DROP [1:1492]
// :Cid4089E41E.0 - [0:0]
// :Cid45349B7222600.0 - [0:0]

create_chain : COLON chain_def
        {
            importer->setCurrentLineNumber(LT(0)->getLine());
            if (importer->current_table=="nat")
                importer->newUnidirRuleSet(LT(0)->getText(),
                                           libfwbuilder::NAT::TYPENAME);
            else
                importer->newUnidirRuleSet(LT(0)->getText(),
                                           libfwbuilder::Policy::TYPENAME);
            *dbg << "NEW CHAIN " << LT(0)->getText() << std::endl;
        }
        (WORD | MINUS)
        {
            importer->setDefaultAction(LT(0)->getText());
            *dbg << "DEFAULT ACTION " << LT(0)->getText() << std::endl;
        }
        (OPENING_SQUARE INT_CONST COLON INT_CONST CLOSING_SQUARE)?
    ;

//****************************************************************

add_rule : ADD_RULE chain_def
        {
            // push previous rule
            *dbg << std::endl;
            importer->pushRule();
            // start new one
            importer->setCurrentLineNumber(LT(0)->getLine());
            if (importer->current_table=="nat")
                importer->newNATRule();
            else
                importer->newPolicyRule();
            importer->current_chain = LT(0)->getText();
            *dbg << "add_rule: line=" << LT(0)->getLine()
                << " chain=" << LT(0)->getText();
        }
        ( ipt_option )+ NEWLINE
    ;

//****************************************************************
ipt_option :
        (
            negation
        |
            module
        |
            match_mark
        |
            match_recent
        |
            src
        |
            dst
        |
            i_intf
        |
            o_intf
        |
            proto
        |
            target
        |
            fragm
        | 
            icmp_type_spec
        |
            basic_tcp_udp_port_spec
        |
            multiport_tcp_udp_port_spec
        |
            tcp_options
        |
            match_limit
        |
            match_limit_burst
        |
            match_length
        |
            match_iprange_src
        |
            match_iprange_dst
        |
            unknown_option
        )
    ;

//****************************************************************
negation : EXCLAMATION
            {
                importer->tmp_neg = true;
            }
    ;

//****************************************************************
unknown_option :
        (
            MINUS WORD
            {
                importer->reportError(
                    std::string("Unknown option: -")+LT(0)->getText());
                *dbg << " UNKNOWN OPTION=-" << LT(0)->getText();
            }
            (unknown_parameter)?
        )
    | 
        (
            ( MINUS MINUS WORD )
            {
                importer->reportError(
                    std::string("Unknown option: --")+LT(0)->getText());
                *dbg << " UNKNOWN OPTION=--" << LT(0)->getText();
            }
            (unknown_parameter)?
        )
    |
        (
            UNSUPPORTED_OPTION
            {
                importer->reportError(
                    std::string("Unknown option: ")+LT(0)->getText());
                *dbg << " UNKNOWN OPTION=" << LT(0)->getText();
            }
            (unknown_parameter)?
        )
    ;

unknown_parameter
        { std::string s; }
:
        (
            (
                (
                    DIGIT     {s+=LT(0)->getText();}
                |
                    INT_CONST {s+=LT(0)->getText();}
                )
                SLASH         {s+=LT(0)->getText();}
                WORD          {s+=LT(0)->getText();}
            )
            {
                importer->reportError(
                    std::string("Unknown parameter: ")+s);
                *dbg << " UNKNOWN PARMETER=" << s;
            }
        )
    |
        (
            (DIGIT | INT_CONST | WORD)
            {
                importer->reportError(
                    std::string("Unknown parameter: ")+LT(0)->getText());
                *dbg << " UNKNOWN PARMETER=" << LT(0)->getText();
            }
        )
    ;

//****************************************************************
// this matches "-m module", except for modules "mark" and "recent"
// which have some parameters that look the same as parameters for
// other modules. See match_mark and match_recent 

module   : OPT_MODULE ( m_state | m_mport | m_icmp | m_tcp | m_udp | m_limit |  m_length | m_iprange | m_comment | m_pkttype | m_unknown_module) 
    ;

//****************************************************************
src : OPT_SRC
        {
            *dbg << " SRC=";
            importer->src_neg = importer->tmp_neg;
            importer->tmp_neg = false;
        }
        ( (WORD | IPV4)
            {
                importer->src_a = LT(0)->getText();
                *dbg << LT(0)->getText();
            }
            (SLASH (IPV4 | INT_CONST)
            {
                importer->src_nm = LT(0)->getText();
                *dbg << "/" << LT(0)->getText();
            }  )?
        )
    ;
                             
//****************************************************************
dst      : OPT_DST
        {
            *dbg << " DST=";
            importer->dst_neg = importer->tmp_neg;
            importer->tmp_neg = false;
        }
        ( (WORD | IPV4)
            {
                importer->dst_a = LT(0)->getText();
                *dbg << LT(0)->getText();
            }
            (SLASH (IPV4 | INT_CONST)
            {
                importer->dst_nm = LT(0)->getText();
                *dbg << "/" << LT(0)->getText();
            }  )?
        )
    ;
                             
//****************************************************************
i_intf  : OPT_IN_INTF
        {
            importer->intf_neg = importer->tmp_neg;
            importer->tmp_neg = false;
        }
        WORD
        {
            importer->i_intf = LT(0)->getText();
            *dbg << " I_INTF=" << LT(0)->getText();
        }
    ;
                            
//****************************************************************
o_intf : OPT_OUT_INTF
        {
            importer->intf_neg = importer->tmp_neg;
            importer->tmp_neg = false;
        }
        WORD
        {
            importer->o_intf = LT(0)->getText();
            *dbg << " O_INTF=" << LT(0)->getText();
        }
    ;
                            
//****************************************************************
protocol_word : (TCP | UDP | ICMP | WORD | INT_CONST )
    ;

proto    : OPT_PROTO
        {
            importer->srv_neg = importer->tmp_neg;
            importer->tmp_neg = false;
        }
        protocol_word
        {
            std::string tmp_s = LT(0)->getText();
            importer->protocol.resize(tmp_s.size());
            std::transform(tmp_s.begin(), 
                           tmp_s.end(),
                           importer->protocol.begin(), 
                           ::tolower);
            *dbg << " PROTO=" << importer->protocol;
        }
    ;
                            
//****************************************************************
target   : OPT_TARGET   t:WORD
        {
            importer->target = LT(0)->getText();
            *dbg << " TARGET=" << t->getText();
        }
        ( target_options )*
    ;

//****************************************************************
target_options : 
        (
            REJECT_WITH WORD
            {
                importer->action_params["reject_with"] = LT(0)->getText();
                *dbg << " REJECT WITH=" << LT(0)->getText();
            }
        |
            LOG_PREFIX (WORD | STRING)
            {
                importer->action_params["log_prefix"] = LT(0)->getText();
                *dbg << " LOG PREFIX=" << LT(0)->getText();
            }
        |
            LOG_TCP_SEQ
            {
                importer->action_params["log_tcp_seq"] = LT(0)->getText();
                *dbg << " LOG TCP SEQUENCE=";
            }
        |
            LOG_TCP_OPT
            {
                importer->action_params["log_tcp_options"] = LT(0)->getText();
                *dbg << " LOG TCP OPTIONS=";
            }
        |
            LOG_IP_OPT
            {
                importer->action_params["log_ip_options"] = LT(0)->getText();
                *dbg << " LOG IP OPTIONS=";
            }
        |
            ULOG_PREFIX (WORD | STRING)
            {
                importer->action_params["log_prefix"] = LT(0)->getText();
                *dbg << " ULOG PREFIX=" << LT(0)->getText();
            }
        |
            LOG_LEVEL (INT_CONST | WORD)
            {
                importer->action_params["log_level"] = LT(0)->getText();
                *dbg << " LOG LEVEL=" << LT(0)->getText();
            }
        |
            SET_CLASS major:INT_CONST COLON minor:INT_CONST
            {
                importer->action_params["set_class"] =
                    major->getText() + ":" + minor->getText();
                *dbg << " SET CLASS=" << major->getText() + ":" + minor->getText();
            }
        |
            SET_MARK (INT_CONST | HEX_CONST)
            {
                importer->action_params["set_mark"] = LT(0)->getText();
                *dbg << " SET MARK=" << LT(0)->getText();
            }
        |
            SET_TOS HEX_CONST
            {
                importer->action_params["set_tos"] = LT(0)->getText();
                *dbg << " SET TOS=" << LT(0)->getText();
            }
        |
            SET_TOS WORD
            {
                importer->action_params["set_tos"] = LT(0)->getText();
                *dbg << " SET TOS=" << LT(0)->getText();
            }
        |
            SAVE_MARK
            {
                importer->action_params["connmark_save_mark"] = "--save-mark";
                *dbg << " SAVE MARK";
            }
        |
            RESTORE_MARK
            {
                importer->action_params["connmark_restore_mark"] = "--restore-mark";
                *dbg << " RESTORE MARK";
            }
        |
            CONTINUE
            {
                importer->action_params["route_continue"] = "--continue";
                *dbg << " CONTINUE";
            }
        |
            ROUTE_IIF WORD
            {
                importer->action_params["route_iif"] = LT(0)->getText();
                *dbg << " ROUTE_IIF=" << LT(0)->getText();
            }
        |
            ROUTE_OIF WORD
            {
                importer->action_params["route_oif"] = LT(0)->getText();
                *dbg << " ROUTE_OIF=" << LT(0)->getText();
            }
        |
            ROUTE_GW IPV4
            {
                importer->action_params["route_gw"] = LT(0)->getText();
                *dbg << " ROUTE_GW=" << LT(0)->getText();
            }
        |
            ROUTE_TEE
            {
                importer->action_params["route_tee"] = "--tee";
                *dbg << " ROUTE_TEE";
            }
        |
            TO_SOURCE
            {
                *dbg << " TO-SOURCE";
            }
            nat_spec
        |
            TO_DESTINATION
            {
                *dbg << " TO-DESTINATION";
            }
            nat_spec
        |
            TO_PORTS redirect_spec
        |
            TO_NETMAP
            {
                *dbg << " TO-NETMAP";
            }
            (
                IPV4
                {
                    importer->nat_addr1 = LT(0)->getText();
                    importer->nat_addr2 = LT(0)->getText();
                    *dbg << LT(0)->getText();
                }
                SLASH (IPV4 | INT_CONST)
                {
                    importer->nat_nm = LT(0)->getText();
                    *dbg << "/" << LT(0)->getText();
                }
            )
        |
            CLAMP_MSS
            {
                importer->action_params["clamp-mss-to-pmtu"] = "--clamp-mss-to-pmtu";
                *dbg << " TO-NETMAP";
            }
        )
    ;

//****************************************************************
nat_spec : 
        nat_addr_range
        (COLON nat_port_def_with_range)?
        {
            *dbg << " "
                << importer->nat_addr1
                << "-"
                << importer->nat_addr2
                << ":"
                << importer->nat_port_range_start
                << "-"
                << importer->nat_port_range_end;
        }
    ;

//****************************************************************
nat_addr_range : 
        (IPV4 MINUS) => (
          a1:IPV4 MINUS a2:IPV4
          {
            importer->nat_port_range_start = "";
            importer->nat_port_range_end = "";
            importer->nat_addr1 = a1->getText();
            importer->nat_addr2 = a2->getText();
          }
        )
    |
        IPV4
        {
            importer->nat_port_range_start = "";
            importer->nat_port_range_end = "";
            importer->nat_addr1 = LT(0)->getText();
            importer->nat_addr2 = LT(0)->getText();
        }
    ;

//****************************************************************
redirect_spec : nat_port_def_with_range
        {
            *dbg << " TO-PORTS "
                << importer->nat_addr1
                << "-"
                << importer->nat_addr2
                << ":"
                << importer->nat_port_range_start
                << "-"
                << importer->nat_port_range_end;
        }
    ;

//****************************************************************
fragm    : OPT_FRAGM
        {
            importer->fragments = true;
            *dbg << " FRAGM";
        }
    ;

//****************************************************************
m_unknown_module : WORD
        {
            *dbg << " UNKNOWN MODULE=" << LT(0)->getText();
            importer->reportError(
                std::string("Unknown module: ")+LT(0)->getText());
        }
    ;

//****************************************************************

state_word : ( INVALID | NEW | ESTABLISHED | RELATED )
    ;

m_state : M_STATE MATCH_STATE 
        {
            importer->current_state = "";
        }
        state_word
        {
            importer->current_state += LT(0)->getText();
        }
        (
            COMMA state_word
            {
                importer->current_state += std::string(",") + LT(0)->getText();
            }
        )*
        {
            *dbg << " STATE MATCH=" << importer->current_state;
        }

    ;

//****************************************************************

m_mark : M_MARK
        {
            *dbg << " MARK";
        }
    ;

//****************************************************************

match_mark : OPT_MODULE m_mark
            ( EXCLAMATION {importer->neg_match_mark = true;} )?
            MATCH_MARK (INT_CONST | HEX_CONST)
            {
                importer->tmp_neg = false;
                importer->match_mark = LT(0)->getText();
                *dbg << " MATCH MARK " << LT(0)->getText();
            }
    ;

//****************************************************************

m_limit : M_LIMIT
        {
            *dbg << " LIMIT";
        }
    ;

//****************************************************************

match_limit : MATCH_LIMIT limit_rate
    ;

limit_rate :
        INT_CONST { importer->limit_val = LT(0)->getText(); }
        SLASH
        WORD      { importer->limit_suffix = LT(0)->getText(); }
        {
            *dbg << " MATCH LIMIT "
                << importer->limit_val << "/"
                << importer->limit_suffix;
        }
    ;

match_limit_burst : MATCH_LIMIT_BURST INT_CONST
        {
            importer->limit_burst = LT(0)->getText();
            *dbg << " LIMIT BURST " << LT(0)->getText();
        }
    ;
        
//****************************************************************

m_recent : M_RECENT
        {
            *dbg << " RECENT";
        }
    ;

//****************************************************************

m_iprange : M_IPRANGE
        {
            *dbg << " IPRANGE";
        }
    ;

match_iprange_src : MATCH_IPRANGE_SRC (WORD | IPV4)
        {
            importer->iprange_src_from = LT(0)->getText();
            importer->using_iprange_src = true;
        }
        MINUS (WORD | IPV4)
        {
            importer->iprange_src_to = LT(0)->getText();
        }
    ;

match_iprange_dst : MATCH_IPRANGE_DST (WORD | IPV4)
        {
            importer->iprange_dst_from = LT(0)->getText();
            importer->using_iprange_dst = true;
        }
        MINUS (WORD | IPV4)
        {
            importer->iprange_dst_to = LT(0)->getText();
        }
    ;


//****************************************************************
/* Unlike with other modules, this matches both "-m recent" and 
 * module arguments
 * I am having difficulties writing grammar to catch negation
 * in front of arguments, such as ! --set
 */
match_recent : OPT_MODULE m_recent ( recent_opts )+
    ;

recent_opts: recent_args_no_param | recent_args_param
    ;

recent_args_no_param: (MATCH_RECENT_SET | MATCH_RECENT_RCHECK |
                       MATCH_RECENT_UPDATE | MATCH_RECENT_REMOVE |
                       MATCH_RECENT_RTTL | RSOURCE |
                       MATCH_RECENT_RDEST)
        { importer->recent_match += LT(0)->getText() + " "; }
    ;

recent_args_param: (MATCH_RECENT_NAME | MATCH_RECENT_SECONDS | MATCH_RECENT_HITCOUNT)
        { importer->recent_match += LT(0)->getText() + " "; }
        (INT_CONST | WORD)
        { importer->recent_match += LT(0)->getText() + " "; }
    ;
        
//****************************************************************

m_length : M_LENGTH
        {
            *dbg << " LENGTH";
        }
    ;

//****************************************************************

match_length : MATCH_LENGTH length_spec
    ;

length_spec :
        INT_CONST { importer->length_spec = LT(0)->getText(); }
        COLON
        INT_CONST { importer->length_spec += ":";
                    importer->length_spec += LT(0)->getText(); }
        {
            *dbg << " MATCH LENGTH " << importer->length_spec;
        }
    ;

//****************************************************************

m_pkttype: M_PKTTYPE MATCH_PKT_TYPE pkt_type_spec
    ;

pkt_type_spec : (WORD_BROADCAST | WORD_MULTICAST | WORD_UNICAST)
        {
            importer->pkt_type_spec = LT(0)->getText();
            *dbg << " PKT_TYPE " << importer->pkt_type_spec;
        }
    ;

//****************************************************************

m_mport : M_MPORT
        {
            *dbg << " MULTIPORT";
        }
    ;

//****************************************************************

m_comment : M_COMMENT MATCH_COMMENT STRING
        {
            *dbg << " COMMENT=" << LT(0)->getText();
        }

    ;

//****************************************************************
/*
 * Note that there can be just one port (i.e. no ,port[,port] part)
 *
 * Note also that we do little preprocessing of the iptables lines
 * before passing them to the parser in IPTImporter::run(). Specifically,
 * we replace --sport and --dport with --source-ports and --destination-ports
 * for module multiport to avoid ambiguity that arises from different
 * rules that arguments for the --sport and --dport parameters can follow
 * depending on the module.
 */
multiport_tcp_udp_port_spec :
        (
            ( MATCH_SRC_MULTIPORT
                {
                    importer->srv_neg = importer->tmp_neg;
                    importer->tmp_neg = false;
                    importer->startSrcMultiPort();
                    *dbg << " SRC MULTIPORT=";
                }
                port_def_with_range
                {
                    importer->pushTmpPortSpecToSrcPortList();
                }
                ( COMMA port_def_with_range
                    {
                        importer->pushTmpPortSpecToSrcPortList();
                    }
                )*
            )
        |
            ( MATCH_DST_MULTIPORT
                {
                    importer->srv_neg = importer->tmp_neg;
                    importer->tmp_neg = false;
                    importer->startDstMultiPort();
                    *dbg << " DST MULTIPORT=";
                }
                port_def_with_range
                {
                    importer->pushTmpPortSpecToDstPortList();
                }
                ( COMMA port_def_with_range
                    {
                        importer->pushTmpPortSpecToDstPortList();
                    }
                )*
            )
        |
            ( MATCH_BOTH_MULTIPORT
                {
                    importer->srv_neg = importer->tmp_neg;
                    importer->tmp_neg = false;
                    importer->startBothMultiPort();
                    *dbg << " MULTIPORT PORTS=";
                }
                port_def_with_range
                {
                    importer->pushTmpPortSpecToBothPortList();
                }
                ( COMMA port_def_with_range
                    {
                        importer->pushTmpPortSpecToBothPortList();
                    }
                )*
            )

        )
    ;

//****************************************************************

m_icmp : ICMP
        {
            importer->protocol = "icmp";
            *dbg << " ICMP";
        }
    ;

//****************************************************************
icmp_type_spec : MATCH_ICMP_TYPE
        (
            WORD
            {
                importer->icmp_spec = LT(0)->getText();
                *dbg << " ICMP_SPEC=" << LT(0)->getText();
            }
        |
            (
                INT_CONST
                {
                    importer->icmp_type = LT(0)->getText();
                    importer->icmp_code = "-1";
                    *dbg << " ICMP_TYPE=" << LT(0)->getText();
                }
                (
                    SLASH INT_CONST
                    {
                        importer->icmp_code = LT(0)->getText();
                        *dbg << " ICMP_CODE=" << LT(0)->getText();
                    }
                )?
            )
        )
    ;

//****************************************************************
// port definition that does not allow for port range
port_def_no_range : (WORD|INT_CONST)
        {
            importer->tmp_port_range_start = LT(0)->getText();
            importer->tmp_port_range_end = LT(0)->getText();
            *dbg << " PORT=" << LT(0)->getText();
        }
    ;

/****************************************************************
 * port definition that allows for port range. That parser should
 * recognize constructs
 *
 * port1         ---> range_start = range_end = port1
 * port1:port2   ---> range_start = port1  range_end = port2
 * port1:        ---> range_start = port1  range_end = 65535
 * :port2        ---> range_start = 0      range_end = port2
 */ 
port_def_with_range : 
        (WORD|INT_CONST)
        {
            importer->tmp_port_range_start = LT(0)->getText();
            importer->tmp_port_range_end = LT(0)->getText();
            *dbg << " PORT=" << LT(0)->getText();
        }
        (
            COLON (WORD|INT_CONST)?
            {
                importer->tmp_port_range_end = LT(0)->getText();
                *dbg << ":" << LT(0)->getText();
            }
        )?
    ;

port_def_with_incomplete_range :
        COLON (WORD|INT_CONST)
        {
            importer->tmp_port_range_start = "0";
            importer->tmp_port_range_end = LT(0)->getText();
            *dbg << "PORT 0:" << LT(0)->getText();
        }
    ;

//****************************************************************
// nat port definition that allows for port range
// (uses '-' instead of ':')
nat_port_def_with_range : 
        ((WORD | INT_CONST) MINUS) => (
          (WORD | INT_CONST)
          {
            importer->nat_port_range_start = LT(0)->getText();
            importer->nat_port_range_end = LT(0)->getText();
            *dbg << " PORT=" << LT(0)->getText();
          }
          MINUS (WORD | INT_CONST)
          {
            importer->nat_port_range_end = LT(0)->getText();
            *dbg << ":" << LT(0)->getText();
          }
        )
    |
        (WORD | INT_CONST)
        {
            importer->nat_port_range_start = LT(0)->getText();
            importer->nat_port_range_end = LT(0)->getText();
            *dbg << " PORT=" << LT(0)->getText();
        }
    ;

//****************************************************************
basic_tcp_udp_port_spec :
        (MATCH_SRC_PORT | MATCH_SRC_PORT_SHORT)
        {
            importer->srv_neg = importer->tmp_neg;
            importer->tmp_neg = false;
        }
        (port_def_with_range | port_def_with_incomplete_range)
        {
            importer->pushTmpPortSpecToSrcPortList();
        }
    |
        (MATCH_DST_PORT | MATCH_DST_PORT_SHORT)
        {
            importer->srv_neg = importer->tmp_neg;
            importer->tmp_neg = false;
        }
        (port_def_with_range | port_def_with_incomplete_range)
        {
            importer->pushTmpPortSpecToDstPortList();
        }
    ;


//****************************************************************

m_udp : UDP
        {
            importer->protocol = "udp";
            *dbg << " UDP";
        }
    ;

//****************************************************************

m_tcp : TCP
        {
            importer->protocol = "tcp";
            *dbg << " TCP";
        }
    ;

//****************************************************************
// tcp options can follow "-p tcp", the "-m tcp" seems to be optional,
// at least in the older versions of iptables

tcp_options : 
        ( syn | tcp_flags | tcp_option)
        {
            importer->srv_neg = importer->tmp_neg;
            importer->tmp_neg = false;
        }
    ;

syn :   MATCH_SYN
        {
            importer->tcp_flags_mask.clear();
            importer->tcp_flags_mask.push_back(libfwbuilder::TCPService::SYN);
            importer->tcp_flags_mask.push_back(libfwbuilder::TCPService::RST);
            importer->tcp_flags_mask.push_back(libfwbuilder::TCPService::ACK);

            importer->tcp_flags_comp.clear();
            importer->tcp_flags_comp.push_back(libfwbuilder::TCPService::SYN);
        }
    ;

tcp_flag_word : 
        (
            SYN { importer->tmp_tcp_flag_code = libfwbuilder::TCPService::SYN; }
        |
            ACK { importer->tmp_tcp_flag_code = libfwbuilder::TCPService::ACK; }
        |
            FIN { importer->tmp_tcp_flag_code = libfwbuilder::TCPService::FIN; }
        |
            RST { importer->tmp_tcp_flag_code = libfwbuilder::TCPService::RST; }
        |
            URG { importer->tmp_tcp_flag_code = libfwbuilder::TCPService::URG; }
        |
            PSH { importer->tmp_tcp_flag_code = libfwbuilder::TCPService::PSH; }
        |
            ALL { importer->tmp_tcp_flag_code = 99; }
        |
            NONE { importer->tmp_tcp_flag_code = 98; }
        )
    ;

tcp_flags_list :
        {
            importer->tmp_tcp_flags_list.clear();
            importer->tmp_tcp_flag_code = 0;
        }
        tcp_flag_word
        {
            importer->tmp_tcp_flags_list.push_back(importer->tmp_tcp_flag_code);
        }
        (
            COMMA tcp_flag_word
            {
                importer->tmp_tcp_flags_list.push_back(
                    importer->tmp_tcp_flag_code);
            }
        )*
    ;

tcp_flags : MATCH_TCP_FLAGS
        tcp_flags_list
        {
            importer->tcp_flags_mask = importer->tmp_tcp_flags_list;
            importer->tmp_tcp_flags_list.clear();
        }
        tcp_flags_list
        {
            importer->tcp_flags_comp = importer->tmp_tcp_flags_list;
            importer->tmp_tcp_flags_list.clear();
            *dbg << " TCP FLAGS=";
            foreach(int x, importer->tcp_flags_mask)
                *dbg << x << "|";
            *dbg << " ";
            foreach(int x, importer->tcp_flags_comp)
                *dbg << x << "|";
        }
    ;

// --tcp-option is not supported in fwbuilder at this time
tcp_option : MATCH_TCP_OPTION INT_CONST
    ;

//****************************************************************


class IPTCfgLexer extends Lexer;
options {
    k = 20;
    // ASCII only
    charVocabulary = '\3'..'\377';
}


tokens
{
    INPUT = "INPUT";
    FORWARD = "FORWARD";
    OUTPUT = "OUTPUT";
    PREROUTING = "PREROUTING";
    POSTROUTING = "POSTROUTING";

    INVALID = "INVALID";
    NEW = "NEW";
    ESTABLISHED = "ESTABLISHED";
    RELATED = "RELATED";

    COMMIT = "COMMIT";

    M_STATE = "state";
    M_COMMENT = "comment";
    M_MPORT = "multiport";
    M_MARK = "mark";
    M_LIMIT = "limit" ;
    M_LENGTH = "length" ;
    M_RECENT = "recent" ;
    M_IPRANGE = "iprange" ;
    M_PKTTYPE = "pkttype" ;

    ICMP = "icmp";
    TCP = "tcp";
    UDP = "udp";

    SYN = "SYN";
    ACK = "ACK";
    FIN = "FIN";
    RST = "RST";
    URG = "URG";
    PSH = "PSH";
    ALL = "ALL";
    NONE = "NONE";

    WORD_BROADCAST = "broadcast" ;
    WORD_MULTICAST = "multicast" ;
    WORD_UNICAST = "unicast" ;

}

// LINE_COMMENT : '#' (~('\r' | '\n'))* NEWLINE ;

IPTABLES_SAVE_HEADER : " Generated by iptables-save v";

Whitespace :  ( '\003'..'\010' | '\t' | '\013' | '\f' | '\016'.. '\037' | '\177'..'\377' | ' ' )
        { _ttype = ANTLR_USE_NAMESPACE(antlr)Token::SKIP;  } ;

NEWLINE : ( "\r\n" | '\r' | '\n' ) { newline(); resetText(); } ;

protected
IPV4:;

protected
THREE_COMPONENT_VERSION:;

protected
IPV6:;

protected 
MAC_ADDRESS:;

protected
INT_CONST:;

protected
HEX_CONST:;

protected
NEG_INT_CONST:;

protected
DIGIT : '0'..'9'  ;

protected
HEXDIGIT : '0'..'9' | 'A'..'F' | 'a'..'f';

// ################################
// Rules for IPv4 and IPv6 partially based on ideas from
// http://www.antlr.org:8080/pipermail/antlr-interest/2005-June/012661.html
// Ruleset copied from the posting does not compile, antlr 2.7.7 seems to
// hang while processing it. Commenting out rules for MAC_ADDRESS and
// IPV6 makes antlr process grammar successfully (but defeats the purpose).

protected
NUM_3DIGIT: ('0'..'9') (('0'..'9') ('0'..'9')?)?;

protected
NUM_HEX_4DIGIT: HEXDIGIT ((HEXDIGIT) ((HEXDIGIT) (HEXDIGIT)?)?)?;



// IPV6
// Note that '::' can only appear once in the address
// but can be used to compress leading and/or trailing zeros in an address
//
// As of 12/2008 these rules do not work, antlr seems to hang while compiling
// this grammar.
//
// IPV6_1: (NUM_HEX_4DIGIT ':' (NUM_HEX_4DIGIT | ':')* NUM_HEX_4DIGIT)
//         { $setType(IPV6); };
//
// IPV6_2: (':' ':' (NUM_HEX_4DIGIT | ':')* NUM_HEX_4DIGIT) { $setType(IPV6); };
//
// IPV6_3: ((NUM_HEX_4DIGIT | ':')* NUM_HEX_4DIGIT ':' ':') { $setType(IPV6); };
//
// IPV6_4: ':' ':' { $setType(IPV6); };


NUMBER
options { testLiterals = true; }
:

// IPv4 RULE
  (NUM_3DIGIT '.' NUM_3DIGIT '.') => (
    NUM_3DIGIT '.' NUM_3DIGIT '.' NUM_3DIGIT '.' NUM_3DIGIT { $setType(IPV4); }
  )
|
  (NUM_3DIGIT '.' NUM_3DIGIT '.') => (
    NUM_3DIGIT '.' NUM_3DIGIT '.' NUM_3DIGIT { $setType(THREE_COMPONENT_VERSION); }
  )
|

  ( '0' 'x' ( HEXDIGIT )+ )  { $setType(HEX_CONST); }
|
  ( DIGIT )+ { $setType(INT_CONST); }

;




WORD : ( 'a'..'z' | 'A'..'Z' | '$' ) ( '!'..'+' | '-' | '.' | '/' | '0'..'9' | ':' | ';' | '<' | '=' | '>' | '?' | '@' | 'A'..'Z' | '^' | '_' | '`' | 'a'..'z'  )*
    ;

STRING : '"' (~'"')* '"';

// ------------------------------------------------------------------------
// I have to add these options even though I do not support them
//
protected
UNSUPPORTED_OPTION:;

//"--seconds" confuses lexer because it interprets it as "-" "-s" "econds"
//SECONDS : "--seconds" { $setType(UNSUPPORTED_OPTION); };

// SET : "--set" { $setType(UNSUPPORTED_OPTION); };

// "--rsource" also confuses lexer which expects "--reject"
RSOURCE : "--rsource" { $setType(UNSUPPORTED_OPTION); };

// ------------------------------------------------------------------------

ADD_RULE : "-A" ;
MATCH_STATE : "--state" ;

MATCH_SRC_MULTIPORT : "--source-ports" ;
MATCH_DST_MULTIPORT : "--destination-ports" ;
MATCH_BOTH_MULTIPORT : "--ports" ;

MATCH_SRC_PORT : "--source-port" ;
MATCH_DST_PORT : "--destination-port" ;

MATCH_SRC_PORT_SHORT : "--sport" ;
MATCH_DST_PORT_SHORT : "--dport" ;

MATCH_SYN : "--syn" ;
MATCH_TCP_FLAGS : "--tcp-flags" ;
MATCH_TCP_OPTION : "--tcp-option" ;

MATCH_ICMP_TYPE : "--icmp-type" ;

MATCH_MARK : "--mark" ;

MATCH_LENGTH : "--length" ;

MATCH_LIMIT : "--limit" ;
MATCH_LIMIT_BURST : "--limit-burst" ;

MATCH_RECENT_NAME : "--name" ;
MATCH_RECENT_RCHECK : "--rcheck" ;
MATCH_RECENT_UPDATE : "--update" ;
MATCH_RECENT_REMOVE : "--remove" ;
MATCH_RECENT_SECONDS : "--seconds" ;
MATCH_RECENT_HITCOUNT : "--hitcount" ;
MATCH_RECENT_RTTL : "--rttl" ;
MATCH_RECENT_RDEST : "--rdest" ;
MATCH_RECENT_SET : "--set" ;

MATCH_IPRANGE_SRC : "--src-range" ;
MATCH_IPRANGE_DST : "--dst-range" ;

MATCH_COMMENT : "--comment" ;

MATCH_PKT_TYPE : "--pkt-type" ;

// ----------------------------------------------------------------
//  target options
REJECT_WITH : "--reject-with" ;
SET_CLASS : "--set-class" ;
SET_MARK : "--set-mark" ;
SAVE_MARK : "--save-mark" ;
RESTORE_MARK : "--restore-mark" ;
SET_TOS : "--set-tos" ;
CONTINUE : "--continue" ;
ROUTE_IIF : "--iif" ;
ROUTE_OIF : "--oif" ;
ROUTE_GW : "--gw" ;
ROUTE_TEE : "--tee" ;

LOG_PREFIX : "--log-prefix" ;
LOG_LEVEL : "--log-level" ;
LOG_TCP_SEQ : "--log-tcp-sequence";
LOG_TCP_OPT : "--log-tcp-options";
LOG_IP_OPT : "--log-ip-options";

ULOG_PREFIX : "--ulog-prefix" ;
ULOG_QTHR : "--ulog-qthreshold" { $setType(UNSUPPORTED_OPTION); };
ULOG_NLG : "--ulog-nlgroup" { $setType(UNSUPPORTED_OPTION); };
ULOG_CPR : "--ulog-cprange" { $setType(UNSUPPORTED_OPTION); };

TO_SOURCE : "--to-source" ;
TO_DESTINATION : "--to-destination" ;
TO_PORTS : "--to-ports" ;
TO_NETMAP : "--to" ;

CLAMP_MSS : "--clamp-mss-to-pmtu" ;

// ----------------------------------------------------------------
// these are the basic iptables options, not too many really
OPT_MODULE : "-m" ;
OPT_SRC : "-s" ;
OPT_DST : "-d" ;
OPT_IN_INTF : "-i" ;
OPT_OUT_INTF : "-o" ;
OPT_PROTO : "-p" ;
OPT_TARGET : "-j" ;
OPT_FRAGM : "-f" ;

EXCLAMATION : '!' ;

NUMBER_SIGN : '#' ;
// DOLLAR : '$' ;
PERCENT : '%' ;
AMPERSAND : '&' ;
APOSTROPHE : '\'' ;
OPENING_PAREN : '(' ;
CLOSING_PAREN : ')' ;
STAR : '*' ;
PLUS : '+' ;
COMMA : ',' ;
MINUS : '-' ;
DOT : '.' ;
SLASH : '/' ;

COLON : ':' ;
SEMICOLON : ';' ;
LESS_THAN : '<' ;
EQUALS : '=' ;
GREATER_THAN : '>' ;
QUESTION : '?' ;
COMMERCIAL_AT : '@' ;

OPENING_SQUARE : '[' ;
CLOSING_SQUARE : ']' ;
CARET : '^' ;
UNDERLINE : '_' ;

OPENING_BRACE : '{' ;
CLOSING_BRACE : '}' ;
TILDE : '~' ;

