Sophie

Sophie

distrib > Mandriva > 2010.0 > i586 > media > contrib-release > by-pkgid > dca483b59ba61f3fa092de932ddd570e > files > 774

nuface-2.0.14-2mdv2009.1.i586.rpm

#!/usr/bin/python

#
# Copyright(C) 2005 INL
# Written by Jean Gillaux <jean@inl.fr>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
#  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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
#
#
"""
Generates iptables commands and ldap nufw acls from xml file.

Nupyf takes an nufw acls xml file and an nufw firewall xml file and can
    * output iptables commands for both nufw and rescue (standard) modes
    * inserts nufw acls into an ldap tree

usage: nupyf [options] acls.xml firewall.xml
"""

__version__ = '1.2'
__author__ = 'Jean Gillaux'
__copyright__ = 'Copyright 2005, INL'

POLICY_VERSION = u"2.0"

import xml.dom.minidom
from nupyf.nuelt import EltGrpList, EltGrp
from nupyf.nuacl import Acl
from nupyf.nuacl_func import xml_new_eltgrp, parse_groups
from nupyf import nuldap
from nupyf.nuldap import LDAPConn, LDAPfw
from nupyf.fw import parse as parseFW
from nupyf.nubackend import NURule
from nupyf.nuxml import parse_periodicities, parse_durations, parse_descsort
from nupyf.nunat import nats_from_xml
from nupyf.ipt import FWipt
from nupyf import ipt
from os import linesep
from optparse import OptionParser
from getpass import getpass
from ldap import LDAPError, NO_SUCH_OBJECT
import cPickle
import IPy
from sys import stderr, exit, stdout, argv
from datetime import datetime

# Do not check networks netmasks
IPy.check_addr_prefixlen = False

def check_ldap(options):
    if not options.ldap_server and not options.ldap_basedn:
        # LDAP is not used: exit
        return
    if not (options.ldap_server and options.ldap_basedn):
        print >>stderr, 'Error: --server and --basedn options must be provided together'
        exit(1)

def parse_config_file(config_file, options):
    """Parse config_file and put directives into options.
    """
    try:
        config = eval(open(config_file).read())
    except IOError, ioe:
        print ioe
        exit(1)
    for key, value in config.iteritems():
        if key == 'ldap_password': options.ldap_pwd = value
        if key == 'ldap_user': options.ldap_user = value
        if key == 'ldap_server': options.ldap_server = value
        if key == 'ldap_basedn': options.ldap_basedn = value

def try_write_file(filename, *args):
    """write args in file with name = filename
- value for filename make function print to stdout
    """
    if filename == '-':
        fd = stdout
    else:
        fd = open(filename, 'w')
    for arg in args:
        if type(arg) == list:
            fd.writelines(arg)
        if type(arg) in [str, unicode]:
            fd.write(arg)

def parseOptions():
    usage = "usage: %prog [options] firewall_file.xml acls_file.xml"
    parser = OptionParser(usage, version = '%prog '+ __version__)
    # command line options
    parser.add_option('-d', '--dispatch', dest='dispatch',
        help='file to write dispatch commands in',
        metavar='FILE', default='')
    parser.add_option('-e', '--default_estrel_invalid', dest='default_estrel_invalid',
        help='file to write default_estrel_invalid commands in', metavar='FILE', default='')
    parser.add_option('-t', '--dispatch_targets', dest='dispatch_targets',
        help='file to write dispatch targets commands in', metavar='FILE', default='')
    parser.add_option('-f', '--forward', dest='forward',
        help='file to write forward iptables rules in', metavar='FILE')
    parser.add_option('-i', '--input', dest='input',
        help='file to ouput input rules in', metavar='FILE', default='')
    parser.add_option('-o', '--output', dest='output',
        help='file to write output rules in', metavar='FILE', default='')
    parser.add_option('-m', '--mangle', dest='mangle',
        help='file to write mangle rules in', metavar='FILE', default='')
    parser.add_option('-v', '--vpn', dest='vpn_rules',
        help='file to output vpn rules for packet marks, special value - means stdout',
        metavar='FILE', default='')
    parser.add_option('-n', '--nat', dest='nat_rules', metavar='FILE',
        help='file to output nat rules in, special value - means stdout')
    parser.add_option('-r', '--rescue', dest='rescue', action='store_true',
        help='activate rescue mode for firewall rules generation')
    parser.add_option('', '--ulog', dest='ulog', action="store_true",
        help='use ULOG system in log commands, default is to use LOG.')
    parser.add_option('', '--iptables', dest='ipt',
        help='path to iptables, can be a variable.', metavar='VALUE', default='')
    parser.add_option('-s', '--server', dest='ldap_server',
        help='ldap server address.', metavar='ADDR', default='')
    parser.add_option('-u', '--user', dest='ldap_user',
        help='ldap user used to connect.', metavar='USER', default='')
    parser.add_option('-p', '--pwd', dest='ldap_pwd',
        help='password used to connect on ldap server (WARNING: visible on comand line).',
        metavar='PWD', default='')
    parser.add_option('-b', '--basedn', dest='ldap_basedn',
        help='basedn used to insert acls in (ex: ou=acls, dc=domain, dc=com).',
        metavar='DN', default='')
    parser.add_option('-a', '--askpwd', dest='ask_ldappwd',
        help='ask user for ldap passwd.', action='store_true', default=False)
    parser.add_option('-c', '--config', dest='config_file',
        help='configuration file', default='')
    parser.add_option('', '--dumpldap', dest='dump_ldap',
        help='file to save ldap informations', default='')
    parser.add_option('', '--loadldap', dest='load_ldap',
        help='file to load ldap informations from', default=None)
    parser.add_option('', '--auth_ext', dest='auth_ext', action='store_true',
        help='Generate netfilter rules to autenticate internet')
    parser.add_option('', '--sortid', dest='sortid',
        help='Use this ID for acl order')
    parser.add_option('', '--same-iface', dest='same_iface', action='store_true',
        help='Generate Netfilter rules with same input and output interfaces', default=False)
    parser.add_option('', '--no-check-net', dest='no_check_net', action='store_true',
        help='Do not check inclusion and overlapping on enterprise networks', default=False)
    parser.add_option('', '--ipv6', action='store_true',
        help='Enable mode IPv6 (for NuFW 2.2)', default=False)
    parser.add_option('', '--nulayer7', dest='enable_nulayer7', action='store_true',
        help='Enable nulayer7 feature (generate rules which set connmarks)', default=False)
    parser.add_option('', '--short-description', dest='disable_logprefix', action='store_true',
        help='Only save ACL id in the description field of ACL objects in LDAP.', default=False)
    parser.add_option('--transparent-proxy-port', type='str',
        help='Port number of the transparent proxy (eg. 3128 for default Squid port)', default=None)
    parser.add_option('--debug', help='Enable debug mode',
        action='store_true', default=False)
    parser.add_option('--nufw', help='Enable authenticated rules',
        action='store_true', default=False)

    parser.set_defaults(rescue=False, forward='', nat_rules='', auth_ext=False)
    parser.set_defaults(sortid=0)
    return parser.parse_args(argv)

class Nupyf:
    def __init__(self):
        self.acls = []
        self.resources = EltGrpList()
        self.protocols = EltGrpList()
        self.applications = EltGrpList()
        self.operating_systems = EltGrpList()
        self.authentications = EltGrpList()

        (self.options, args) = parseOptions()
        if self.options.config_file:
            parse_config_file(self.options.config_file, self.options)
        if self.options.ipv6:
            nuldap.USE_IPV6 = True
        if self.options.ask_ldappwd:
            self.options.ldap_pwd = getpass('LDAP Password: ')
        check_ldap(self.options)
        if self.options.load_ldap:
            self.mainLDAP()
        if self.options.transparent_proxy_port:
            ipt.TRANSPARENT_PROXY_PORT = self.options.transparent_proxy_port

        NURule.disable_logprefix = self.options.disable_logprefix

        self.desc_filename = args[1]
        self.acl_filename = args[2]
        self.doc = xml.dom.minidom.parse(self.acl_filename)

    def run(self):
        self.parseAll()
        self.process()
        self.createIptablesCommands()
        if self.options.ldap_server and self.options.ldap_basedn:
            self.ldapStuff()

    def parseAll(self):
        root = self.doc.documentElement
        version = root.getAttribute('version')
        if version != POLICY_VERSION:
            raise RuntimeError('Document version is not "%s": "%s"' % (POLICY_VERSION, version))

        # Parse network description file, and take first firewall
        fwlist = parseFW(self.desc_filename)
        try:
            (fwid, self.myfw) = fwlist.popitem()
        except KeyError:
            print "unable to find a firewall"
            exit(1)

        # Parse groups
        self.groups_list = parse_groups(self.doc)
        if 1 not in self.groups_list:
            # group 1 is enabled by default
            self.groups_list[1] = 1

        # Parse NAT
        self.lsnat, self.ldnat, self.lpnat = nats_from_xml(self.doc, self.groups_list)

        # Parse resources
        for node in self.doc.getElementsByTagName("resource"):
            grp = xml_new_eltgrp(int(node.getAttribute('ID')),
                node.getAttribute('name'),
                node.getElementsByTagName("elt"))
            self.resources.add(grp)

        # Parse authentications objects
        if not self.options.rescue:
            for node in self.doc.getElementsByTagName("auth"):
                grp = xml_new_eltgrp(int(node.getAttribute('ID')),
                    node.getAttribute('name'),
                    node.getElementsByTagName("elt"))
                self.authentications.add(grp)

        # Layer7 support
        l7connmark = {}
        if self.options.enable_nulayer7:
            from nulayer7 import l7xml
            l7rulelist = l7xml.load(open(self.acl_filename))
            l7connmark['mask'] = l7rulelist.mask
            for l7rule in l7rulelist:
                l7connmark[str(l7rule.ID)] = l7rule.connmark

        # Parse protocols
        for node in self.doc.getElementsByTagName("protocol"):
            grp = xml_new_eltgrp(int(node.getAttribute('ID')),
                node.getAttribute('name'),
                node.getElementsByTagName("elt"),
                l7connmark=l7connmark)
            self.protocols.add(grp)

        # Parse applications
        for node in self.doc.getElementsByTagName("application"):
            grp = xml_new_eltgrp(int(node.getAttribute('ID')),
                node.getAttribute('name'),
                node.getElementsByTagName("elt"))
            self.applications.add(grp)

        # Parse operating systems
        for node in self.doc.getElementsByTagName("os"):
            grp = xml_new_eltgrp(
                int(node.getAttribute('ID')),
                node.getAttribute('name'),
                node.getElementsByTagName("elt"))
            self.operating_systems.add(grp)

        # Parse periodicities
        self.periodicities = parse_periodicities(self.doc)
        self.durations = parse_durations(self.doc)

    def processAcl(self, acl, acl_type):
        # Check that ACL group is enabled
        try:
            acl_group = int(acl.getAttribute("group"))
            if acl_group \
            and (acl_group not in self.groups_list or self.groups_list[acl_group]!=1):
                # Group disabled: exit
                return
        except ValueError:
            pass
        acl_id = int(acl.getAttribute('ID'))
        if not self.options.rescue:
            auth_id = acl.getAttribute("auth")
        else:
            auth_id = None
        log = acl.getAttribute("log")
        if not log:
            log = 'no'
        src = acl.getAttribute("from")
        dst = acl.getAttribute("to")
        idappli = acl.getAttribute("with")
        idos = acl.getAttribute("on")
        proto = acl.getAttribute("proto")
        if proto:
            proto = self.protocols.find(int(proto))
        else:
            proto = None
        transparent_proxy = acl.getAttribute("transparent_proxy")
        if acl_type == 'FORWARD' and transparent_proxy:
            transparent_proxy = int(transparent_proxy)
        else:
            transparent_proxy = None

        if idos:
            os = self.operating_systems.find(int(idos))
        else:
            os = None

        if idappli:
            appli = self.applications.find(int(idappli))
        else:
            appli = None

        idperiod = acl.getAttribute("periodicity")
        if idperiod:
            period = self.periodicities.find(int(idperiod)).name
        else:
            idduration = acl.getAttribute("duration")
            if idduration:
                period = self.durations.find(int(idduration)).name
            else:
                period = ''

        #get acl only if its group is enabled
        if src:
            subject = self.resources.find(int(src))
        else:
            subject = None
        if dst:
            resource = self.resources.find(int(dst))
        else:
            resource = None
        if self.options.nufw and auth_id:
            auth = self.authentications.find(int(auth_id))
        else:
            auth = None

        if acl_type == 'FORWARD':
            if not subject:
                raise ValueError("Unable to find subject of ACL #%s" % acl_id)
            if not resource:
                raise ValueError("Unable to find resource of ACL #%s" % acl_id)
            # FIXME: Support descsort
            descsort = parse_descsort(acl)
            order = descsort[self.options.sortid]
        else:
            order = None
            if not subject:
                subject = EltGrp(-1, 'all', 'or')
            if not resource:
                resource = EltGrp(-1, 'all', 'or')
        aclobj = Acl(subject, resource, self.myfw,
            id=acl_id,
            proto=proto,
            name=acl.getAttribute("name"),
            decision=acl.getAttribute('decision'),
            ulog_prefix=acl.getAttribute('prefix'),
            appli=appli,
            os=os,
            period_name=period,
            sort_order=order,
            auth=auth,
            log=log,
            acl_type=acl_type,
            transparent_proxy=transparent_proxy)
        self.acls.append(aclobj)

    def process(self):
        self.resources.normall()
        self.protocols.normall()
        self.applications.normall()
        self.operating_systems.normall()
        self.periodicities.normall()
        self.durations.normall()
        self.authentications.normall()

        # do we have to authenticate packets from internet?
        self.myfw.auth_ext = self.options.auth_ext

        # don't generate rules for same output and input intefaces ?
        if self.myfw.nb_ifaces() == 1:
            self.options.same_iface = True
        self.myfw.same_iface = self.options.same_iface

        # don't check inclusion and overlaping on enterprise networks ?
        self.myfw.no_check_net = self.options.no_check_net

        # to check descsort unicity by (descsort.ID, elt.from, elt.to, elt.order)
        for acl in self.doc.getElementsByTagName("acl"):
            self.processAcl(acl, 'FORWARD')
        for acl in self.doc.getElementsByTagName("localin"):
            self.processAcl(acl, 'INPUT')
        for acl in self.doc.getElementsByTagName("localout"):
            self.processAcl(acl, 'OUTPUT')

        #net_ent = self.myfw.entreprise_nets()
        self.myfw.chains(all = 1, inout = 1)

        for x in self.acls:
            for myress in x.rules():
                self.myfw.add_rule(myress)
        self.myfw.set_nat_rules(self.lsnat, self.ldnat, self.lpnat)

    def createIptablesCommands(self):
        if self.options.ulog:
            log_type = 'ulog'
        else:
            log_type = 'log'
        fwp = FWipt(self.myfw, logtype = log_type)
        rules_create = fwp.create_ipt_chains()
        rules_connect = fwp.connect_chains()

        default_estrel_invalid = rules_connect.pop(0) + rules_connect.pop(0)
        dispatch_rules = rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0) + rules_connect.pop(0)
        
        input_rules, output_rules, srules = fwp.gen_rules(rescue = self.options.rescue)
        intro = "#Generated by nupyf on %s from %s\n\n" % (
            datetime.now(), self.acl_filename)
        if self.options.dispatch:
            try_write_file(self.options.dispatch, intro, '#DISPATCH Rules%s'%(linesep), dispatch_rules)
        if self.options.default_estrel_invalid:
            try_write_file(self.options.default_estrel_invalid, intro, '#DEFAULT Established and Related Rules%s'%(linesep), default_estrel_invalid)
        if self.options.dispatch_targets:
            try_write_file(self.options.dispatch_targets, intro, '#DISPATCH Target Rules%s'%(linesep), rules_create)
        if self.options.forward:
            try_write_file(self.options.forward, intro, '#Rules for FORWARD%s'%linesep, srules)
        if self.options.input:
            try_write_file(self.options.input, intro, '#Rules for INPUT%s'%linesep, input_rules)
        if self.options.output:
            try_write_file(self.options.output, intro, '#Rules for OUTPUT%s'%linesep, output_rules)
        if self.options.vpn_rules:
            try_write_file(self.options.vpn_rules, intro, '#VPN Rules%s'%linesep, fwp.gen_vpn_rules())
        if self.options.nat_rules:
            try_write_file(self.options.nat_rules, intro, '#NAT Rules%s'%linesep, fwp.gen_nat_rules())
        if self.options.mangle:
            try_write_file(self.options.mangle, intro, '#MANGLE Rules%s'%linesep, fwp.mangle_rules())

    def ldapStuff(self):
        ldapfw = LDAPfw(self.myfw)
        if self.options.dump_ldap:
            cPickle.dump(ldapfw, open(self.options.dump_ldap, 'w+'))
        else:
            conn = LDAPConn(self.options.ldap_server, self.options.ldap_user, self.options.ldap_pwd, self.options.ldap_basedn)
            ldapfw.process(conn)

    def mainLDAP(self):
        filename = self.options.load_ldap
        server = self.options.ldap_server
        user = self.options.ldap_user
        password = self.options.ldap_pwd
        basedn = self.options.ldap_basedn
        try:
            dapfw = cPickle.load(open(filename))
            ldapconn = LDAPConn(server, user, password, basedn)
            dapfw.process(ldapconn)
            exit(0)
        except NO_SUCH_OBJECT, err:
            print "LDAP error: no such object!"
            print "Check that the top domain does exist."
        except LDAPError, err:
            print "LDAP error: %s" % err
            if self.options.debug:
                raise
        print "(user: %s, server: %s, basedn: %s)" % (user, server, basedn)
        print
        print "Please check LDAP parameters of your nupyf configuration"
        exit(1)


def main():
    #use psyco if available
    try:
        import psyco
        psyco.full()
    except ImportError:
        pass

    # Call main code
    nupyf = Nupyf()
    if not nupyf.options.debug:
        try:
            nupyf.run()
            exit(0)
        except LDAPError, ldaperr:
            print >>stderr, 'LDAP error: %s' % ldaperr
        except Exception, err:
            if err.__class__ is SystemExit:
                # exit(0) is not an error!
                raise
            print >>stderr, 'Error: [%s] %s' % (err.__class__.__name__, err)
        exit(1)
    else:
        nupyf.run()
        exit(0)

if __name__ == "__main__":
    main()