From 82a89b79d6d2a744d4621dbd8cb37cfd8243ae3c Mon Sep 17 00:00:00 2001
From: Boris Kaul <me@boriskaul.com>
Date: Thu, 10 Oct 2013 20:09:52 +0700
Subject: [PATCH] Add support for Rich Rules in firewalld module

---
 system/firewalld | 98 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 96 insertions(+), 2 deletions(-)

diff --git a/system/firewalld b/system/firewalld
index 4665d03a940..9526abbefff 100644
--- a/system/firewalld
+++ b/system/firewalld
@@ -36,6 +36,11 @@ options:
       - "Name of a port to add/remove to/from firewalld must be in the form PORT/PROTOCOL"
     required: false
     default: null
+  rich_rule:
+    description:
+      - "Rich rule to add/remove to/from firewalld"
+    required: false
+    default: null
   zone:
     description:
       - 'The firewalld zone to add/remove to/from (NOTE: default zone can be configured per system but "public" is default from upstream. Available choices can be extended based on per-system configs, listed here are "out of the box" defaults).'
@@ -67,6 +72,7 @@ EXAMPLES = '''
 - firewalld: service=https permanent=true state=enabled
 - firewalld: port=8081/tcp permanent=true state=disabled
 - firewalld: zone=dmz service=http permanent=true state=enabled
+- firewalld: rich_rule='rule service name="ftp" audit limit value="1/m" accept' permanent=true state=enabled
 '''
 
 import os
@@ -154,12 +160,50 @@ def set_service_disabled_permanent(zone, service):
     fw_settings.removeService(service)
     fw_zone.update(fw_settings)
     
+
+####################
+# rich rule handling
+#
+def get_rich_rule_enabled(zone, rule):
+    if rule in fw.getRichRules(zone):
+        return True
+    else:
+        return False
+
+def set_rich_rule_enabled(zone, rule, timeout):
+    fw.addRichRule(zone, rule, timeout)
+
+def set_rich_rule_disabled(zone, rule):
+    fw.removeRichRule(zone, rule)
+
+def get_rich_rule_enabled_permanent(zone, rule):
+    fw_zone = fw.config().getZoneByName(zone)
+    fw_settings = fw_zone.getSettings()
+    if rule in fw_settings.getRichRules():
+        return True
+    else:
+        return False
+
+def set_rich_rule_enabled_permanent(zone, rule):
+    fw_zone = fw.config().getZoneByName(zone)
+    fw_settings = fw_zone.getSettings()
+    fw_settings.addRichRule(rule)
+    fw_zone.update(fw_settings)
+
+def set_rich_rule_disabled_permanent(zone, rule):
+    fw_zone = fw.config().getZoneByName(zone)
+    fw_settings = fw_zone.getSettings()
+    fw_settings.removeRichRule(rule)
+    fw_zone.update(fw_settings)
+
+
 def main():
 
     module = AnsibleModule(
         argument_spec = dict(
             service=dict(required=False,default=None),
             port=dict(required=False,default=None),
+            rich_rule=dict(required=False,default=None),
             zone=dict(required=False,default=None),
             permanent=dict(type='bool',required=True),
             state=dict(choices=['enabled', 'disabled'], required=True),
@@ -176,6 +220,7 @@ def main():
     changed=False
     msgs = []
     service = module.params['service']
+    rich_rule = module.params['rich_rule']
 
     if module.params['port'] != None:
         port, protocol = module.params['port'].split('/')
@@ -201,8 +246,16 @@ def main():
         module.fail_json(msg="firewalld connection can't be established,\
                 version likely too old. Requires firewalld >= 2.0.11")
 
-    if service != None and port != None:
-        module.fail_json(msg='can only operate on port or service at once')
+    modification_count = 0
+    if service != None:
+        modification_count += 1
+    if port != None:
+        modification_count += 1
+    if rich_rule != None:
+        modification_count += 1
+
+    if modification_count > 1:
+        module.fail_json(msg='can only operate on port, service or rich_rule at once')
 
     if service != None:
         if permanent:
@@ -288,6 +341,47 @@ def main():
             msgs.append("Changed port %s to %s" % ("%s/%s" % (port, protocol), \
                         desired_state))
 
+    if rich_rule != None:
+        if permanent:
+            is_enabled = get_rich_rule_enabled_permanent(zone, rich_rule)
+            msgs.append('Permanent operation')
+
+            if desired_state == "enabled":
+                if is_enabled == False:
+                    if module.check_mode:
+                        module.exit_json(changed=True)
+
+                    set_rich_rule_enabled_permanent(zone, rich_rule)
+                    changed=True
+            elif desired_state == "disabled":
+                if is_enabled == True:
+                    if module.check_mode:
+                        module.exit_json(changed=True)
+
+                    set_rich_rule_disabled_permanent(zone, rich_rule)
+                    changed=True
+        else:
+            is_enabled = get_rich_rule_enabled(zone, rich_rule)
+            msgs.append('Non-permanent operation')
+
+            if desired_state == "enabled":
+                if is_enabled == False:
+                    if module.check_mode:
+                        module.exit_json(changed=True)
+
+                    set_rich_rule_enabled(zone, rich_rule, timeout)
+                    changed=True
+            elif desired_state == "disabled":
+                if is_enabled == True:
+                    if module.check_mode:
+                        module.exit_json(changed=True)
+
+                    set_rich_rule_disabled(zone, rich_rule)
+                    changed=True
+
+        if changed == True:
+            msgs.append("Changed rich_rule %s to %s" % (rich_rule, desired_state))
+
     module.exit_json(changed=changed, msg=', '.join(msgs))