Changeset 198

Show
Ignore:
Timestamp:
04/21/05 22:09:50 (4 years ago)
Author:
athomas
Message:

Commit before factoring out FWC stuff. The grammar parser is pretty much
complete, with the addition of validating nodes, external "callable" grammars
and a few other features.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • manage/branches/python/CLI.py

    r197 r198  
    55import sys 
    66import readline 
     7import copy 
    78 
    89# Commands 
     
    2627# of labels, in which case each grammar branch will be a candidate. 
    2728JUMP_TO = 'JUMP_TO' 
     29RETURN = 'RETURN' 
    2830# Include this branch only if the given function returns true. 
    2931IF = 'IF' 
     
    3941# Store value of parent with the given identifier 
    4042VAR = 'VAR' 
    41 # Require between [min, max] 
     43# Require between [min [, max]]  
    4244RANGE = 'RANGE' 
     45# Merge the given target at this location. Target can either be a sub-grammar 
     46# or a callable with the signature function(context, grammar), where grammar is 
     47# the grammar at the current depth. 
     48MERGE = 'MERGE' 
    4349 
    4450# Context states 
     
    6369                if LABEL in grammar: 
    6470                        self.context.add_label(grammar[LABEL], {grammar_key : grammar}) 
     71                elif GLOBAL_LABEL in grammar: 
     72                        self.context.add_label(grammar[GLOBAL_LABEL], {grammar_key : grammar}) 
    6573                # Increase reference count 
    6674                context.traverse(grammar) 
     
    103111                def __init__(self, grammar, tokens, text): 
    104112                        self.__label = [] 
     113                        self.__mark = [] 
    105114                        self.__var = {} 
    106115                        self.__count = {} 
     116                        self.__rangemap = {} 
     117                        self.grammar = self.__copy_grammar(grammar) 
    107118                        self.text = text 
    108                         self.grammar = grammar 
    109119                        self.tokens = tokens 
    110120                        self.parsed_tokens = [] 
     
    112122                        self.result = None 
    113123                        self.pre_parse(self.grammar) 
     124 
     125                def __copy_grammar(self, grammar): 
     126                        out = {} 
     127                        for k, v in grammar.iteritems(): 
     128                                t = type(v) 
     129                                if t is dict: 
     130                                        out[k] = self.__copy_grammar(v) 
     131                                else: 
     132                                        if t is types.FunctionType: 
     133                                                out[k] = v 
     134                                        else: 
     135                                                out[k] = copy.copy(v) 
     136                        return out 
    114137 
    115138                def pre_parse(self, grammar, grammar_key = None): 
     
    124147 
    125148                def traversed(self, node): 
    126                         if RANGE in node: 
    127                                 return node[RANGE][2] 
    128                         else: 
    129                                 return 0 
     149                        return self.range(node)[2] 
    130150 
    131151                def traverse(self, node): 
    132                         if RANGE in node: 
    133                                 if type(node[RANGE]) is int: 
    134                                         node[RANGE] = [ node[RANGE], node[RANGE], 0 ] 
    135                         else: 
    136                                 node[RANGE] = [ 0, 9999, 0 ] 
     152                        node[RANGE] = self.range(node) 
    137153                        node[RANGE][2] += 1 
    138154                        return node[RANGE][2] 
     
    140156                def add_var(self, var, value): 
    141157                        if var in self.__var: 
     158                                if type(self.__var[var]) is not list: 
     159                                        self.__var[var] = [ self.__var[var] ] 
    142160                                self.__var[var].append(value) 
    143161                        else: 
    144                                 self.__var[var] = [ value ] 
     162                                self.__var[var] = value 
     163 
     164                def add_mark(self, grammar): 
     165                        self.__mark.append(grammar) 
    145166 
    146167                def add_label(self, name, grammar): 
     
    153174                        self.__label.append([name, grammar]) 
    154175 
    155                 def has_labels(self, seek): 
     176                def has_all_labels(self, seek): 
    156177                        if type(seek) is str: seek = [ seek ] 
    157178                        for s in seek: 
     
    165186                        return True 
    166187 
    167                 def has_vars(self, seek): 
     188                def has_labels(self, seek): 
     189                        """ Return true if any of the sought labels exist. """ 
     190                        if type(seek) is str: seek = [ seek ] 
     191                        for s in seek: 
     192                                for l in self.__label: 
     193                                        if l[0] == s: 
     194                                                return True 
     195                        return False 
     196 
     197                def has_all_vars(self, seek): 
     198                        """ Determine if all of the sought variables are defiend. """ 
    168199                        if type(seek) is str: seek = [ seek ] 
    169200                        for var in seek: 
     
    172203                        return True 
    173204 
    174                 def merge_grammars_shallow(self, seek): 
     205                def has_vars(self, seek): 
     206                        """ Determine if any of the given variables are defined. """ 
     207                        if type(seek) is str: seek = [ seek ] 
     208                        for var in seek: 
     209                                if var in self.__var: 
     210                                        return True 
     211                        return False 
     212 
     213                def merge_labels_shallow(self, seek): 
    175214                        out = {} 
    176215                        for label in self.__label: 
     
    181220                        return out 
    182221 
    183                 def merge_grammars(self, seek): 
     222                def merge_labels(self, seek): 
    184223                        out = {} 
    185224                        for label in self.__label: 
     
    188227                                                out[key] = label[1][key] 
    189228                        return out 
     229 
     230                def range(self, grammar): 
     231                        range = None 
     232                        if RANGE in grammar: 
     233                                range = grammar[RANGE] 
     234                                t = type(range) 
     235                                if t is int: 
     236                                        range = [ range, range, 0, id(self) ] 
     237                                elif t is tuple or t is list: 
     238                                        range = list(range) 
     239                                        if len(range) < 3: range.append(0) 
     240                                        if len(range) < 4: range.append(id(self)) 
     241                                        if range[3] != id(self): 
     242                                                range[2] = 0 
     243                                                range[3] = id(self) 
     244                                        if len(range) != 4: 
     245                                                raise TypeError("RANGE list or tuple must be 1 or 2 elements") 
     246                                else: 
     247                                        raise TypeError("RANGE keys must be either an integer count or a (min,max) tuple/list, not %s" % t) 
     248                        else: 
     249                                range = grammar[RANGE] = [ 0, 9999, 0, id(self) ] 
     250                        grammar[RANGE] = range 
     251                        return range 
    190252 
    191253                def filter_grammar(self, grammar): 
     
    199261                                                (UNLESS in branch and not branch[UNLESS](self) or UNLESS not in branch) \ 
    200262                                                and 
    201                                                 (IF_VAR in branch and branch[IF_VAR] in self or IF_VAR not in branch) \ 
     263                                                ((IF_VAR in branch and self.has_vars(branch[IF_VAR])) or IF_VAR not in branch) \ 
    202264                                                and 
    203265                                                ((UNLESS_VAR in branch and not self.has_vars(branch[UNLESS_VAR])) or UNLESS_VAR not in branch) \ 
     
    208270                                                and 
    209271                                                # If RANGE is exceeded... 
    210                                                 ((RANGE in branch and branch[RANGE][2] < branch[RANGE][1]) or not RANGE in branch
     272                                                (self.range(branch)[2] < self.range(branch)[1]
    211273                                        ): 
    212274                                        out[key] = branch 
     
    223285 
    224286                def grammar_branches(self, grammar): 
    225                         new_grammar = {} 
    226                         if JUMP in grammar: 
    227                                 if not self.has_labels(grammar[JUMP]): 
    228                                         raise NameError("no such label '%s' referenced in JUMP" % grammar[JUMP]) 
    229                                 new_grammar.update(self.merge_grammars_shallow(grammar[JUMP])) 
    230                         if JUMP_TO in grammar: 
    231                                 if not self.has_labels(grammar[JUMP_TO]): 
    232                                         raise NameError("no such label '%s' referenced in JUMP_TO" % grammar[JUMP_TO]) 
    233                                 new_grammar.update(self.merge_grammars(grammar[JUMP_TO])) 
    234                         return new_grammar 
     287                        done = {} 
     288                        for i in range(0, 3): 
     289                                if MERGE in grammar and MERGE not in done: 
     290                                        done[MERGE] = 1 
     291                                        merge = grammar[MERGE] 
     292                                        if type(merge) is not list: merge = [ merge ] 
     293                                        self.add_mark(grammar) 
     294                                        for m in merge: 
     295                                                mtype = type(m) 
     296                                                if mtype is types.FunctionType: 
     297                                                        m = m(self, grammar) 
     298                                                        mtype = type(m) 
     299                                                if mtype is not dict: 
     300                                                        raise TypeError("MERGE target must be a dictionary (or callable that returns a dictionary)") 
     301                                                grammar.update(m) 
     302                                if JUMP in grammar and JUMP not in done: 
     303                                        done[JUMP] = 1 
     304                                        label = grammar[JUMP] 
     305                                        if label == RETURN: 
     306                                                grammar.update(self.__mark[-1]) 
     307                                        else: 
     308                                                if not self.has_labels(label): 
     309                                                        raise NameError("no such label '%s' referenced in JUMP" % grammar[JUMP]) 
     310                                                grammar.update(self.merge_labels_shallow(label)) 
     311                                if JUMP_TO in grammar and JUMP_TO not in done: 
     312                                        done[JUMP_TO] = 1 
     313                                        label = grammar[JUMP_TO] 
     314                                        if label == RETURN: 
     315                                                grammar.update(self.__mark[-1]) 
     316                                        else: 
     317                                                if not self.has_labels(label): 
     318                                                        raise NameError("no such label '%s' referenced in JUMP_TO" % grammar[JUMP_TO]) 
     319                                                grammar.update(self.merge_labels(label)) 
     320                        return grammar 
    235321 
    236322                def parse_help_node(self, key, node): 
     
    313399 
    314400                def parse_branch(self, source_grammar, tokens, nodes): 
    315                         grammar = self.copy_grammar_shallow(source_grammar) 
    316                         grammar.update(self.grammar_branches(grammar)) 
     401                        grammar = self.grammar_branches(self.copy_grammar_shallow(source_grammar)) 
    317402                        self.result_grammar = grammar 
    318403                        # End of command 
     
    339424                                                return self.parse_branch(grammar[match], tokens, nodes) 
    340425 
    341                         return Result(Result.NOMATCH, self, grammar, token, message = "Invalid command") 
     426                        return Result(Result.NOMATCH, self, grammar, token, message = "Invalid token") 
    342427 
    343428                def __iter__(self): 
     
    353438                self.grammar = grammar 
    354439                self.tokenise = re.compile(r'(\'(?:[^\\\']|.)*\'|"(?:[^\\"]|.)*")|(\S+)') 
    355  
    356         def init_grammar(self, grammar): 
    357                 if type(grammar) is not dict: return 
    358                 if RANGE in grammar: 
    359                         t = type(grammar[RANGE]) 
    360                         if t is int: 
    361                                 grammar[RANGE] = [ grammar[RANGE], grammar[RANGE], 0 ] 
    362                         elif (t is tuple or t is list) and len(grammar[RANGE]) == 2: 
    363                                 grammar[RANGE] = [ grammar[RANGE][0], grammar[RANGE][1], 0 ] 
    364                         elif t is list and len(grammar[RANGE]) == 3: 
    365                                 grammar[RANGE][2] = 0 
    366                         else: 
    367                                 raise TypeError("RANGE keys must be either an integer count or a (min,max) tuple, not %s" % t) 
    368                 else: 
    369                         grammar[RANGE] = [ 0, 9999, 0 ] 
    370                 for key, branch in grammar.iteritems(): 
    371                         if not _is_command(key): 
    372                                 self.init_grammar(branch) 
    373440 
    374441        def parse(self, command): 
     
    379446                else: 
    380447                        raise TypeError("CLI.parse() must be passed a string command") 
    381                 self.init_grammar(self.grammar) 
    382448                context = CLI.Context(self.grammar, tokens, command) 
    383449                return context.parse() 
  • manage/branches/python/fwc

    r197 r198  
    88import textwrap 
    99 
     10CONF_DIR = "/etc/fwcrc" 
     11 
    1012class Rule: 
    11         def __init__(self, action = None, source = [], sport = [], destination = [], dport = [], protocol = [], description = None, state = None): 
     13        def __init__(self, action = None, source = [], sport = [], destination = [], dport = [], protocol = [], description = None, state = None, log = None): 
    1214                self.action = action 
    1315                self.source = source 
     
    1921                self.description = description 
    2022                self.state = state 
     23                self.log = log 
    2124 
    2225class Object: 
     
    3134                self.description = description 
    3235 
    33 class Ruleset
    34         def __init__(self): 
     36class Firewall
     37        def __init__(self, name): 
    3538                self.rules = [] 
     39                self.name = name 
    3640                self.ip = {} 
    3741                self.network = {} 
     
    4246                self.add_object(Object(Object.PORT, 'any', '0-65535', 'Any port')) 
    4347 
     48        def resolve_object(self, type, name): 
     49                map = getattr(self, type) 
     50                if map and name in map: 
     51                        return map[name] 
     52                return Object(type, name, name) 
     53 
     54        def resolve_network(self, network): 
     55                return self.resolve_object('network', network) 
     56 
     57        def resolve_ip(self, ip): 
     58                return self.resolve_ip('ip', ip) 
     59 
     60        def resolve_port(self, port): 
     61                return self.resolve_port('port', port) 
     62 
     63        def resolve_rule(self, rule): 
     64                try: 
     65                        rule = int(rule) 
     66                        if rule >= 0 and rule < len(self.rules): 
     67                                return int(rule) 
     68                except TypeError: 
     69                        return None 
     70 
    4471        def add(self, rule, where, index = None): 
    4572                try: 
    4673                        if where == 'top': 
    4774                                self.rules.insert(0, rule) 
     75                                return 0 
    4876                        elif where == 'bottom' : 
    4977                                self.rules.append(rule) 
     78                                return len(self.rules) - 1 
    5079                        elif where == 'before': 
    51                                 self.rules.insert(int(index), rule) 
     80                                self.rules.insert(index, rule) 
     81                                return index 
    5282                        elif where == 'after': 
    53                                 self.rules.insert(int(index) + 1, rule) 
     83                                self.rules.insert(index + 1, rule) 
     84                                return index + 1 
    5485                        elif where == 'replace' : 
    55                                 self.rules[int(index)] = rule 
     86                                self.rules[index] = rule 
     87                                return index 
    5688                        else: 
    5789                                raise IndexError("invalid location '%s' for rule addition" % where) 
    5890                except IndexError: 
    5991                        error("Invalid ruleset index %s" % index) 
     92                        return None 
     93 
     94        def move(self, old, new): 
     95                if old == new: return 
     96                try: 
     97                        rule = self.rules.pop(old) 
     98                        try: 
     99                                if new > old: 
     100                                        self.rules.insert(new - 1, rule) 
     101                                else: 
     102                                        self.rules.insert(new, rule) 
     103                        except: 
     104                                self.rules.insert(old, rule) 
     105                                raise 
     106                except IndexError: 
     107                        error("Invalid ruleset index %s or %s" % (old, new)) 
    60108 
    61109        def remove(self, index): 
     110                if type(index) is not list: index = [ index ] 
    62111                try: 
    63                         self.rules.pop(index) 
     112                        for i in index: 
     113                                self.rules[i] = None 
     114                        newrules = [] 
     115                        for i in self.rules: 
     116                                if i: newrules.append(i) 
     117                        self.rules = newrules 
    64118                except IndexError: 
    65119                        error("Invalid rule index %s" % index) 
    66120                 
    67121        def add_object(self, object): 
    68                 getattr(self, object.type)[object.name] = object 
     122                map = getattr(self, object.type) 
     123                if object.name in map: 
     124                        error("%s object '%s' already exists" % (object.type.title(), object.name)) 
     125                else: 
     126                        map[object.name] = object 
    69127         
    70128        def remove_object(self, object): 
     
    74132                        error("Object '%s' not in ruleset" % object.name) 
    75133 
    76 ruleset = Ruleset() 
     134# Globally useful stuff 
     135  
     136firewalls = {} 
     137firewall = firewalls['DEFAULT'] = Firewall() 
     138 
     139def to_list(value): 
     140        if type(value) is not list: return [value] 
     141        return value 
    77142 
    78143def info(message): 
     
    90155 
    91156def check_port_protocol(context): 
    92         return 'protocol' in context and context['protocol'][0] in [ 'tcp', 'udp' ] 
    93  
    94 def enumerate_ip(context): 
     157        return 'protocol' in context and context['protocol'] in [ 'tcp', 'udp' ] 
     158 
     159# Help extractors 
     160  
     161def help_ip(context): 
    95162        help = { '<ip>' : 'IP address.'} 
    96         for o in ruleset.ip.values(): 
    97                 text = o.description or "IP address object (%s)" % o.name 
    98                 help[o.name] = text 
     163        for o in firewall.ip.values(): 
     164                text = o.description or "IP address object %s" % o.name 
     165                help[o.name] = text + " (%s)" % o.value 
    99166        return help 
    100167                 
     168def help_network(context): 
     169        help = { '<network>' : 'Network address.'} 
     170        for o in firewall.network.values(): 
     171                text = o.description or "Network object %s" % o.name 
     172                help[o.name] = text + " (%s)" % o.value 
     173        return help 
     174                 
     175def help_network_ip(context): 
     176        help = {} 
     177        help.update(help_ip(context)) 
     178        help.update(help_network(context)) 
     179        return help 
     180 
     181def help_port(context): 
     182        help = { '<port>' : 'Port.'} 
     183        for o in firewall.port.values(): 
     184                text = o.description or "Port object %s" % o.name 
     185                help[o.name] = text + " (%s)" % o.value 
     186        return help 
     187 
     188def help_firewall(context): 
     189        help = {} 
     190        for f in firewalls: 
     191                help[f] = "Firewall %s" % firewalls[f].name 
     192        return help 
     193 
     194# Command validators 
     195  
     196def NETWORK(context, str): 
     197        return re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}', str) \ 
     198                or str in help_network(context) 
     199 
     200def NETWORK_IP(context, str): 
     201        return re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:/\d{1,2})?', str) \ 
     202                or str in help_network(context) \ 
     203                or str in help_ip(context) 
     204 
    101205def IP(context, str): 
    102206        return re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', str) \ 
    103                 or str in enumerate_ip(context).keys() 
    104  
    105 def enumerate_network(context): 
    106         help = { '<network>' : 'Network address.'} 
    107         for o in ruleset.network.values(): 
    108                 text = o.description or "Network object (%s)" % o.name 
    109                 help[o.name] = text 
    110         return help 
    111                  
    112 def NETWORK(context, str): 
    113         return re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}', str) \ 
    114                 or str in enumerate_network(context).keys() 
    115  
    116 def NETWORK_IP(context, str): 
    117         return re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:/\d{1,2})?', str) \ 
    118                 or str in enumerate_network(context).keys() \ 
    119                 or str in enumerate_ip(context).keys() 
    120  
    121 def enumerate_port(context): 
    122         help = { '<port>' : 'Port.'} 
    123         for o in ruleset.port.values(): 
    124                 text = o.description or "Port object (%s)" % o.name 
    125                 help[o.name] = text 
    126         return help 
     207                or str in help_ip(context) 
    127208 
    128209def PORT(context, str): 
    129210        return re.match(r'\d{1,5}', str) \ 
    130                 or str in enumerate_port(context).keys(
     211                or str in help_port(context
    131212 
    132213def PORT_RANGE(context, str): 
     
    134215        return PORT(context, range[0]) and (len(range) < 2 or len(range) > 1 and PORT(context, range[1])) 
    135216 
    136 def insert_rule(context, action, source = [], sport = [], destination = [], dport = [], protocol = None, description = None, where = [ 'bottom' ], index = [ None ], state = []): 
    137         action = action[0] 
    138         if description: description = description[0] 
    139         if state: state = state[0] 
    140         if index: index = index[0] 
    141         if where: where = where[0] 
    142         if protocol: protocol = protocol[0] 
    143         ruleset.add(Rule(action, source, sport, destination, dport, protocol, description, state), where, index) 
     217def RULE(context, str): 
     218        try: 
     219                return int(str) >= 0 and int(str) < len(firewall.rules) 
     220        except: 
     221                return False 
     222 
     223def FIREWALL(context, str): 
     224        print help_firewall(context) 
     225        return str in help_firewall(context) 
     226 
     227# Commands 
     228  
     229def insert_rule(context, action, source = [], sport = [], destination = [], dport = [], protocol = None, description = None, where = 'bottom', index = None, state = 'new', log = None): 
     230        if log: 
     231                if log == 'log': 
     232                        log = True 
     233                else: 
     234                        log = log[1] 
     235        if index != None: index = int(index) 
     236        firewall.add(Rule(action, to_list(source), to_list(sport), to_list(destination), to_list(dport), protocol, description, state, log), where, index) 
    144237 
    145238def remove_rule(context, rules): 
    146         for r in rules: 
    147                 ruleset.remove(int(r)) 
     239        firewall.remove(map(int, rules)) 
     240 
     241def move_rule(context, old, where = 'top', index = None): 
     242        if index and old == index: return 
     243        try: 
     244                old = int(old) 
     245                new = firewall.add(firewall.rules[old], where, index and int(index) or None) 
     246                if new < old: old += 1 
     247                firewall.remove(old) 
     248        except TypeError, IndexError: 
     249                error("Invalid move") 
     250                raise 
    148251 
    149252def list_ruleset(context): 
    150         for ruleno, rule in enumerate(ruleset.rules): 
    151                 cmd = rule.action 
    152                 if rule.state
    153                         cmd += " " + rule.state 
     253        for ruleno, rule in enumerate(firewall.rules): 
     254                cmd = "%s" % rule.action 
     255                if rule.state != 'new'
     256                        cmd += " state " + rule.state + "" 
    154257                if rule.protocol: 
    155                         cmd += " " + rule.protocol 
     258                        cmd += " " + rule.protocol + "" 
    156259                if rule.source or rule.sport: 
    157260                        cmd += " from" 
    158261                        if rule.source: 
    159                                 cmd += " " + ' '.join(rule.source) 
     262                                cmd += " " + ' '.join(rule.source) + "" 
    160263                        if rule.sport: 
    161                                 cmd += " port " + ' '.join(rule.sport) 
     264                                cmd += " port " + ' '.join(rule.sport) + "" 
    162265                if rule.destination or rule.dport: 
    163266                        cmd += " to" 
    164267                        if rule.destination: 
    165                                 cmd += " " + ' '.join(rule.destination) 
     268                                cmd += " " + ' '.join(rule.destination) + "" 
    166269                        if rule.dport: 
    167                                 cmd += " port " + ' '.join(rule.dport) 
     270                                cmd += " port " + ' '.join(rule.dport) + "" 
     271                if rule.log: 
     272                        cmd += " log" 
     273                        if type(rule.log) is str: 
     274                                cmd += " '%s'" % rule.log 
    168275                if rule.description: 
    169                         cmd += " description '%s'" % rule.description 
     276                        cmd += " description '%s'" % rule.description 
    170277                print "%3i: %s" % (ruleno, cmd) 
    171                 #r = [[ruleno], [action_map[rule.action]], rule.source, rule.sport, rule.destination, rule.dport, [rule.protocol], [rule.state], [rule.description]] 
    172278 
    173279def create_object(context, type, name, value, description = None): 
    174         if description: description = description[0] 
    175         ruleset.add_object(Object(type[0], name[0], value[0], description)) 
     280        firewall.add_object(Object(type, name, value, description)) 
    176281 
    177282def list_objects(context, type = ['network', 'ip', 'port']): 
    178         for t in type: 
    179                 objects = getattr(ruleset, t) 
    180                 keys = objects.keys() 
    181                 keys.sort() 
    182                 for k in keys: 
     283        for t in to_list(type): 
     284                objects = getattr(firewall, t) 
     285                print "%s objects" % t.title() 
     286                for k in sorted(objects): 
    183287                        o = objects[k] 
    184                         str = "object create %s %s %s" % (o.name, o.type, o.value) 
     288                        str = "  %s %s" % (o.name, o.value) 
    185289                        if o.description: str += " '%s'" % o.description 
    186290                        print str 
     
    190294        sys.exit(0) 
    191295 
     296# Grammar content 
     297  
     298placement_rules = { 
     299        'replace' : { 
     300                GROUP : 'Rule ordering.', 
     301                UNLESS_VAR : [ 'old', 'where' ], 
     302                VAR : 'where', 
     303                HELP : 'Replace an existing rule.', 
     304                RULE : { 
     305                        HELP : ('<rule>', 'Rule to replace.'), 
     306                        VAR : 'index', 
     307                        JUMP : RETURN, 
     308                }, 
     309        }, 
     310        'top|bottom' : { 
     311                GROUP : 'Rule ordering.', 
     312                UNLESS_VAR : 'where', 
     313                VAR : 'where', 
     314                HELP : { 
     315                        'top' : 'Insert rule at top of ruleset.', 
     316                        'bottom' : 'Insert rule at bottom of ruleset (default).', 
     317                }, 
     318                JUMP : RETURN, 
     319        }, 
     320        'before|after' : { 
     321                GROUP : 'Rule ordering.', 
     322                UNLESS_VAR : 'where', 
     323                VAR : 'where', 
     324                HELP : { 
     325                        'before' : 'Insert rule before another.', 
     326                        'after' : 'Insert rule after another.', 
     327                }, 
     328                RULE : { 
     329                        HELP : ('<rule>', 'Rule to insert relative to.'), 
     330                        VAR : 'index', 
     331                        JUMP : RETURN, 
     332                }, 
     333        }, 
     334} 
     335 
     336protocol_rules = { 
     337        'tcp|udp|icmp|ah|esp' : { 
     338                GROUP : 'Packet matching.', 
     339                UNLESS_VAR : 'protocol', 
     340                VAR : 'protocol', 
     341                HELP : { 
     342                        'tcp' : 'Match TCP packets.', 
     343                        'udp' : 'Match UDP packets.', 
     344                        'icmp' : 'Match ICMP packets.', 
     345                        'ah' : 'Match AH packets.', 
     346                        'esp' : 'Match ESP packets.', 
     347                }, 
     348                JUMP : RETURN, 
     349        }, 
     350} 
     351 
    192352# Grammar 
    193353cli = CLI({ 
    194         'accept|deny|reject' : { 
     354        'accept|drop|reject' : { 
     355                GLOBAL_LABEL : 'commands', 
    195356                'state' : { 
    196357                        GROUP : 'Packet matching.', 
    197358                        RANGE : 1, 
    198                         LABEL : 'state', 
    199359                        HELP : 'Match connections in this state (default: new).', 
    200360                        'new|established|invalid|related' : { 
     
    206366                                        'related' : 'Match related connections.', 
    207367                                }, 
    208                         }, 
    209                 }, 
    210                 'replace' : { 
    211                         GROUP : 'Rule ordering.', 
    212                         UNLESS_VAR : 'where', 
    213                         VAR : 'where', 
    214                         HELP : 'Replace an existing rule.', 
    215                         '\d+' : { 
    216                                 HELP : ('<rule>', 'Rule to replace.'), 
    217                                 VAR : 'index', 
    218                                 JUMP : 'commands', 
    219                         }, 
    220                 }, 
    221                 'top|bottom' : { 
    222                         GROUP : 'Rule ordering.', 
    223                         UNLESS_VAR : 'where', 
    224                         VAR : 'where', 
    225                         HELP : { 
    226                                 'top' : 'Insert rule at top of ruleset (default).', 
    227                                 'bottom' : 'Insert rule at bottom of ruleset.', 
    228                         }, 
    229                         JUMP : 'commands', 
    230                 }, 
    231                 'before|after' : { 
    232                         GROUP : 'Rule ordering.', 
    233                         UNLESS_VAR : 'where', 
    234                         VAR : 'where', 
    235                         HELP : { 
    236                                 'before' : 'Insert rule before another.', 
    237                                 'after' : 'Insert rule after another.', 
    238                         }, 
    239                         '\d+' : { 
    240                                 HELP : ('<rule>', 'Rule to insert relative to.'), 
    241                                 VAR : 'index', 
    242368                                JUMP : 'commands', 
    243369                        }, 
     
    249375                        ACTION : insert_rule, 
    250376                }, 
     377                MERGE : [ placement_rules, protocol_rules ], 
    251378                VAR : 'action', 
    252                 GLOBAL_LABEL : 'commands', 
    253379                HELP : { 
    254380                        'accept' : 'Add rule accepting matching packets.', 
    255                         'deny' : 'Add rule denying matching packets.', 
     381                        'drop' : 'Add rule dropping matching packets.', 
    256382                        'reject' : 'Add rule rejecting matching packets.', 
    257                 }, 
    258                 'tcp|udp|icmp|ah|esp' : { 
    259                         GROUP : 'Packet matching.', 
    260                         RANGE : 1, 
    261                         VAR : 'protocol', 
    262                         HELP : { 
    263                                 'tcp' : 'Match TCP packets.', 
    264                                 'udp' : 'Match UDP packets.', 
    265                                 'icmp' : 'Match ICMP packets.', 
    266                                 'ah' : 'Match AH packets.', 
    267                                 'esp' : 'Match ESP packets.', 
    268                         }, 
    269                         JUMP : 'commands', 
    270383                }, 
    271384                'from' : { 
    272385                        GROUP : 'Packet matching.', 
    273386                        HELP : 'Match source network or port.', 
    274                         IF : lambda ctx: ('protocol' in ctx and ctx['protocol'][0] in ['tcp', 'udp'] and not 'sport' in ctx) or not 'source' in ctx, 
     387                        IF : lambda ctx: ('protocol' in ctx and ctx['protocol'] in ['tcp', 'udp'] and not 'sport' in ctx) or not 'source' in ctx, 
    275388                        NETWORK_IP : { 
    276389                                VAR : 'source', 
     
    281394  &