Changeset 128
- Timestamp:
- 07/19/05 06:29:58 (3 years ago)
- Files:
-
- fwc/trunk/Config.py (modified) (1 diff)
- fwc/trunk/DynamicLoader.py (modified) (1 diff)
- fwc/trunk/Engine.py (modified) (3 diffs)
- fwc/trunk/Firewall.py (modified) (1 diff)
- fwc/trunk/fwc (modified) (3 diffs)
- fwc/trunk/Interface.py (modified) (1 diff)
- fwc/trunk/LinuxIPTables.py (modified) (1 diff)
- fwc/trunk/MANIFEST (modified) (1 diff)
- fwc/trunk/Network.py (added)
- fwc/trunk/Object.py (modified) (1 diff)
- fwc/trunk/Resolver.py (modified) (1 diff)
- fwc/trunk/setup.py (modified) (1 diff)
- fwc/trunk/Singleton.py (modified) (2 diffs)
- fwc/trunk/Transactional.py (modified) (1 diff)
- fwc/trunk/util.py (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
fwc/trunk/Config.py
r127 r128 1 1 import string 2 2 import re 3 from util import abstract 4 from copy import deepcopy 3 5 4 6 class Config(object): 5 class Error(Exception): pass 6 class ReadOnly(Error): pass 7 class InvalidValue(Error): pass 7 class Error(Exception): pass 8 class ReadOnly(Error): pass 9 class InvalidValue(Error): pass 10 class KeyRequired(Error): pass 11 class KeyError(Error): pass 8 12 9 class Template(string.Template): 10 idpattern = '[_a-z][-_a-z0-9]*' 13 class Serialiser: 14 @abstract 15 def serialise(self, out): 16 """ Serialise this object in a form that can be parsed by FWC. 17 "out" is an I/O object. """ 11 18 12 class Value: 13 def __init__(self, name, value, help = 'No help available', readonly = None, constraint = '.*'): 14 self.name, self.value, self.help, self.readonly, self.constraint = name, value, help, readonly, constraint 19 class __Template(string.Template): 20 idpattern = '[_a-z][-_a-z0-9]*' 15 21 16 class Iterator:17 def __init__(self, config):18 self.__config = config 19 self.__iter = iter(config) 22 class Value: 23 def __init__(self, name, value, help = ('<value>', 'No help available'), immutable = False, constraint = '.*', required = True): 24 if type(help) is str: help = ('<value>', help) 25 self.name, self.value, self.help, self.immutable, self.constraint, required = name, value, help, immutable, constraint, required 20 26 21 def next(self): 22 n = self.__iter.next() 23 return (n, self.__config[n].value) 27 class Iterator: 28 def __init__(self, config): 29 self.__config = config 30 self.__iter = iter(config) 24 31 25 def __init__(self): 26 object.__setattr__(self, '_Config__store', {}) 32 def next(self): 33 n = self.__iter.next() 34 return (n, self.__config[n].value) 27 35 28 def define(self, key, value, help = None, readonly = False):29 key = key.replace('-', '_')30 self.__store[key] = Config.Value(key, value, help, readonly)36 def __init__(self, config = ()): 37 object.__setattr__(self, '_Config__store', {}) 38 self.merge(config) 31 39 32 def __contains__(self, key): 33 return key in self.__store 40 def merge(self, config): 41 for v in config: 42 assert isinstance(v, Config.Value) 43 self.define(v.name, deepcopy(v)) 34 44 35 def __getattr__(self, name): 36 return self.__store[name].value 37 __getitem__ = __getattr__ 45 def define(self, key, value, **argd): 46 key = key.replace('-', '_') 47 if isinstance(value, Config.Value): 48 self.__store[key] = value 49 else: 50 self.__store[key] = Config.Value(key, value, **argd) 38 51 39 def __setattr__(self, name, value): 40 if name not in self.__store: 41 raise KeyError(name) 42 v = self.__store[name] 43 if v.readonly: raise Config.ReadOnly('"%s" is read only' % name) 44 if not re.match(v.constraint, value): raise Config.InvalidValue('Invalid value "%s" for "%s"' % (value, name)) 45 v.value = value 46 __setitem__ = __setattr__ 52 def __contains__(self, key): 53 return key in self.__store 47 54 48 def __iter__(self): 49 return iter(self.__store) 55 def __getattr__(self, name): 56 return self.__store[name].value 57 __getitem__ = __getattr__ 50 58 51 def __delattr__(self, key): 52 del(self.__store[key]) 53 __delitem__ = __delattr__ 59 def __setattr__(self, name, value): 60 if name not in self.__store: 61 raise Config.KeyError("unknown configuration key '%s'" % name) 62 v = self.__store[name] 63 if v.immutable: 64 raise Config.ReadOnly("Key '%s' is read only" % name) 65 if not re.match(v.constraint, value): 66 raise Config.InvalidValue("Invalid value '%s' for '%s'" % (value, name)) 67 v.value = value 68 __setitem__ = __setattr__ 54 69 55 def iteritems(self): 56 """ Iterate over (k, v) pairs """ 57 return Config.Iterator(self.__store) 70 def __iter__(self): 71 return iter(self.__store) 58 72 59 def __len__(self): 60 return len(self.__store) 73 def __delattr__(self, key): 74 if key not in self.__store: 75 raise Config.KeyError("can't delete non-existent configuration key '%s'" % name) 76 if self.__store[key].required: 77 raise Config.KeyRequired("Key '%s' is required and can not be removed" % key) 78 del(self.__store[key]) 79 __delitem__ = __delattr__ 61 80 62 def keys(self): 63 return self.__store.keys() 81 def iteritems(self): 82 """ Iterate over (k, v) pairs """ 83 return Config.Iterator(self.__store) 64 84 65 def values(self):66 return [x.value for x in self.__store.values()] 85 def __len__(self): 86 return len(self.__store) 67 87 68 def help(self, key):69 return self.__store[key].help 88 def keys(self): 89 return self.__store.keys() 70 90 71 def readonly(self, key): 72 return self.__store[key].readonly 91 def raw(self, key): 92 """ Fetch raw Config.Value object """ 93 return self.__store[key] 73 94 74 def validate(self, key, value):75 return re.match(self.__store[key].constraint, value) 95 def values(self): 96 return [x.value for x in self.__store.values()] 76 97 77 def expand(self, text): 78 vars = dict([(v.name.replace('_', '-'), v.value) for v in self.__store.values()]) 79 lasttext = '' 80 while '$' in text and lasttext != text: 81 lasttext = text[:] 82 text = Config.Template(text).safe_substitute(vars) 83 return text 98 def help(self, key): 99 return self.__store[key].help 100 101 def immutable(self, key): 102 return self.__store[key].immutable 103 104 def validate(self, key, value): 105 return re.match(self.__store[key].constraint, value) 106 107 def expand(self, text): 108 vars = dict([(v.name.replace('_', '-'), v.value) for v in self.__store.values()]) 109 lasttext = '' 110 while '$' in text and lasttext != text: 111 lasttext = text[:] 112 text = Config.__Template(text).safe_substitute(vars) 113 return text fwc/trunk/DynamicLoader.py
r123 r128 2 2 3 3 def loadModule(modulePath): 4 try:5 aMod = sys.modules[modulePath]6 if not isinstance(aMod, types.ModuleType):7 raise KeyError8 except KeyError:9 # The last [''] is very important!10 aMod = __import__(modulePath, globals(), locals(), [''])11 sys.modules[modulePath] = aMod12 return aMod4 try: 5 aMod = sys.modules[modulePath] 6 if not isinstance(aMod, types.ModuleType): 7 raise KeyError 8 except KeyError: 9 # The last [''] is very important! 10 aMod = __import__(modulePath, globals(), locals(), ['']) 11 sys.modules[modulePath] = aMod 12 return aMod 13 13 14 14 def loadFunction(fullFuncName): 15 """Retrieve a function object from a full dotted-package name."""16 17 # Parse out the path, module, and function18 lastDot = fullFuncName.rfind(u".")19 funcName = fullFuncName[lastDot + 1:]20 modPath = fullFuncName[:lastDot]21 22 aMod = loadModule(modPath)23 try:24 aFunc = getattr(aMod, funcName)25 except AttributeError:26 raise ImportError("no such function '" + funcName + "'")27 28 # Assert that the function is a *callable* attribute.29 assert callable(aFunc), u"%s is not callable." % fullFuncName30 31 # Return a reference to the function itself,32 # not the results of the function.33 return aFunc15 """Retrieve a function object from a full dotted-package name.""" 16 17 # Parse out the path, module, and function 18 lastDot = fullFuncName.rfind(u".") 19 funcName = fullFuncName[lastDot + 1:] 20 modPath = fullFuncName[:lastDot] 21 22 aMod = loadModule(modPath) 23 try: 24 aFunc = getattr(aMod, funcName) 25 except AttributeError: 26 raise ImportError("no such function '" + funcName + "'") 27 28 # Assert that the function is a *callable* attribute. 29 assert callable(aFunc), u"%s is not callable." % fullFuncName 30 31 # Return a reference to the function itself, 32 # not the results of the function. 33 return aFunc 34 34 35 35 def loadClass(fullClassName, parentClass=None): 36 """Load a module and retrieve a class (NOT an instance).37 38 If the parentClass is supplied, className must be of parentClass39 or a subclass of parentClass (or None is returned).40 """41 aClass = loadFunction(fullClassName)42 43 # Assert that the class is a subclass of parentClass.44 if parentClass is not None:45 if not issubclass(aClass, parentClass):46 raise TypeError(u"%s is not a subclass of %s" %47 (fullClassName, parentClass))48 49 # Return a reference to the class itself, not an instantiated object.50 return aClass36 """Load a module and retrieve a class (NOT an instance). 37 38 If the parentClass is supplied, className must be of parentClass 39 or a subclass of parentClass (or None is returned). 40 """ 41 aClass = loadFunction(fullClassName) 42 43 # Assert that the class is a subclass of parentClass. 44 if parentClass is not None: 45 if not issubclass(aClass, parentClass): 46 raise TypeError(u"%s is not a subclass of %s" % 47 (fullClassName, parentClass)) 48 49 # Return a reference to the class itself, not an instantiated object. 50 return aClass fwc/trunk/Engine.py
r127 r128 1 import os 1 2 import re 2 3 import sys … … 4 5 import readline 5 6 import textwrap 7 from copy import copy, deepcopy 8 from fnmatch import fnmatch 6 9 from ConfigParser import RawConfigParser 7 10 from Config import Config … … 9 12 from CLY.Parser import Parser 10 13 from CLY.Symbols import * 11 from util import *12 14 from Resolver import Resolver 13 15 from Firewall import Firewall 14 from LinuxIPTables import LinuxIPTables15 16 from Object import Object 16 17 from Singleton import Singleton 17 from fnmatch import fnmatch 18 19 class Engine(Singleton): 20 version = '0.1' 21 firewall_types = {'linux' : 'LinuxIPTables'} 22 23 class Error(Exception): pass 24 25 def __init__(self): 26 self.__config = RawConfigParser() 27 self.__config.read(['/etc/fwcrc', os.path.expanduser('~/.fwcrc')]) 28 self.config = Config() 29 self.config.define('prompt', 'fwc> ', 'FWC prompt') 30 # TODO: Merge loaded config into self.config 31 self.__firewalls = {} 32 # Grammar hooks. 33 self.__hooks = { 34 'match' : [], 35 'nat-to' : [], 36 'nat-from' : [], 37 'nat' : [], 38 'from' : [], 39 'to' : [], 40 } 41 self.existing_input = '' 42 43 # Currently active firewall 44 self.firewall = None 45 self.__placement_rules = { 46 'replace' : { 47 GROUP : 10, 48 UNLESS_VAR : [ 'old', 'where' ], 49 VAR : 'where', 50 HELP : 'Replace an existing rule.', 51 RULE : { 52 HELP : ('<rule>', 'Rule to replace.'), 53 VAR : 'index', 54 JUMP : RETURN, 55 }, 56 }, 57 'top|bottom' : { 58 GROUP : 10, 59 UNLESS_VAR : 'where', 60 VAR : 'where', 61 HELP : { 62 'top' : 'Insert rule at top of policy.', 63 'bottom' : 'Insert rule at bottom of policy (default).', 64 }, 65 JUMP : RETURN, 66 }, 67 'before|after' : { 68 GROUP : 10, 69 UNLESS_VAR : 'where', 70 VAR : 'where', 71 HELP : { 72 'before' : 'Insert rule before another.', 73 'after' : 'Insert rule after another.', 74 }, 75 RULE : { 76 HELP : ('<rule>', 'Rule to insert relative to.'), 77 VAR : 'index', 78 JUMP : RETURN, 79 }, 80 }, 81 } 82 83 self.__protocol_rules = { 84 lambda ctx, token: self.resolver.have_object(Object.PROTOCOL, token) : { 85 GROUP : 30, 86 UNLESS_VAR : 'protocol', 87 VAR : 'protocol', 88 HELP : lambda x: [(x.name, x.description or 'Protocol %s' % x.value) for x in self.resolver.get_objects('protocol')], 89 JUMP : RETURN, 90 }, 91 'protocol' : { 92 GROUP : 30, 93 HELP : 'Arbitrary IP protocol number.', 94 UNLESS_VAR : 'protocol', 95 '\d+' : { 96 HELP : ('<protocol>', 'IP protocol number.'), 97 JUMP : RETURN, 98 VAR : 'protocol', 99 }, 100 }, 101 } 102 103 self.hook('nat', self.__placement_rules) 104 self.hook('nat', self.__protocol_rules) 105 106 self.__grammar = { 107 'nat' : { 108 IF : have_firewall, 109 MERGE : self.__hooks['nat'], 110 GROUP : 10, 111 HELP : 'NAT matching packets.', 112 VAR : 'action', 113 'to' : { 114 MERGE : self.__hooks['nat-to'], 115 HELP : 'Specify destination to NAT to.', 116 NETWORK : { 117 VAR : 'nat_ip', 118 HELP : ('<ip>', 'NAT to IP address.'), 119 'if' : { 120 HELP : 'NAT on the following conditions.', 121 JUMP : 'commands', 122 }, 123 }, 124 }, 125 }, 126 'accept|drop|reject' : { 127 GROUP : 10, 128 IF : have_firewall, 129 GLOBAL_LABEL : 'commands', 130 MERGE : [ self.__placement_rules, self.__protocol_rules, self.__hooks['match'] ], 131 VAR : 'action', 132 ACTION : { 133 HELP : 'Add rule to firewall.', 134 ACTION : self.__insert_rule, 135 }, 136 HELP : { 137 'accept' : 'Add rule accepting matching packets.', 138 'drop' : 'Add rule dropping matching packets.', 139 'reject' : 'Add rule rejecting matching packets.', 140 }, 141 'with' : { 142 IF : lambda ctx: 'reject_type' not in ctx and ctx['action'] == 'reject', 143 HELP : 'Reject with the specified packet type.', 144 'tcp' : { 145 VAR : 'reject_type', 146 IF : lambda ctx: 'protocol' in ctx and ctx['protocol'] == 'tcp', 147 HELP : 'Reject with tcp... packet.', 148 'reset' : { 149 VAR : 'reject_subtype', 150 HELP : 'Reject with TCP reset.', 151 JUMP : 'commands', 152 }, 153 }, 154 'network|host|protocol|admin' : { 155 VAR : 'reject_type', 156 HELP : { 157 'network' : 'Reject with icmp-network... packet.', 158 'host' : 'Reject with icmp-host... packet.', 159 'protocol' : 'Reject with icmp-protocol... packet.', 160 'admin' : 'Reject with icmp-admin... packet.', 161 }, 162 'unreachable' : { 163 HELP : lambda ctx: { 'unreachable' : 'Reject with icmp-%s-unreachable.' % ctx['reject_type'] }, 164 IF : lambda ctx: ctx['reject_type'] != 'admin', 165 VAR : 'reject_subtype', 166 JUMP : 'commands', 167 }, 168 'prohibited' : { 169 HELP : lambda ctx: { 'prohibited' : 'Reject with icmp-%s-prohibited.' % ctx['reject_type'] }, 170 IF : lambda ctx: ctx['reject_type'] in ('network', 'host', 'admin'), 171 VAR : 'reject_subtype', 172 JUMP : 'commands', 173 }, 174 }, 175 176 }, 177 'in' : { 178 GROUP : 20, 179 HELP : 'Match packets coming in an interface.', 180 lambda ctx, arg: INTERFACE(ctx, arg) and 'in_if' not in ctx or arg not in ctx['in_if'] : { 181 HELP : lambda ctx: [(k, v) for k, v in help_interface(ctx).iteritems() if 'in_if' not in ctx or k not in ctx['in_if']], 182 VAR : 'in_if', 183 LABEL : 'in-if', 184 JUMP_TO : 'in-if', 185 JUMP : 'commands', 186 }, 187 }, 188 'out' : { 189 GROUP : 20, 190 HELP : 'Match packets going out an interface.', 191 INTERFACE : { 192 HELP : help_interface, 193 VAR : 'out_if', 194 LABEL : 'out-if', 195 JUMP_TO : 'out-if', 196 JUMP : 'commands', 197 }, 198 }, 199 'from' : { 200 GROUP : 20, 201 HELP : 'Match source network or port.', 202 IF : lambda ctx: ('protocol' in ctx and ctx['protocol'] in ['tcp', 'udp'] and not 'sport' in ctx) or not 'source' in ctx, 203 NETWORK : { 204 VAR : 'source', 205 LABEL : 'source', 206 JUMP : 'commands', 207 JUMP_TO : 'source', 208 GROUP : 5, 209 'port' : { 210 GROUP : 20, 211 RANGE : 1, 212 HELP : 'Match source port.', 213 PORT : { 214 LABEL : 'sport', 215 VAR : 'sport', 216 JUMP : 'commands', 217 JUMP_TO : 'sport', 218 HELP : lambda ctx: help_object(ctx, 'port'), 219 }, 220 IF : check_port_protocol, 221 }, 222 HELP : help_network, 223 }, 224 'port' : { 225 HELP : 'Match source port.', 226 GROUP : 20, 227 PORT : { 228 GROUP : 5, 229 HELP : help_port, 230 LABEL : 'sports', 231 VAR : 'sport', 232 JUMP : 'commands', 233 # This allows syntax like: accept from port 22 23 25 to anywhere 234 JUMP_TO : 'sports', 235 }, 236 IF : check_port_protocol, 237 }, 238 }, 239 'description' : { 240 '.+' : { 241 VAR : 'description', 242 JUMP : 'commands', 243 HELP : ('<description>', 'Rule description.'), 244 }, 245 RANGE : 1, 246 HELP : 'Set rule description.' 247 }, 248 'to' : { 249 GROUP : 20, 250 IF : lambda ctx: ('protocol' in ctx and ctx['protocol'] in ['tcp', 'udp'] and not 'dport' in ctx) or not 'destination' in ctx, 251 UNLESS_VAR : [ 'dport', 'destination' ], 252 NETWORK : { 253 GROUP : 5, 254 LABEL : 'destination', 255 VAR : 'destination', 256 JUMP : 'commands', 257 JUMP_TO : 'destination', 258 'port' : { 259 GROUP : 20, 260 PORT : { 261 GROUP : 5, 262 LABEL : 'dport', 263 VAR : 'dport', 264 JUMP : 'commands', 265 JUMP_TO : 'dport', 266 HELP : help_port, 267 }, 268 IF : check_port_protocol, 269 HELP : 'Match destination port.', 270 }, 271 HELP : help_network, 272 }, 273 'port' : { 274 GROUP : 20, 275 PORT : { 276 GROUP : 5, 277 LABEL : 'dports', 278 VAR : 'dport', 279 JUMP : 'commands', 280 JUMP_TO : 'dports', 281 HELP : help_port, 282 }, 283 IF : check_port_protocol, 284 HELP : 'Match destination port.', 285 }, 286 HELP : 'Match destination network or port.', 287 }, 288 'log' : { 289 VAR : 'log', 290 UNLESS_VAR : 'log', 291 HELP : "Log a message when this rule matches.", 292 'message' : { 293 HELP : 'Log with custom message.', 294 '.+' : { 295 HELP : ('<message>', 'Custom log message.'), 296 JUMP : 'commands', 297 VAR : 'log', 298 }, 299 }, 300 JUMP : 'commands', 301 }, 302 }, 303 'quit|exit' : { 304 GROUP : 20000, 305 HELP : [ 'quit', 'Exit.' ], 306 ACTION : { 307 HELP : 'Exit.', 308 ACTION : self.quit, 309 }, 310 }, 311 '!\d+' : { 312 VAR : 'index', 313 ACTION : lambda ctx, index: self.__set_history(ctx, index[1:]), 314 }, 315 'history' : { 316 GROUP : 20000, 317 HELP : 'Command history management.', 318 ACTION : { 319 HELP : 'Show history.', 320 ACTION : self.__list_history, 321 }, 322 'clear' : { 323 HELP : 'Clear history.', 324 ACTION : self.__clear_history, 325 }, 326 '\d+' : { 327 VAR : 'index', 328 HELP : ('<index>', 'History item index.'), 329 ACTION : self.__set_history, 330 }, 331 }, 332 'object' : { 333 IF : have_firewall, 334 HELP : 'Firewall object manipulation.', 335 'import' : { 336 LABEL : 'import', 337 'all' : { 338 RANGE : 1, 339 GROUP : 10, 340 HELP : 'Also import aliases.', 341 VAR : 'aliases', 342 JUMP : 'import', 343 }, 344 's|'.join(Resolver.get_object_types()) + 's' : { 345 GROUP : 20, 346 VAR : 'import_types', 347 HELP : lambda ctx: [(x + 's', 'Import system %s objects' % x) for x in Resolver.get_object_types()], 348 ACTION : lambda ctx, import_types, **args: self.resolver.populate_defaults(tolist(import_types), with_aliases = 'aliases' in ctx), 349 }, 350 HELP : 'Import system objects.', 351 ACTION : lambda ctx, **args: self.resolver.populate_defaults(with_aliases = 'aliases' in ctx), 352 }, 353 'create' : { 354 HELP : 'Create a policy object.', 355 '|'.join(Resolver.get_object_types()) : { 356 VAR : 'type', 357 HELP : lambda ctx: [(x, 'Create %s object' % x) for x in Resolver.get_object_types()], 358 Object.NAME_PATTERN : { 359 VAR : 'name', 360 HELP : { '<name>' : 'Name of object to create.' }, 361 LABEL : 'create', 362 'description' : { 363 UNLESS_VAR : 'description', 364 ORDER : 10, 365 GROUP : 20, 366 '.+' : { 367 VAR : 'description', 368 HELP : { '<description>' : 'Description of object.' }, 369 JUMP : 'create', 370 ACTION : { 371 IF_VAR : 'value', 372 ACTION : self.__create_object, 373 }, 374 }, 375 HELP : 'Optional description of object.' 376 }, 377 lambda ctx, token: re.match(Object.TYPE_PATTERNS[ctx['type']], token) or token in help_object(ctx, ctx['type']) : { 378 ORDER : 20, 379 GROUP : (10, 'Add ...'), 380 VAR : 'value', 381 HELP : lambda ctx: [(k, v) for k, v in help_object(ctx, ctx['type']).iteritems() if 'value' in ctx and k not in tolist(ctx['value']) or 'va
