Wojtek Porczyk 6 жил өмнө
parent
commit
37d501c42b
1 өөрчлөгдсөн 141 нэмэгдсэн , 16 устгасан
  1. 141 16
      qubesmanager/firewall.py

+ 141 - 16
qubesmanager/firewall.py

@@ -19,18 +19,24 @@
 #
 #
 
-import sys
+import datetime
+import ipaddress
 import os
 import re
+import sys
 import xml.etree.ElementTree
 
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
-import datetime
+
+import qubesadmin.firewall
 
 from . import ui_newfwruledlg
 
 
+class FirewallModifiedOutsideError(ValueError):
+    pass
+
 class QIPAddressValidator(QValidator):
     def __init__(self, parent = None):
         super (QIPAddressValidator, self).__init__(parent)
@@ -39,10 +45,10 @@ class QIPAddressValidator(QValidator):
         hostname = str(input)
 
         if len(hostname) > 255 or len(hostname) == 0:
-            return (QValidator.Intermediate, pos)
+            return (QValidator.Intermediate, input, pos)
 
         if hostname == "*":
-            return (QValidator.Acceptable, pos)
+            return (QValidator.Acceptable, input, pos)
 
         unmask = hostname.split("/", 1)
         if len(unmask) == 2:
@@ -50,25 +56,25 @@ class QIPAddressValidator(QValidator):
             mask = unmask[1]
             if mask.isdigit() or mask == "":
                 if re.match("^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None:
-                    return (QValidator.Invalid, pos)
+                    return (QValidator.Invalid, input, pos)
                 if mask != "":
                     mask = int(unmask[1])
                     if mask < 0 or mask > 32:
-                        return (QValidator.Invalid, pos)
+                        return (QValidator.Invalid, input, pos)
             else:
-                return (QValidator.Invalid, pos)
+                return (QValidator.Invalid, input, pos)
 
         if hostname[-1:] == ".":
             hostname = hostname[:-1]
 
         if hostname[-1:] == "-":
-            return (QValidator.Intermediate, pos)
+            return (QValidator.Intermediate, input, pos)
 
         allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
         if all(allowed.match(x) for x in hostname.split(".")):
-            return (QValidator.Acceptable, pos)
+            return (QValidator.Acceptable, input, pos)
 
-        return (QValidator.Invalid, pos)
+        return (QValidator.Invalid, input, pos)
 
 class NewFwRuleDlg (QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg):
     def __init__(self, parent = None):
@@ -194,12 +200,135 @@ class QubesFirewallRulesModel(QAbstractItemModel):
     def get_column_string(self, col, row):
         return self.__columnValues[col](row)
 
+
+    def rule_to_dict(self, rule):
+        if rule.dsthost is None:
+            raise FirewallModifiedOutsideError('no dsthost')
+
+        d = {}
+
+        if not rule.proto:
+            d['proto'] = 'any'
+            d['portBegin'] = 'any'
+            d['portEnd'] = None
+
+        else:
+            d['proto'] = rule.proto
+            if rule.dstports is None:
+                raise FirewallModifiedOutsideError('no dstport')
+            d['portBegin'] = rule.dstports.range[0]
+            d['portEnd'] = rule.dstports.range[1] \
+                if rule.dstports.range[0] != rule.dstports.range[1] \
+                else None
+
+        if rule.dsthost.type == 'dsthost':
+            d['address'] = str(rule.dsthost)
+            d['netmask'] = 32
+        elif rule.dsthost.type == 'dst4':
+            network = ipaddress.IPv4Network(rule.dsthost)
+            d['address'] = str(network.network_address)
+            d['netmask'] = int(network.prefixlen)
+        else:
+            raise FirewallModifiedOutsideError(
+                'cannot map dsthost.type={!s}'.format(rule.dsthost))
+
+        if rule.expire is not None:
+            d['expire'] = int(rule.expire)
+
+        return d
+
+    def get_firewall_conf(self, vm):
+        conf = {
+            'allow': None,
+            'allowDns': False,
+            'allowIcmp': False,
+            'allowYumProxy': False,
+            'rules': [],
+        }
+
+        common_action = None
+        tentative_action = None
+
+        reversed_rules = list(reversed(vm.firewall.rules))
+
+        while reversed_rules:
+            rule = reversed_rules[0]
+            if rule.dsthost is not None or rule.proto is not None:
+                break
+            tentative_action = reversed_rules.pop(0).action
+
+        if not reversed_rules:
+            conf['allow'] = tentative_action == 'accept'
+            return conf
+
+        for rule in reversed_rules:
+            if rule.specialtarget == 'dns':
+                conf['allowDns'] = (rule.action == 'accept')
+                continue
+
+            if rule.proto == 'icmp':
+                if rule.icmptype is not None:
+                    raise FirewallModifiedOutsideError(
+                        'cannot map icmptype != None')
+                conf['allowIcmp'] = (rule.action == 'accept')
+                continue
+
+            if common_action is None:
+                common_action = rule.action
+            elif common_action != rule.action:
+                raise FirewallModifiedOutsideError('incoherent action')
+
+            conf['rules'].insert(0, self.rule_to_dict(rule))
+
+        if common_action is None or common_action != tentative_action:
+            # we've got only specialtarget and/or icmp
+            conf['allow'] = tentative_action == 'accept'
+            return conf
+
+        raise FirewallModifiedOutsideError('it does not add up')
+
+    def write_firewall_conf(self, vm, conf):
+        common_action = qubesadmin.firewall.Action(
+            'drop' if conf['allow'] else 'accept')
+
+        rules = []
+
+        for rule in conf['rules']:
+            kwargs = {}
+            if rule['proto'] != 'any':
+                kwargs['proto'] = rule['proto']
+                if rule['portBegin'] != 'any':
+                    kwargs['dstports'] = '-'.join(map(str, filter((lambda x: x),
+                        (rule['portBegin'], rule['portEnd']))))
+
+            netmask = str(rule['netmask']) if rule['netmask'] != 32 else None
+
+            rules.append(qubesadmin.firewall.Rule(None,
+                action=common_action,
+                dsthost='/'.join(map(str, filter((lambda x: x),
+                    (rule['address'], netmask)))),
+                **kwargs))
+
+        if conf['allowDns']:
+            rules.append(qubesadmin.firewall.Rule(None,
+                action='accept', specialtarget='dns'))
+
+        if conf['allowIcmp']:
+            rules.append(qubesadmin.firewall.Rule(None,
+                action='accept', proto='icmp'))
+
+        if common_action == 'drop':
+            rules.append(qubesadmin.firewall.Rule(None,
+                action='accept'))
+
+        vm.firewall.rules = rules
+
     def set_vm(self, vm):
         self.__vm = vm
 
         self.clearChildren()
 
-        conf = vm.get_firewall_conf()
+        conf = self.get_firewall_conf(vm)
 
         self.allow = conf["allow"]
         self.allowDns = conf["allowDns"]
@@ -252,7 +381,7 @@ class QubesFirewallRulesModel(QAbstractItemModel):
                                   })
 
         if self.fw_changed:
-            self.__vm.write_firewall_conf(conf)
+            self.write_firewall_conf(self.__vm, conf)
 
             if self.__vm.is_running():
                 vm = self.__vm.netvm
@@ -287,15 +416,11 @@ class QubesFirewallRulesModel(QAbstractItemModel):
         if index.isValid() and role == Qt.DisplayRole:
             return self.__columnValues[index.column()](index.row())
 
-        return QVariant()
-
     def headerData(self, section, orientation, role=Qt.DisplayRole):
         if section < len(self.__columnNames) \
                 and orientation == Qt.Horizontal and role == Qt.DisplayRole:
                     return self.__columnNames[section]
 
-        return QVariant()
-
     @property
     def children(self):
         return self.__children