예제 #1
0
    def interpret(self):
        '''interpret a line (first call Parser.getline())
        Returns 0 on success, 1 on error
        '''

        if not self.keyword:
            stderr('%s: no keyword set; invalid parser state' % self)
            self.errors += 1
            return 1

        if not self.ifdef_stack[0]:
            if not self.keyword in ('ifdef', 'ifndef', 'else', 'endif'):
                debug("%s: skipping %s" % (self, self.keyword))
                return 0

        # get the parser function
        try:
            func = getattr(self, 'parse_%s' % self.keyword)
        except AttributeError:
            stderr("%s: unknown keyword '%s'" % (self, self.keyword))
            self.errors += 1
            return 1

        try:
            func()
        except ParseError as parse_error:
            parse_error.perror()
            self.errors += 1
            return 1

        return 0
예제 #2
0
    def parse_end(self):
        arr = self.arr
        if len(arr) > 2:
            raise ParseError("%s: syntax error, 'end' takes only one "
                             "argument" % self)

        if arr[1] == 'verbatim':
            if not self.in_verbatim:
                raise ParseError("%s: 'end' can not be used here" % self)

            debug('end verbatim')

            self.in_verbatim = False

            bytecode_end_verbatim = firewater.globals.BYTECODE.pop()

            bytecode = firewater.bytecode.ByteCode()
            bytecode.set_verbatim(self.filename, self.lineno,
                                  self.verbatim_text)
            firewater.globals.BYTECODE.append(bytecode)

            firewater.globals.BYTECODE.append(bytecode_end_verbatim)

        else:
            raise ParseError("%s: unknown argument '%s' to 'end'" %
                             (self, arr[1]))
예제 #3
0
    def getline(self):
        '''read statement from input file
        Upon return, self.keyword should be set, as well as other members
        Returns True when OK, False on EOF
        '''

        self.full_line = None
        self.line = ''
        self.comment = None
        self.arr = None
        self.keyword = None

        while True:
            # read lines from the input file
            # variable tmp_line is used to be able to
            # do multi-line reads (backslash terminated)
            tmp_line = self.file.readline()
            if not tmp_line:
                return False

            self.lineno += 1

            if self.in_verbatim:
                # copy verbatim until the statement 'end verbatim'
                # is reached
                verbatim_line = tmp_line.strip()
                arr = verbatim_line.lower().split()
                # it is tested with an array so that both spaces
                # and tabs work
                # note that this shadows the 'end' keyword, but
                # only when in verbatim mode
                if not (len(arr) == 2 and arr[0] == 'end' and
                        arr[1] == 'verbatim'):
                    debug('verbatim line == [%s]' % verbatim_line)
                    self.verbatim_text.append(verbatim_line)
                    continue

            n = tmp_line.find('#')
            if n >= 0:
                self.comment = '    ' + tmp_line[n:].strip()
                tmp_line = tmp_line[:n]     # strip comment
            else:
                self.comment = ''

            tmp_line = tmp_line.strip()
            if not tmp_line:
                continue

            if tmp_line[-1] == '\\':
                tmp_line = tmp_line[:-1].strip()
                self.line = self.line + ' ' + tmp_line
                continue

            self.line = self.line + ' ' + tmp_line
            self.full_line = self.line + self.comment
            self.arr = self.line.split()
            self.keyword = self.arr[0].lower()
            break

        return True
예제 #4
0
def read_input_file(filename):
    '''read a (included) input file
    Returns 0 on success, or error count on errors
    '''

    errors = 0

    parser = Parser()
    try:
        parser.open(filename)
    except IOError as err:
        stderr('failed to open %s: %s' % (filename, err.strerror))
        return 1

    while parser.getline():
        parser.insert_comment_line()
        parser.interpret()

    if len(parser.ifdef_stack) > 1:
        ParseError("%s: missing 'endif' statement" % parser).perror()
        errors += 1

    parser.close()
    errors = errors + parser.errors

    debug('errors == %d' % errors)
    return errors
예제 #5
0
    def parse_define(self):
        arr = self.arr
        if len(arr) != 2:
            raise ParseError("%s: syntax error, 'define' takes only one "
                             "argument: a symbol to define" % self)

        debug('parser: define "%s"' % arr[1])
        firewater.globals.DEFINES.append(arr[1])
예제 #6
0
    def parse_verbatim(self):
        arr = self.arr
        if len(arr) > 1:
            raise ParseError("%s: syntax error, 'verbatim' does not take "
                             "any arguments" % self)

        debug('in verbatim')

        self.in_verbatim = True
        self.verbatim_text = []
예제 #7
0
    def parse_interface(self):
        arr = self.arr
        if len(arr) < 3:
            raise ParseError("%s: '%s' requires at least 2 arguments: "
                             "the interface alias and the real interface "
                             "name" % (self, self.keyword))

        alias = arr[1]
        if alias == 'any':
            raise ParseError("%s: 'any' is a reserved word" % self)

        iface_list = ' '.join(arr[2:])
        iface_list = iface_list.split(',')

        elem = self.missing_comma(iface_list)
        if elem is not None:
            raise ParseError("%s: missing comma after '%s'" % (self, elem))

        if alias in iface_list:
            raise ParseError("%s: interface %s references back to itself" %
                             (self, alias))

        if firewater.globals.INTERFACES.has_key(alias):
            raise ParseError("%s: redefinition of interface %s" % (self,
                                                                   alias))

        # expand the list by filling in any previously defined aliases
        new_iface_list = []
        while len(iface_list) > 0:
            iface = iface_list.pop(0)
            if firewater.globals.INTERFACES.has_key(iface):
                iface_list.extend(firewater.globals.INTERFACES[iface])
            else:
                # treat as real system interface name
                if not iface in new_iface_list:
                    new_iface_list.append(iface)

        debug('new interface: %s:%s' % (alias, new_iface_list))

        firewater.globals.INTERFACES[alias] = new_iface_list

        all_ifaces = firewater.globals.INTERFACES['all']
        for iface in new_iface_list:
            if not iface in all_ifaces:
                all_ifaces.append(iface)
예제 #8
0
    def parse_include(self):
        arr = self.arr
        if len(arr) <= 1:
            raise ParseError("%s: 'include' requires a filename argument" %
                             self)

        include_file = ' '.join(arr[1:])

        debug('include %s' % include_file)

        try:
            # recursively read the given parse file
            if read_input_file(include_file) > 0:
                raise ParseError("%s: error in included file %s" %
                                 (self, include_file))
        except IOError:
            raise ParseError("%s: failed to read file '%s'" % (self,
                                                               include_file))
예제 #9
0
    def parse_chain(self):
        arr = self.arr
        if len(arr) < 2:
            raise ParseError("%s: syntax error" % self)

        chain = arr[1]

        if not chain in ('incoming', 'outgoing', 'forwarding'):
            raise ParseError("%s: syntax error: unknown chain '%s'" % (self,
                                                                       chain))

        if len(arr) == 5:
            if arr[2] != 'default' or arr[3] != 'policy':
                raise ParseError("%s: syntax error" % self)

            policy = arr[4]

            debug('policy == %s' % policy)

            if not policy in ('allow', 'deny', 'accept', 'drop'):
                raise ParseError("%s: syntax error: unknown policy '%s'" %
                                 (self, policy))

            # allow for common aliases to be used here
            if policy == 'accept':
                policy = 'allow'

            if policy == 'drop':
                policy = 'deny'

            debug('set chain %s policy %s' % (chain, policy))

            # emit default policy setting code
            bytecode = firewater.bytecode.ByteCode()
            bytecode.set_policy(self.filename, self.lineno, chain, policy)
            firewater.globals.BYTECODE.append(bytecode)

        else:
            if len(arr) == 2:
                # change the current chain
                debug('set current chain %s' % chain)

                bytecode = firewater.bytecode.ByteCode()
                bytecode.set_chain(self.filename, self.lineno, chain)
                firewater.globals.BYTECODE.append(bytecode)

            else:
                raise ParseError("%s: syntax error" % self)
예제 #10
0
    def _parse_rule(self):
        '''parse a rule

        rule syntax:

        allow|deny|reject [<proto>] [from <source> [port <service>]] \
        [to <dest> [port <service>]] \
        [on [interface|iface] <iface> [interface]]
        '''

        arr = self.arr
        allow = arr.pop(0)

        if len(arr) < 1:
            raise ParseError("%s: syntax error, premature end of line" % self)

        proto = None
        if arr[0] in firewater.globals.KNOWN_PROTOCOLS:
            proto = arr.pop(0)

        if len(arr) <= 1:
            raise ParseError("%s: syntax error, premature end of line" % self)

        # the line can be parsed using tokens

        source_addr = None
        source_port = None
        dest_addr = None
        dest_port = None
        interface = None

        while len(arr) > 0:
            token = arr.pop(0)

            if len(arr) < 1:
                raise ParseError("%s: syntax error, premature end of line" %
                                 self)

            if token == 'from':
                if source_addr is not None:
                    raise ParseError("%s: syntax error ('from' is used "
                                     "multiple times)" % self)

                source_addr = arr.pop(0)

                if len(arr) > 0:
                    # check for source port
                    if arr[0] == 'port':
                        arr.pop(0)

                        if len(arr) < 1:
                            raise ParseError("%s: syntax error, premature "
                                             "end of line" % self)

                        source_port = arr.pop(0)

                continue

            elif token == 'to':
                if dest_addr is not None:
                    raise ParseError("%s: syntax error ('to' is used "
                                     "multiple times)" % self)

                dest_addr = arr.pop(0)

                if len(arr) > 0:
                    # check for dest port
                    if arr[0] == 'port':
                        arr.pop(0)

                        if len(arr) < 1:
                            raise ParseError("%s: syntax error, premature "
                                             "end of line" % self)

                        dest_port = arr.pop(0)

                continue

            elif token == 'on':
                if interface is not None:
                    raise ParseError("%s: syntax error ('on' is used "
                                     "multiple times)" % self)

                if arr[0] in ('interface', 'iface'):
                    arr.pop(0)

                    if len(arr) < 1:
                        raise ParseError("%s: syntax error, premature "
                                         "end of line" % self)

                interface = arr.pop(0)

                if len(arr) > 0 and arr[0] in ('interface', 'iface'):
                    arr.pop(0)

                continue

            else:
                raise ParseError("%s: syntax error, unknown token '%s'" %
                                 (self, token))

        debug('rule {')
        debug('  %s proto %s' % (allow, proto))
        debug('  source (%s, %s)' % (source_addr, source_port))
        debug('  dest   (%s, %s)' % (dest_addr, dest_port))
        debug('  iface   %s' % interface)
        debug('}')

        sources = self._parse_rule_address(source_addr)
        source_port = self._parse_rule_service(source_port)
        destinations = self._parse_rule_address(dest_addr)
        dest_port = self._parse_rule_service(dest_port)
        ifaces = self._parse_rule_interfaces(interface)

        debug('rule got {')
        debug('  sources: ' + str(sources))
        debug('  port: ' + str(source_port))
        debug('  destinations: ' + str(destinations))
        debug('  port: ' + str(dest_port))
        debug('  ifaces: ' + str(ifaces))
        debug('}')

        if not proto and (source_port.port > 0 or dest_port.port > 0):
            if source_port.port > 0 and source_port.proto:
                proto = source_port.proto

            if dest_port.port > 0 and dest_port.proto:
                proto = dest_port.proto

            if not proto:
                raise ParseError("%s: missing protocol" % self)

        # save the rule in globals.BYTECODE[]
        # the output statements are generated later,
        # if there were no parse errors

        for src in sources:
            for dest in destinations:
                if not ifaces:
                    debug('%s: %s %s %s eq %s %s eq %s' % (self, allow, proto,
                                                           src, source_port,
                                                           dest, dest_port))
                    bytecode = firewater.bytecode.ByteCode()
                    bytecode.set_rule(self.filename, self.lineno, allow,
                                      proto, src, source_port,
                                      dest, dest_port, None)
                    firewater.globals.BYTECODE.append(bytecode)
                else:
                    for iface in ifaces:
                        debug('%s: %s %s %s eq %s %s eq %s on %s' %
                              (self, allow, proto, src, source_port,
                               dest, dest_port, iface))
                        bytecode = firewater.bytecode.ByteCode()
                        bytecode.set_rule(self.filename, self.lineno, allow,
                                          proto, src, source_port,
                                          dest, dest_port, iface)
                        firewater.globals.BYTECODE.append(bytecode)
예제 #11
0
    def parse_service(self):
        arr = self.arr
        if len(arr) < 3:
            raise ParseError("%s: '%s' requires at least 2 arguments: "
                             "the service alias and at least 1 property" %
                             (self, arr[0]))

        alias = arr[1]
        if alias == 'any':
            raise ParseError("%s: 'any' is a reserved word" % self)

        if firewater.globals.SERVICES.has_key(alias):
            raise ParseError("%s: redefinition of service %s" % (self, alias))

        obj = firewater.service.ServiceObject(alias)

        if arr[2] in firewater.globals.KNOWN_PROTOCOLS:
            obj.proto = arr.pop(2)

        if len(arr) < 3:
            raise ParseError("%s: missing service or port number" % self)

        # parse port range or number, or alias, or service name
        m = REGEX_PORT_RANGE.match(arr[2])
        if m is not None:
            # it's a port range
            port_range = arr[2]
            obj.port = int(m.groups()[0])
            if obj.port < 0 or obj.port > 65535:
                raise ParseError("%s: invalid port range '%s'" %
                                 (self, port_range))

            obj.endport = int(m.groups()[1])
            if obj.endport < 0 or obj.endport > 65535:
                raise ParseError("%s: invalid port range '%s'" %
                                 (self, port_range))
        else:
            m = REGEX_NUMERIC.match(arr[2])
            if m is not None:
                # it's a single port number
                obj.port = int(arr[2])
                if obj.port < 0 or obj.port > 65535:
                    raise ParseError("%s: invalid port number '%d'" %
                                     (self, obj.port))
            else:
                # it's a string
                if arr[2] == alias:
                    raise ParseError("%s: service %s references back to "
                                     "itself" % self)

                if firewater.globals.SERVICES.has_key(arr[2]):
                    obj2 = firewater.globals.SERVICES[arr[2]]

                    # copy the other service object
                    if not obj.proto:
                        obj.proto = obj2.proto

                    obj.port = obj2.port
                    obj.endport = obj2.endport
                    obj.iface = obj2.iface
                else:
                    # treat as system service name
                    obj.port = firewater.service.servbyname(arr[2])
                    if obj.port is None:
                        raise ParseError("%s: no such service '%s'" %
                                         (self, arr[2]))

        if len(arr) > 3:
            if arr[3] in ('iface', 'interface'):
                if len(arr) == 5:
                    # interface-specific service
                    iface = arr[4]
                    if firewater.globals.INTERFACES.has_key(iface):
                        obj.iface = firewater.globals.INTERFACES[iface]
                    else:
                        # treat as real system interface
                        obj.iface = []
                        obj.iface.append(arr[4])

                else:
                    raise ParseError("%s: too many arguments to '%s'" %
                                     (self, arr[0]))

        debug('new service: %s:%s' % (alias, obj))
        firewater.globals.SERVICES[alias] = obj
예제 #12
0
    def parse_group(self):
        arr = self.arr
        if len(arr) < 3:
            raise ParseError("%s: 'group' requires at least 2 arguments: "
                             "the group alias and at least 1 member" % self)

        alias = arr[1]
        if alias == 'any':
            raise ParseError("%s: 'any' is a reserved word" % self)

        group_list = ','.join(arr[2:])
        group_list = group_list.replace(' ', '')
        group_list = group_list.replace(',,', ',')
        group_list = group_list.split(',')

        elem = self.missing_comma(group_list)
        if elem is not None:
            raise ParseError("%s: missing comma after '%s'" % (self, elem))

        if alias in group_list:
            raise ParseError("%s: range %s references back to itself" %
                             (self, alias))

        # note that group are stored in the same way as groups
        if firewater.globals.HOSTS.has_key(alias):
            raise ParseError("%s: redefinition of range or group %s" %
                             (self, alias))

        # expand the list by filling in any previously defined aliases
        new_group_list = []
        while len(group_list) > 0:
            group = group_list.pop(0)
            if firewater.globals.HOSTS.has_key(group):
                group_list.extend(firewater.globals.HOSTS[group])
            else:
                # treat as IP address or fqdn
                if group.find(':') > -1:
                    # treat as IPv6 address
                    pass

                elif group.find('/') > -1:
                    # treat as network range
                    a = group.split('/')
                    if len(a) != 2:
                        raise ParseError("%s: invalid address range '%s'" %
                                         (self, group))

                    if not _is_ipv4_address(a[0]):
                        raise ParseError("%s: invalid address range '%s'" %
                                         (self, group))

                    try:
                        bits = int(a[1])
                    except ValueError:
                        raise ParseError("%s: invalid address range '%s'" %
                                         (self, group))

                    if bits < 0 or bits > 32:
                        raise ParseError("%s: invalid address range '%s'" %
                                         (self, group))

                else:
                    # treat as fqdn, so resolve the address
                    addrs = firewater.resolv.resolv(group)
                    if addrs is None:   # error
                        raise ParseError("%s: failed to resolve '%s'" %
                                         (self, group))

                    for addr in addrs:
                        if not addr in new_group_list:
                            new_group_list.append(addr)

                    continue

                if not group in new_group_list:
                    new_group_list.append(group)

        debug('new group: %s:%s' % (alias, new_group_list))

        firewater.globals.HOSTS[alias] = new_group_list
예제 #13
0
    def parse_range(self):
        arr = self.arr
        if len(arr) < 3:
            raise ParseError("%s: '%s' requires at least 2 arguments: "
                             "the range alias and the address range" %
                             (self, arr[0]))

        alias = arr[1]
        if alias == 'any':
            raise ParseError("%s: 'any' is a reserved word" % self)

        ranges_list = ' '.join(arr[2:])
        ranges_list = ranges_list.replace(' ', '')
        ranges_list = ranges_list.replace(',,', ',')
        ranges_list = ranges_list.split(',')

        elem = self.missing_comma(ranges_list)
        if elem is not None:
            raise ParseError("%s: missing comma after '%s'" % (self, elem))

        if alias in ranges_list:
            raise ParseError("%s: %s %s references back to itself" %
                             (self, arr[0], alias))

        # note that ranges are stored in the same way as hosts
        if firewater.globals.HOSTS.has_key(alias):
            raise ParseError("%s: redefinition of %s or host %s" %
                             (self, arr[0], alias))

        # expand the list by filling in any previously defined aliases
        new_ranges_list = []
        while len(ranges_list) > 0:
            # 'range' is a Python keyword ...
            # so I use 'host' instead (confusing huh?)
            host = ranges_list.pop(0)
            if firewater.globals.HOSTS.has_key(host):
                ranges_list.extend(firewater.globals.HOSTS[host])
            else:
                # treat as IP address or fqdn
                if host.find(':') > -1:
                    # treat as IPv6 address
                    pass

                elif host.find('/') > -1:
                    # treat as network range
                    a = host.split('/')
                    if len(a) != 2:
                        raise ParseError("%s: invalid address range '%s'" %
                                         (self, host))

                    if not _is_ipv4_address(a[0]):
                        raise ParseError("%s: invalid address range '%s'" %
                                         (self, host))

                    try:
                        bits = int(a[1])
                    except ValueError:
                        raise ParseError("%s: invalid address range '%s'" %
                                         (self, host))

                    if bits < 0 or bits > 32:
                        raise ParseError("%s: invalid address range '%s'" %
                                         (self, host))

                else:
                    raise ParseError("%s: invalid address range '%s'" %
                                     (self, host))

                if not host in new_ranges_list:
                    new_ranges_list.append(host)

        debug('new %s: %s:%s' % (arr[0], alias, new_ranges_list))
        firewater.globals.HOSTS[alias] = new_ranges_list
예제 #14
0
    def parse_host(self):
        arr = self.arr
        if len(arr) < 3:
            raise ParseError("%s: 'host' requires at least 2 arguments: "
                             "the host alias and the IP address or fqdn" %
                             self)

        alias = arr[1]
        if alias == 'any':
            raise ParseError("%s: 'any' is a reserved word" % self)

        host_list = ' '.join(arr[2:])
        host_list = host_list.replace(' ', '')
        host_list = host_list.replace(',,', ',')
        host_list = host_list.split(',')

        elem = self.missing_comma(host_list)
        if elem is not None:
            raise ParseError("%s: missing comma after '%s'" % (self, elem))

        if alias in host_list:
            raise ParseError("%s: host %s references back to itself" %
                             (self, alias))

        if firewater.globals.HOSTS.has_key(alias):
            raise ParseError("%s: redefinition of host %s" % (self, alias))

        # expand the list by filling in any previously defined aliases
        new_host_list = []
        while len(host_list) > 0:
            host = host_list.pop(0)
            if firewater.globals.HOSTS.has_key(host):
                host_list.extend(firewater.globals.HOSTS[host])
            else:
                # treat as IP address or fqdn
                if host.find(':') > -1:
                    # treat as IPv6 address
                    pass

                elif host.find('/') > -1:
                    # treat as network range
                    a = host.split('/')
                    if len(a) != 2:
                        raise ParseError("%s: invalid host address '%s'" %
                                         (self, host))

                    if not _is_ipv4_address(a[0]):
                        raise ParseError("%s: invalid host address '%s'" %
                                         (self, host))

                    if a[1] != '32':
                        raise ParseError("%s: invalid host address '%s'" %
                                         (self, host))

                elif _is_ipv4_address(host):
                    # treat as IPv4 address
                    pass

                else:
                    # treat as fqdn, so resolve the address
                    addrs = firewater.resolv.resolv(host)
                    if addrs is None:   # error
                        raise ParseError("%s: failed to resolve '%s'" %
                                         (self, host))

                    for addr in addrs:
                        if not addr in new_host_list:
                            new_host_list.append(addr)

                    continue

                if not host in new_host_list:
                    new_host_list.append(host)

        debug('new host: %s:%s' % (alias, new_host_list))
        firewater.globals.HOSTS[alias] = new_host_list