Esempio n. 1
0
 def __init__(self, module):
     self.module = module
     self.pfsense = PFSenseModule(module)
     self.pfsense_aliases = PFSenseAliasModule(module, self.pfsense)
     self.pfsense_interfaces = PFSenseInterfaceModule(module, self.pfsense)
     self.pfsense_rules = PFSenseRuleModule(module, self.pfsense)
     self.pfsense_rule_separators = PFSenseRuleSeparatorModule(module, self.pfsense)
     self.pfsense_vlans = PFSenseVlanModule(module, self.pfsense)
Esempio n. 2
0
 def __init__(self, module):
     self.module = module
     self.pfsense = PFSenseModule(module)
     self.pfsense_aliases = PFSenseAliasModule(module, self.pfsense)
     self.pfsense_interfaces = PFSenseInterfaceModule(module, self.pfsense)
     self.pfsense_nat_outbounds = PFSenseNatOutboundModule(
         module, self.pfsense)
     self.pfsense_nat_port_forwards = PFSenseNatPortForwardModule(
         module, self.pfsense)
     self.pfsense_rules = PFSenseRuleModule(module, self.pfsense)
     self.pfsense_rule_separators = PFSenseRuleSeparatorModule(
         module, self.pfsense)
     self.pfsense_vlans = PFSenseVlanModule(module, self.pfsense)
Esempio n. 3
0
    def _delete_associated_rule(self, ruleid, interface=None):
        if ruleid is None or ruleid == '' or ruleid == 'pass':
            return

        if interface is None:
            interface = self.params['interface']
        self.pfsense_rule_module = PFSenseRuleModule(self.module, self.pfsense)
        params = dict()
        params['name'] = 'NAT ' + self.params['descr']
        params['interface'] = interface
        params['state'] = 'absent'
        params['associated-rule-id'] = ruleid
        self.pfsense_rule_module.run(params)
        self.result['commands'] += self.pfsense_rule_module.result['commands']
Esempio n. 4
0
    def _remove_all_rules(self, interface):
        """ delete all interface rules """

        # we use the pfsense_rule module to delete the rules since, at least for floating rules,
        # it implies to recalculate separators positions
        # if we have to just remove the deleted interface of a floating rule we do it ourselves
        todel = []
        for rule_elt in self.pfsense.rules:
            if rule_elt.find('floating') is not None:
                interfaces = rule_elt.find('interface').text.split(',')
                old_ifs = ','.join([
                    self.pfsense.get_interface_display_name(old_interface)
                    for old_interface in interfaces
                ])
                if interface in interfaces:
                    if len(interfaces) > 1:
                        interfaces.remove(interface)
                        new_ifs = ','.join([
                            self.pfsense.get_interface_display_name(
                                new_interface) for new_interface in interfaces
                        ])
                        rule_elt.find('interface').text = ','.join(interfaces)
                        cmd = 'update rule \'{0}\' on \'floating({1})\' set interface=\'{2}\''.format(
                            rule_elt.find('descr').text, old_ifs, new_ifs)
                        self.result['commands'].append(cmd)
                        continue
                    todel.append(rule_elt)
                else:
                    continue
            else:
                iface = rule_elt.find('interface')
                if iface is not None and iface.text == interface:
                    todel.append(rule_elt)

        if todel:
            pfsense_rules = PFSenseRuleModule(self.module, self.pfsense)
            for rule_elt in todel:
                params = {}
                params['state'] = 'absent'
                params['name'] = rule_elt.find('descr').text
                params['interface'] = rule_elt.find('interface').text
                if rule_elt.find('floating') is not None:
                    params['floating'] = True
                pfsense_rules.run(params)
            if pfsense_rules.result['commands']:
                self.result['commands'].extend(
                    pfsense_rules.result['commands'])
 def _create_associated_rule(self):
     if self.pfsense_rule_module is None:
         self.pfsense_rule_module = PFSenseRuleModule(
             self.module, self.pfsense)
     params = dict()
     params['name'] = 'NAT ' + self.params['descr']
     params['state'] = 'present'
     params['action'] = 'pass'
     params['ipprotocol'] = 'inet'
     params['statetype'] = 'keep state'
     params['interface'] = self.params['interface']
     params['source'] = self.params['source']
     params['destination'] = self.params['target']
     params['disabled'] = self.params['disabled']
     params['protocol'] = self.params['protocol']
     if self.params['associated_rule'] == 'associated':
         params['associated-rule-id'] = self.pfsense.uniqid('nat_', True)
         self.obj['associated-rule-id'] = params['associated-rule-id']
     self.result['commands'] = list()
     self.pfsense_rule_module.run(params)
     self.result['commands'] += self.pfsense_rule_module.result['commands']
Esempio n. 6
0
def main():
    module = AnsibleModule(argument_spec=RULE_ARGUMENT_SPEC,
                           required_if=RULE_REQUIRED_IF,
                           supports_check_mode=True)

    pfmodule = PFSenseRuleModule(module)
    pfmodule.run(module.params)
    pfmodule.commit_changes()
Esempio n. 7
0
class PFSenseModuleAggregate(object):
    """ module managing pfsense aggregated aliases, rules, rule separators, interfaces and VLANs """
    def __init__(self, module):
        self.module = module
        self.pfsense = PFSenseModule(module)
        self.pfsense_aliases = PFSenseAliasModule(module, self.pfsense)
        self.pfsense_interfaces = PFSenseInterfaceModule(module, self.pfsense)
        self.pfsense_nat_outbounds = PFSenseNatOutboundModule(
            module, self.pfsense)
        self.pfsense_nat_port_forwards = PFSenseNatPortForwardModule(
            module, self.pfsense)
        self.pfsense_rules = PFSenseRuleModule(module, self.pfsense)
        self.pfsense_rule_separators = PFSenseRuleSeparatorModule(
            module, self.pfsense)
        self.pfsense_vlans = PFSenseVlanModule(module, self.pfsense)

    def _update(self):
        run = False
        cmd = 'require_once("filter.inc");\n'
        # TODO: manage one global list of commands as ordering can be important between modules
        if self.pfsense_vlans.result['changed']:
            run = True
            cmd += self.pfsense_vlans.get_update_cmds()

        if self.pfsense_interfaces.result['changed']:
            run = True
            cmd += self.pfsense_interfaces.get_update_cmds()

        cmd += 'if (filter_configure() == 0) { \n'
        if self.pfsense_aliases.result['changed']:
            run = True
            cmd += 'clear_subsystem_dirty(\'aliases\');\n'

        if self.pfsense_nat_port_forwards.result[
                'changed'] or self.pfsense_nat_outbounds.result['changed']:
            run = True
            cmd += 'clear_subsystem_dirty(\'natconf\');\n'

        if (self.pfsense_rules.result['changed']
                or self.pfsense_rule_separators.result['changed']
                or self.pfsense_nat_port_forwards.result['changed']
                or self.pfsense_nat_outbounds.result['changed']):
            run = True
            cmd += 'clear_subsystem_dirty(\'filter\');\n'
        cmd += '}'
        if run:
            return self.pfsense.phpshell(cmd)

        return ('', '', '')

    def _parse_floating_interfaces(self, interfaces):
        """ parse interfaces """
        res = set()
        for interface in interfaces.split(','):
            res.add(self.pfsense.parse_interface(interface))
        return res

    def want_rule(self, rule_elt, rules, name_field='name'):
        """ return True if we want to keep rule_elt """
        descr = rule_elt.find('descr')
        interface = rule_elt.find('interface')
        floating = rule_elt.find('floating') is not None

        # probably not a rule
        if descr is None or interface is None:
            return True

        for rule in rules:
            if rule['state'] == 'absent':
                continue
            if rule[name_field] != descr.text:
                continue

            rule_floating = (
                rule.get('floating') is not None
                and (isinstance(rule['floating'], bool) and rule['floating']
                     or rule['floating'].lower() in ['yes', 'true']))
            if floating != rule_floating:
                continue

            if floating or self.pfsense.parse_interface(
                    rule['interface']) == interface.text:
                return True
        return False

    def want_rule_separator(self, separator_elt, rule_separators):
        """ return True if we want to keep separator_elt """
        name = separator_elt.find('text').text
        interface = separator_elt.find('if').text

        for separator in rule_separators:
            if separator['state'] == 'absent':
                continue
            if separator['name'] != name:
                continue
            if separator.get('floating'):
                if interface == 'floatingrules':
                    return True
            elif self.pfsense.parse_interface(
                    separator['interface']) == interface:
                return True
        return False

    @staticmethod
    def want_alias(alias_elt, aliases):
        """ return True if we want to keep alias_elt """
        name = alias_elt.find('name')
        alias_type = alias_elt.find('type')

        # probably not an alias
        if name is None or type is None:
            return True

        for alias in aliases:
            if alias['state'] == 'absent':
                continue
            if alias['name'] == name.text and alias['type'] == alias_type.text:
                return True
        return False

    @staticmethod
    def want_interface(interface_elt, interfaces):
        """ return True if we want to keep interface_elt """
        descr_elt = interface_elt.find('descr')
        if descr_elt is not None and descr_elt.text:
            name = descr_elt.text
        else:
            name = interface_elt.tag

        for interface in interfaces:
            if interface['state'] == 'absent':
                continue
            if interface['descr'] == name:
                return True
        return False

    @staticmethod
    def want_vlan(vlan_elt, vlans):
        """ return True if we want to keep vlan_elt """
        tag = int(vlan_elt.find('tag').text)
        interface = vlan_elt.find('if')

        for vlan in vlans:
            if vlan['state'] == 'absent':
                continue
            if vlan['vlan_id'] == tag and vlan['interface'] == interface.text:
                return True
        return False

    @staticmethod
    def is_filtered(interface_filter, params):
        if interface_filter is None:
            return False

        if 'floating' in params:
            if isinstance(params['floating'], str):
                floating = params['floating'].lower()
            else:
                floating = 'true' if params['floating'] else 'false'

            if floating != 'false' and floating != 'no':
                return 'floating' not in interface_filter

        return params['interface'].lower() not in interface_filter

    def run_rules(self):
        """ process input params to add/update/delete all rules """

        want = self.module.params['aggregated_rules']
        interface_filter = self.module.params['interface_filter'].lower(
        ).split(' ') if self.module.params.get(
            'interface_filter') is not None else None

        if want is None:
            return

        # delete every other rule if required
        if self.module.params['purge_rules']:
            todel = []
            for rule_elt in self.pfsense_rules.root_elt:
                if not self.want_rule(rule_elt, want):
                    params = {}
                    params['state'] = 'absent'
                    params['name'] = rule_elt.find('descr').text

                    if rule_elt.find('floating') is not None:
                        params['floating'] = True
                        interfaces = rule_elt.find('interface').text.split(',')
                        params['interface'] = list()
                        for interface in interfaces:
                            target = self.pfsense.get_interface_display_name(
                                interface, return_none=True)
                            if target is not None:
                                params['interface'].append(target)
                            else:
                                params['interface'].append(interface)
                        params['interface'] = ','.join(params['interface'])
                    else:
                        params[
                            'interface'] = self.pfsense.get_interface_display_name(
                                rule_elt.find('interface').text,
                                return_none=True)

                    if params['interface'] is None:
                        continue

                    todel.append(params)

            for params in todel:
                if self.is_filtered(interface_filter, params):
                    continue
                self.pfsense_rules.run(params)

        # generating order if required
        if self.module.params.get('order_rules'):
            last_rules = dict()
            for params in want:
                if params.get('before') is not None or params.get(
                        'after') is not None:
                    self.module.fail_json(
                        msg=
                        "You can't use after or before parameters on rules when using order_rules (see {0})"
                        .format(params['name']))

                if params.get('state') == 'absent':
                    continue

                if params.get('floating'):
                    key = 'floating'
                else:
                    key = params['interface']

                # first rule on interface
                if key not in last_rules:
                    params['after'] = 'top'
                    last_rules[key] = params['name']
                    continue

                params['after'] = last_rules[key]
                last_rules[key] = params['name']

        # processing aggregated parameters
        for params in want:
            if self.is_filtered(interface_filter, params):
                continue
            self.pfsense_rules.run(params)

    def run_nat_outbounds_rules(self):
        """ process input params to add/update/delete all nat_outbound rules """

        want = self.module.params['aggregated_nat_outbounds']
        interface_filter = self.module.params['interface_filter'].lower(
        ).split(' ') if self.module.params.get(
            'interface_filter') is not None else None

        if want is None:
            return

        # delete every other rule if required
        if self.module.params['purge_nat_outbounds']:
            todel = []
            for rule_elt in self.pfsense_nat_outbounds.root_elt:
                if not self.want_rule(rule_elt, want, name_field='descr'):
                    params = {}
                    params['state'] = 'absent'
                    params['descr'] = rule_elt.find('descr').text
                    params[
                        'interface'] = self.pfsense.get_interface_display_name(
                            rule_elt.find('interface').text, return_none=True)

                    if params['interface'] is None:
                        continue

                    todel.append(params)

            for params in todel:
                if self.is_filtered(interface_filter, params):
                    continue
                self.pfsense_nat_outbounds.run(params)

        # processing aggregated parameters
        for params in want:
            if self.is_filtered(interface_filter, params):
                continue
            self.pfsense_nat_outbounds.run(params)

    def run_nat_port_forwards_rules(self):
        """ process input params to add/update/delete all nat_port_forwards_rule rules """

        want = self.module.params['aggregated_nat_port_forwards']
        interface_filter = self.module.params['interface_filter'].lower(
        ).split(' ') if self.module.params.get(
            'interface_filter') is not None else None

        if want is None:
            return

        # delete every other rule if required
        if self.module.params['purge_nat_port_forwards']:
            todel = []
            for rule_elt in self.pfsense_nat_port_forwards.root_elt:
                if not self.want_rule(rule_elt, want, name_field='descr'):
                    params = {}
                    params['state'] = 'absent'
                    params['descr'] = rule_elt.find('descr').text
                    params[
                        'interface'] = self.pfsense.get_interface_display_name(
                            rule_elt.find('interface').text, return_none=True)

                    if params['interface'] is None:
                        continue

                    todel.append(params)

            for params in todel:
                if self.is_filtered(interface_filter, params):
                    continue
                self.pfsense_nat_port_forwards.run(params)

        # processing aggregated parameters
        for params in want:
            if self.is_filtered(interface_filter, params):
                continue
            self.pfsense_nat_port_forwards.run(params)

    def run_aliases(self):
        """ process input params to add/update/delete all aliases """
        want = self.module.params['aggregated_aliases']

        if want is None:
            return

        # processing aggregated parameter
        for param in want:
            self.pfsense_aliases.run(param)

        # delete every other alias if required
        if self.module.params['purge_aliases']:
            todel = []
            for alias_elt in self.pfsense_aliases.root_elt:
                if not self.want_alias(alias_elt, want):
                    params = {}
                    params['state'] = 'absent'
                    params['name'] = alias_elt.find('name').text
                    todel.append(params)

            for params in todel:
                self.pfsense_aliases.run(params)

    def run_interfaces(self):
        """ process input params to add/update/delete all interfaces """
        want = self.module.params['aggregated_interfaces']

        if want is None:
            return

        # processing aggregated parameter
        for param in want:
            self.pfsense_interfaces.run(param)

        # delete every other if required
        if self.module.params['purge_interfaces']:
            todel = []
            for interface_elt in self.pfsense_interfaces.root_elt:
                if not self.want_interface(interface_elt, want):
                    params = {}
                    params['state'] = 'absent'
                    descr_elt = interface_elt.find('descr')
                    if descr_elt is not None and descr_elt.text:
                        params['descr'] = descr_elt.text
                        todel.append(params)

            for params in todel:
                self.pfsense_interfaces.run(params)

    def run_rule_separators(self):
        """ process input params to add/update/delete all separators """
        want = self.module.params['aggregated_rule_separators']
        interface_filter = self.module.params['interface_filter'].lower(
        ).split(' ') if self.module.params.get(
            'interface_filter') is not None else None

        if want is None:
            return

        # processing aggregated parameter
        for params in want:
            if self.is_filtered(interface_filter, params):
                continue
            self.pfsense_rule_separators.run(params)

        # delete every other if required
        if self.module.params['purge_rule_separators']:
            todel = []
            for interface_elt in self.pfsense_rule_separators.separators:
                for separator_elt in interface_elt:
                    if not self.want_rule_separator(separator_elt, want):
                        params = {}
                        params['state'] = 'absent'
                        params['name'] = separator_elt.find('text').text
                        if interface_elt.tag == 'floatingrules':
                            params['floating'] = True
                        else:
                            params[
                                'interface'] = self.pfsense.get_interface_display_name(
                                    interface_elt.tag, return_none=True)
                            if params['interface'] is None:
                                continue
                        todel.append(params)

            for params in todel:
                if self.is_filtered(interface_filter, params):
                    continue
                self.pfsense_rule_separators.run(params)

    def run_vlans(self):
        """ process input params to add/update/delete all VLANs """
        want = self.module.params['aggregated_vlans']

        if want is None:
            return

        # processing aggregated parameter
        for param in want:
            self.pfsense_vlans.run(param)

        # delete every other if required
        if self.module.params['purge_vlans']:
            todel = []
            for vlan_elt in self.pfsense_vlans.root_elt:
                if not self.want_vlan(vlan_elt, want):
                    params = {}
                    params['state'] = 'absent'
                    params['interface'] = vlan_elt.find('if').text
                    params['vlan_id'] = int(vlan_elt.find('tag').text)
                    todel.append(params)

            for params in todel:
                self.pfsense_vlans.run(params)

    def commit_changes(self):
        """ apply changes and exit module """
        stdout = ''
        stderr = ''
        changed = (self.pfsense_aliases.result['changed']
                   or self.pfsense_interfaces.result['changed']
                   or self.pfsense_nat_outbounds.result['changed']
                   or self.pfsense_nat_port_forwards.result['changed']
                   or self.pfsense_rules.result['changed']
                   or self.pfsense_rule_separators.result['changed']
                   or self.pfsense_vlans.result['changed'])

        if changed and not self.module.check_mode:
            self.pfsense.write_config(descr='aggregated change')
            (dummy, stdout, stderr) = self._update()

        result = {}
        result['result_aliases'] = self.pfsense_aliases.result['commands']
        result['result_interfaces'] = self.pfsense_interfaces.result[
            'commands']
        result['result_nat_outbounds'] = self.pfsense_nat_outbounds.result[
            'commands']
        result[
            'result_nat_port_forwards'] = self.pfsense_nat_port_forwards.result[
                'commands']
        result['result_rules'] = self.pfsense_rules.result['commands']
        result['result_rule_separators'] = self.pfsense_rule_separators.result[
            'commands']
        result['result_vlans'] = self.pfsense_vlans.result['commands']
        result['changed'] = changed
        result['stdout'] = stdout
        result['stderr'] = stderr
        self.module.exit_json(**result)
Esempio n. 8
0
class PFSenseModuleAggregate(object):
    """ module managing pfsense aggregated aliases, rules, rule separators, interfaces and vlans """

    def __init__(self, module):
        self.module = module
        self.pfsense = PFSenseModule(module)
        self.pfsense_aliases = PFSenseAliasModule(module, self.pfsense)
        self.pfsense_interfaces = PFSenseInterfaceModule(module, self.pfsense)
        self.pfsense_rules = PFSenseRuleModule(module, self.pfsense)
        self.pfsense_rule_separators = PFSenseRuleSeparatorModule(module, self.pfsense)
        self.pfsense_vlans = PFSenseVlanModule(module, self.pfsense)

    def _update(self):
        run = False
        cmd = 'require_once("filter.inc");\n'
        if self.pfsense_vlans.result['changed']:
            run = True
            cmd += self.pfsense_vlans.get_update_cmds()

        if self.pfsense_interfaces.result['changed']:
            run = True
            cmd += self.pfsense_interfaces.get_update_cmds()

        cmd += 'if (filter_configure() == 0) { \n'
        if self.pfsense_aliases.result['changed']:
            run = True
            cmd += 'clear_subsystem_dirty(\'aliases\');\n'
        if self.pfsense_rules.result['changed'] or self.pfsense_rule_separators.result['changed']:
            run = True
            cmd += 'clear_subsystem_dirty(\'filter\');\n'
        cmd += '}'
        if run:
            return self.pfsense.phpshell(cmd)

        return ('', '', '')

    def want_rule(self, rule_elt, rules):
        """ return True if we want to keep rule_elt """
        descr = rule_elt.find('descr')
        interface = rule_elt.find('interface')

        # probably not a rule
        if descr is None or interface is None:
            return True

        for rule in rules:
            if rule['state'] == 'absent':
                continue
            if rule['name'] == descr.text and self.pfsense.parse_interface(rule['interface']) == interface.text:
                return True
        return False

    def want_rule_separator(self, separator_elt, rule_separators):
        """ return True if we want to keep separator_elt """
        name = separator_elt.find('text').text
        interface = separator_elt.find('if').text

        for separator in rule_separators:
            if separator['state'] == 'absent':
                continue
            if separator['name'] != name:
                continue
            if self.pfsense.parse_interface(separator['interface']) == interface or interface == 'floatingrules' and separator.get('floating'):
                return True
        return False

    @staticmethod
    def want_alias(alias_elt, aliases):
        """ return True if we want to keep alias_elt """
        name = alias_elt.find('name')
        alias_type = alias_elt.find('type')

        # probably not an alias
        if name is None or type is None:
            return True

        for alias in aliases:
            if alias['state'] == 'absent':
                continue
            if alias['name'] == name.text and alias['type'] == alias_type.text:
                return True
        return False

    def want_interface(self, interface_elt, interfaces):
        """ return True if we want to keep interface_elt """
        descr_elt = interface_elt.find('descr')
        if descr_elt is not None and descr_elt.text:
            name = descr_elt.text
        else:
            name = interface_elt.tag

        for interface in interfaces:
            if interface['state'] == 'absent':
                continue
            if interface['descr'] == name:
                return True
        return False

    @staticmethod
    def want_vlan(vlan_elt, vlans):
        """ return True if we want to keep vlan_elt """
        tag = int(vlan_elt.find('tag').text)
        interface = vlan_elt.find('if')

        for vlan in vlans:
            if vlan['state'] == 'absent':
                continue
            if vlan['vlan_id'] == tag and vlan['interface'] == interface.text:
                return True
        return False

    def run_rules(self):
        """ process input params to add/update/delete all rules """
        want = self.module.params['aggregated_rules']

        if want is None:
            return

        # delete every other rule if required
        if self.module.params['purge_rules']:
            todel = []
            for rule_elt in self.pfsense_rules.root_elt:
                if not self.want_rule(rule_elt, want):
                    params = {}
                    params['state'] = 'absent'
                    params['name'] = rule_elt.find('descr').text
                    params['interface'] = self.pfsense.get_interface_display_name(rule_elt.find('interface').text)
                    if rule_elt.find('floating') is not None:
                        params['floating'] = True
                    todel.append(params)

            for params in todel:
                self.pfsense_rules.run(params)

        # generating order if required
        if self.module.params.get('order_rules'):
            last_rules = dict()
            for params in want:
                if params.get('before') is not None or params.get('after') is not None:
                    self.module.fail_json(msg="You can't use after or before parameters on rules when using order_rules (see {0})".format(params['name']))

                if params.get('state') == 'absent':
                    continue

                if params.get('floating'):
                    key = 'floating'
                else:
                    key = params['interface']

                # first rule on interface
                if key not in last_rules:
                    params['after'] = 'top'
                    last_rules[key] = params['name']
                    continue

                params['after'] = last_rules[key]
                last_rules[key] = params['name']

        # processing aggregated parameters
        for params in want:
            self.pfsense_rules.run(params)

    def run_aliases(self):
        """ process input params to add/update/delete all aliases """
        want = self.module.params['aggregated_aliases']

        if want is None:
            return

        # processing aggregated parameter
        for param in want:
            self.pfsense_aliases.run(param)

        # delete every other alias if required
        if self.module.params['purge_aliases']:
            todel = []
            for alias_elt in self.pfsense_aliases.root_elt:
                if not self.want_alias(alias_elt, want):
                    params = {}
                    params['state'] = 'absent'
                    params['name'] = alias_elt.find('name').text
                    todel.append(params)

            for params in todel:
                self.pfsense_aliases.run(params)

    def run_interfaces(self):
        """ process input params to add/update/delete all interfaces """
        want = self.module.params['aggregated_interfaces']

        if want is None:
            return

        # processing aggregated parameter
        for param in want:
            self.pfsense_interfaces.run(param)

        # delete every other if required
        if self.module.params['purge_interfaces']:
            todel = []
            for interface_elt in self.pfsense_interfaces.root_elt:
                if not self.want_interface(interface_elt, want):
                    params = {}
                    params['state'] = 'absent'
                    descr_elt = interface_elt.find('descr')
                    if descr_elt is not None and descr_elt.text:
                        params['descr'] = descr_elt.text
                        todel.append(params)

            for params in todel:
                self.pfsense_interfaces.run(params)

    def run_rule_separators(self):
        """ process input params to add/update/delete all separators """
        want = self.module.params['aggregated_rule_separators']

        if want is None:
            return

        # processing aggregated parameter
        for param in want:
            self.pfsense_rule_separators.run(param)

        # delete every other if required
        if self.module.params['purge_rule_separators']:
            todel = []
            for interface_elt in self.pfsense_rule_separators.separators:
                for separator_elt in interface_elt:
                    if not self.want_rule_separator(separator_elt, want):
                        params = {}
                        params['state'] = 'absent'
                        params['name'] = separator_elt.find('text').text
                        if interface_elt.tag == 'floatingrules':
                            params['floating'] = True
                        else:
                            params['interface'] = self.pfsense.get_interface_display_name(interface_elt.tag)
                        todel.append(params)

            for params in todel:
                self.pfsense_rule_separators.run(params)

    def run_vlans(self):
        """ process input params to add/update/delete all vlans """
        want = self.module.params['aggregated_vlans']

        if want is None:
            return

        # processing aggregated parameter
        for param in want:
            self.pfsense_vlans.run(param)

        # delete every other if required
        if self.module.params['purge_vlans']:
            todel = []
            for vlan_elt in self.pfsense_vlans.root_elt:
                if not self.want_vlan(vlan_elt, want):
                    params = {}
                    params['state'] = 'absent'
                    params['interface'] = vlan_elt.find('if').text
                    params['vlan_id'] = int(vlan_elt.find('tag').text)
                    todel.append(params)

            for params in todel:
                self.pfsense_vlans.run(params)

    def commit_changes(self):
        """ apply changes and exit module """
        stdout = ''
        stderr = ''
        changed = (
            self.pfsense_aliases.result['changed'] or self.pfsense_interfaces.result['changed'] or self.pfsense_rules.result['changed']
            or self.pfsense_rule_separators.result['changed'] or self.pfsense_vlans.result['changed']
        )

        if changed and not self.module.check_mode:
            self.pfsense.write_config(descr='aggregated change')
            (dummy, stdout, stderr) = self._update()

        result = {}
        result['result_aliases'] = self.pfsense_aliases.result['commands']
        result['result_interfaces'] = self.pfsense_interfaces.result['commands']
        result['result_rules'] = self.pfsense_rules.result['commands']
        result['result_rule_separators'] = self.pfsense_rule_separators.result['commands']
        result['result_vlans'] = self.pfsense_vlans.result['commands']
        result['changed'] = changed
        result['stdout'] = stdout
        result['stderr'] = stderr
        self.module.exit_json(**result)
class PFSenseNatPortForwardModule(PFSenseModuleBase):
    """ module managing pfsense NAT rules """
    @staticmethod
    def get_argument_spec():
        """ return argument spec """
        return NAT_PORT_FORWARD_ARGUMENT_SPEC

    ##############################
    # init
    #
    def __init__(self, module, pfsense=None):
        super(PFSenseNatPortForwardModule, self).__init__(module, pfsense)
        self.name = "pfsense_nat_port_forward"
        self.obj = dict()

        self.after = None
        self.before = None
        self.position_changed = False

        self.root_elt = self.pfsense.get_element('nat')
        if self.root_elt is None:
            self.root_elt = self.pfsense.new_element('nat')
            self.pfsense.root.append(self.root_elt)

        self.pfsense_rule_module = None

    ##############################
    # params processing
    #
    def _params_to_obj(self):
        """ return a dict from module params """

        obj = dict()
        self.obj = obj
        obj['descr'] = self.params['descr']
        if self.params['state'] == 'present':
            obj['interface'] = self.pfsense.parse_interface(
                self.params['interface'])
            self._get_ansible_param(obj, 'protocol')
            self._get_ansible_param(obj, 'poolopts')
            self._get_ansible_param(obj, 'source_hash_key')
            self._get_ansible_param(obj, 'natport')

            self._get_ansible_param(obj, 'natreflection')
            if obj['natreflection'] == 'system-default':
                del obj['natreflection']

            if self.params['associated_rule'] == 'pass':
                obj['associated-rule-id'] = 'pass'
            elif self.params[
                    'associated_rule'] == 'unassociated' and self._find_target(
                    ) is not None:
                self.module.fail_json(
                    msg=
                    'You cannot set an unassociated filter rule if the NAT rule already exists.'
                )
            else:
                obj['associated-rule-id'] = ''

            self._get_ansible_param_bool(obj, 'disabled')
            self._get_ansible_param_bool(obj, 'nordr')
            self._get_ansible_param_bool(obj, 'nosync')

            if 'after' in self.params and self.params['after'] is not None:
                self.after = self.params['after']

            if 'before' in self.params and self.params['before'] is not None:
                self.before = self.params['before']

            obj['source'] = self.pfsense.parse_address(self.params['source'],
                                                       allow_self=False)
            obj['destination'] = self.pfsense.parse_address(
                self.params['destination'])
            self._parse_target_address(obj)

        return obj

    def _parse_target_address(self, obj):
        """ validate param address field and returns it as a dict """

        if self.params.get('target') is None or self.params['target'] == '':
            self.module.fail_json(
                msg='The field Redirect target IP is required.')

        param = self.params['target']
        addr = param.split(':')
        if len(addr) > 2:
            self.module.fail_json(msg='Cannot parse address %s' % (param))

        address = addr[0]

        ports = addr[1] if len(addr) > 1 else None
        if self.pfsense.find_alias(
                address,
                'host') is not None or self.pfsense.is_ipv4_address(address):
            obj['target'] = address
        else:
            self.module.fail_json(
                msg=
                '"%s" is not a valid redirect target IP address or host alias.'
                % (param))

        if ports is not None and self.pfsense.is_port_or_alias(ports):
            obj['local-port'] = ports
        else:
            self.module.fail_json(
                msg=
                '"{0}" is not a valid redirect target port. It must be a port alias or integer between 1 and 65535.'
                .format(ports))

    def _validate_params(self):
        """ do some extra checks on input parameters """

        if self.params.get('after') and self.params.get('before'):
            self.module.fail_json(msg='Cannot specify both after and before')
        elif self.params.get('after'):
            if self.params['after'] == self.params['descr']:
                self.module.fail_json(
                    msg='Cannot specify the current rule in after')
        elif self.params.get('before'):
            if self.params['before'] == self.params['descr']:
                self.module.fail_json(
                    msg='Cannot specify the current rule in before')

    ##############################
    # XML processing
    #
    def _copy_and_add_target(self):
        """ create the XML target_elt """
        self._set_associated_rule()
        self.pfsense.copy_dict_to_element(self.obj, self.target_elt)
        self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
        self._insert(self.target_elt)

    def _copy_and_update_target(self):
        """ update the XML target_elt """
        before = self.pfsense.element_to_dict(self.target_elt)
        self.diff['before'] = before
        changed = self._set_associated_rule(before)

        if self.pfsense.copy_dict_to_element(self.obj, self.target_elt):
            changed = True

        if self._remove_deleted_params():
            changed = True

        if self._update_rule_position(self.target_elt):
            changed = True

        self.diff['after'] = self.pfsense.element_to_dict(self.target_elt)
        return (before, changed)

    def _create_associated_rule(self):
        if self.pfsense_rule_module is None:
            self.pfsense_rule_module = PFSenseRuleModule(
                self.module, self.pfsense)
        params = dict()
        params['name'] = 'NAT ' + self.params['descr']
        params['state'] = 'present'
        params['action'] = 'pass'
        params['ipprotocol'] = 'inet'
        params['statetype'] = 'keep state'
        params['interface'] = self.params['interface']
        params['source'] = self.params['source']
        params['destination'] = self.params['target']
        params['disabled'] = self.params['disabled']
        params['protocol'] = self.params['protocol']
        if self.params['associated_rule'] == 'associated':
            params['associated-rule-id'] = self.pfsense.uniqid('nat_', True)
            self.obj['associated-rule-id'] = params['associated-rule-id']
        self.result['commands'] = list()
        self.pfsense_rule_module.run(params)
        self.result['commands'] += self.pfsense_rule_module.result['commands']

    def _create_target(self):
        """ create the XML target_elt """
        target_elt = self.pfsense.new_element('rule')
        return target_elt

    def _delete_associated_rule(self, ruleid, interface=None):
        if ruleid is None or ruleid == '' or ruleid == 'pass':
            return

        if interface is None:
            interface = self.params['interface']
        self.pfsense_rule_module = PFSenseRuleModule(self.module, self.pfsense)
        params = dict()
        if self.params['descr'] is None:
            params['name'] = 'NAT '
        else:
            params['name'] = 'NAT ' + self.params['descr']
        params['interface'] = interface
        params['state'] = 'absent'
        params['associated-rule-id'] = ruleid
        self.pfsense_rule_module.run(params)
        self.result['commands'] += self.pfsense_rule_module.result['commands']

    def _find_rule_by_descr(self, descr):
        """ find the XML target_elt """
        for idx, rule_elt in enumerate(self.root_elt):
            if rule_elt.tag != 'rule':
                continue

            if rule_elt.find('descr').text == descr:
                return (rule_elt, idx)
        return (None, None)

    def _find_target(self):
        """ find the XML target_elt """
        for rule_elt in self.root_elt:
            if rule_elt.tag != 'rule':
                continue

            if rule_elt.find('descr').text == self.obj['descr']:
                return rule_elt
        return None

    def _get_expected_rule_position(self):
        """ get expected rule position in interface/floating """
        if self.before == 'bottom':
            return len(self.root_elt)
        elif self.after == 'top':
            return 0
        elif self.after is not None:
            return self._get_rule_position(self.after) + 1
        elif self.before is not None:
            position = self._get_rule_position(self.before) - 1
            if position < 0:
                return 0
            return position
        else:
            position = self._get_rule_position(self.after, fail=False)
            if position is not None:
                return position
            return len(self.root_elt)
        return -1

    def _get_expected_rule_xml_index(self):
        """ get expected rule index in xml """
        if self.before == 'bottom':
            return len(self.root_elt)
        elif self.after == 'top':
            return 0
        elif self.after is not None:
            found, i = self._find_rule_by_descr(self.after)
            if found is not None:
                return i + 1
            else:
                self.module.fail_json(msg='Failed to insert after rule=%s' %
                                      (self.after))
        elif self.before is not None:
            found, i = self._find_rule_by_descr(self.before)
            if found is not None:
                return i
            else:
                self.module.fail_json(msg='Failed to insert before rule=%s' %
                                      (self.before))
        else:
            found, i = self._find_rule_by_descr(self.obj['descr'])
            if found is not None:
                return i
            return len(self.root_elt)
        return -1

    @staticmethod
    def _get_params_to_remove():
        """ returns the list of params to remove if they are not set """
        return ['disabled', 'nordr', 'nosync', 'natreflection']

    def _get_rule_position(self, descr=None, fail=True):
        """ get rule position in interface/floating """
        if descr is None:
            descr = self.obj['descr']

        (res, idx) = self._find_rule_by_descr(descr)
        if fail and res is None:
            self.module.fail_json(msg='Failed to find rule=%s' % (descr))
        return idx

    def _insert(self, rule_elt):
        """ insert rule into xml """
        rule_xml_idx = self._get_expected_rule_xml_index()
        self.root_elt.insert(rule_xml_idx, rule_elt)

    def _pre_remove_target_elt(self):
        """ processing before removing elt """
        ruleid_elt = self.target_elt.find('associated-rule-id')
        if ruleid_elt is not None:
            self._delete_associated_rule(ruleid_elt.text)

    def _set_associated_rule(self, before=None):
        """ manage changes to the associated rule """
        if before is None:
            if self.params['associated_rule'] == 'associated' or self.params[
                    'associated_rule'] == 'unassociated':
                self._create_associated_rule()
        else:
            if self.params['associated_rule'] == 'associated':
                if before['associated-rule-id'].startswith('nat_'):
                    if self.params['interface'] != before['interface']:
                        self._delete_associated_rule(
                            before['associated-rule-id'], before['interface'])
                    else:
                        self.obj['associated-rule-id'] = before[
                            'associated-rule-id']
                        return
                self._create_associated_rule()
            elif before['associated-rule-id'].startswith('nat_'):
                self._delete_associated_rule(before['associated-rule-id'])

    def _update_rule_position(self, rule_elt):
        """ move rule in xml if required """
        current_position = self._get_rule_position()
        expected_position = self._get_expected_rule_position()
        if current_position == expected_position:
            self.position_changed = False
            return False

        self.diff['before']['position'] = current_position
        self.diff['after']['position'] = expected_position
        self.root_elt.remove(rule_elt)
        self._insert(rule_elt)
        self.position_changed = True
        return True

    ##############################
    # run
    #
    def _update(self):
        """ make the target pfsense reload """
        return self.pfsense.phpshell('''require_once("filter.inc");
if (filter_configure() == 0) { clear_subsystem_dirty('natconf'); clear_subsystem_dirty('filter'); }'''
                                     )

    ##############################
    # Logging
    #
    def _get_obj_name(self):
        """ return obj's name """
        return "'{0}'".format(self.obj['descr'])

    @staticmethod
    def fassociate(value):
        """ associated-rule-id value formatting function """
        if value is None or value == '':
            return 'none'

        if value == 'pass':
            return 'pass'

        return 'associated'

    @staticmethod
    def fnatreflection(value):
        """ natreflection value formatting function """
        if value is None or value == 'none':
            return "'system-default'"

        return value

    @staticmethod
    def fprotocol(value):
        """ protocol value formatting function """
        if value is None or value == 'none':
            return 'any'

        return value

    def _log_fields(self, before=None):
        """ generate pseudo-CLI command fields parameters to create an obj """
        values = ''
        fafter = self._obj_to_log_fields(self.obj)
        if before is None:
            values += self.format_cli_field(self.params,
                                            'disabled',
                                            fvalue=self.fvalue_bool,
                                            default=False)
            values += self.format_cli_field(self.params,
                                            'nordr',
                                            fvalue=self.fvalue_bool,
                                            default=False)
            values += self.format_cli_field(self.params, 'interface')
            values += self.format_cli_field(self.params,
                                            'protocol',
                                            default='tcp')
            values += self.format_cli_field(self.params, 'source')
            values += self.format_cli_field(self.params, 'destination')
            values += self.format_cli_field(self.params, 'target')
            values += self.format_cli_field(self.params,
                                            'natreflection',
                                            default='system-default')
            values += self.format_cli_field(self.params,
                                            'associated_rule',
                                            default='associated')
            values += self.format_cli_field(self.params,
                                            'nosync',
                                            fvalue=self.fvalue_bool,
                                            default=False)
            values += self.format_cli_field(self.params, 'after')
            values += self.format_cli_field(self.params, 'before')
        else:
            fbefore = self._obj_to_log_fields(before)
            fafter['before'] = self.before
            fafter['after'] = self.after

            values += self.format_updated_cli_field(self.obj,
                                                    before,
                                                    'disabled',
                                                    fvalue=self.fvalue_bool,
                                                    default=False,
                                                    add_comma=(values))
            values += self.format_updated_cli_field(self.obj,
                                                    before,
                                                    'nordr',
                                                    fvalue=self.fvalue_bool,
                                                    default=False,
                                                    add_comma=(values))
            values += self.format_updated_cli_field(fafter,
                                                    fbefore,
                                                    'interface',
                                                    add_comma=(values))
            values += self.format_updated_cli_field(self.obj,
                                                    before,
                                                    'protocol',
                                                    fvalue=self.fprotocol,
                                                    add_comma=(values))
            values += self.format_updated_cli_field(fafter,
                                                    fbefore,
                                                    'source',
                                                    add_comma=(values))
            values += self.format_updated_cli_field(fafter,
                                                    fbefore,
                                                    'destination',
                                                    add_comma=(values))
            values += self.format_updated_cli_field(fafter,
                                                    fbefore,
                                                    'target',
                                                    add_comma=(values))
            values += self.format_updated_cli_field(self.obj,
                                                    before,
                                                    'natreflection',
                                                    fvalue=self.fnatreflection,
                                                    default='system-default',
                                                    add_comma=(values))
            values += self.format_updated_cli_field(self.obj,
                                                    before,
                                                    'associated-rule-id',
                                                    fvalue=self.fassociate,
                                                    fname='associated_rule',
                                                    add_comma=(values))
            values += self.format_updated_cli_field(self.obj,
                                                    before,
                                                    'nosync',
                                                    fvalue=self.fvalue_bool,
                                                    default=False,
                                                    add_comma=(values))
            if self.position_changed:
                values += self.format_updated_cli_field(fafter, {},
                                                        'after',
                                                        log_none=False,
                                                        add_comma=(values))
                values += self.format_updated_cli_field(fafter, {},
                                                        'before',
                                                        log_none=False,
                                                        add_comma=(values))

        return values

    @staticmethod
    def _obj_address_to_log_field(rule, addr):
        """ return formated address from dict """
        field = ''
        if isinstance(rule[addr], dict):
            if 'any' in rule[addr]:
                field = 'any'
            if 'address' in rule[addr]:
                field = rule[addr]['address']
            if 'port' in rule[addr]:
                if field:
                    field += ':'
                field += rule[addr]['port']
        else:
            field = rule[addr]
        return field

    def _obj_to_log_fields(self, rule):
        """ return formated source and destination from dict """
        res = {}
        res['source'] = self._obj_address_to_log_field(rule, 'source')
        res['destination'] = self._obj_address_to_log_field(
            rule, 'destination')
        res['target'] = rule['target'] + ':' + rule['local-port']
        res['interface'] = self.pfsense.get_interface_display_name(
            rule['interface'])

        return res