From 43391a3d82b09418822068a79b6bb335656b2967 Mon Sep 17 00:00:00 2001
From: Őry Máté <ory.mate@cloud.bme.hu>
Date: Wed, 4 Sep 2013 23:23:07 +0200
Subject: [PATCH] firewall: merge from network-gui

---
 circle/firewall/admin.py                        |  46 +++++++++++++++++++++++++++++-----------------
 circle/firewall/fields.py                       |  31 ++++++++++++++++++++++++++++---
 circle/firewall/fw.py                           | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------------------------
 circle/firewall/locale/hu/LC_MESSAGES/django.po | 572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 circle/firewall/models.py                       | 700 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 circle/firewall/tasks.py                        |  14 ++++++++++----
 circle/firewall/tests.py                        |   6 +++++-
 circle/firewall/views.py                        |  61 ++++++++++++++++++++++++++++++++++---------------------------
 8 files changed, 1295 insertions(+), 340 deletions(-)
 create mode 100644 circle/firewall/locale/hu/LC_MESSAGES/django.po

diff --git a/circle/firewall/admin.py b/circle/firewall/admin.py
index 448f619..0eabebf 100644
--- a/circle/firewall/admin.py
+++ b/circle/firewall/admin.py
@@ -1,19 +1,23 @@
-# -*- coding: utf8 -*-
+# -*- coding: utf-8 -*-
 
 from django.contrib import admin
-from firewall.models import *
+from firewall.models import (Rule, Host, Vlan, Group, VlanGroup, Firewall,
+                             Domain, Record, Blacklist)
 from django import contrib
 
 
 class RuleInline(contrib.admin.TabularInline):
     model = Rule
 
+
 class RecordInline(contrib.admin.TabularInline):
     model = Record
 
+
 class HostAdmin(admin.ModelAdmin):
     list_display = ('hostname', 'vlan', 'ipv4', 'ipv6', 'pub_ipv4', 'mac',
-        'shared_ip', 'owner', 'description', 'reverse', 'list_groups')
+                    'shared_ip', 'owner', 'description', 'reverse',
+                    'list_groups')
     ordering = ('hostname', )
     list_filter = ('owner', 'vlan', 'groups')
     search_fields = ('hostname', 'description', 'ipv4', 'ipv6', 'mac')
@@ -26,42 +30,46 @@ class HostAdmin(admin.ModelAdmin):
         names = [group.name for group in instance.groups.all()]
         return u', '.join(names)
 
+
 class HostInline(contrib.admin.TabularInline):
     model = Host
     fields = ('hostname', 'ipv4', 'ipv6', 'pub_ipv4', 'mac', 'shared_ip',
-        'owner', 'reverse')
+              'owner', 'reverse')
+
 
 class VlanAdmin(admin.ModelAdmin):
     list_display = ('vid', 'name', 'ipv4', 'net_ipv4', 'ipv6', 'net_ipv6',
-        'description', 'domain', 'snat_ip', )
+                    'description', 'domain', 'snat_ip', )
     ordering = ('vid', )
     inlines = (RuleInline, )
 
+
 class RuleAdmin(admin.ModelAdmin):
     list_display = ('r_type', 'color_desc', 'owner', 'extra', 'direction',
-        'accept', 'proto', 'sport', 'dport', 'nat', 'nat_dport', 'used_in')
+                    'accept', 'proto', 'sport', 'dport', 'nat',
+                    'nat_dport', 'used_in')
     list_filter = ('r_type', 'vlan', 'owner', 'direction', 'accept',
-        'proto', 'nat')
+                   'proto', 'nat')
 
     def color_desc(self, instance):
         """Returns a colorful description of the instance."""
         return (u'<span style="color: #FF0000;">[%(type)s]</span> '
                 u'%(src)s<span style="color: #0000FF;"> ▸ </span>%(dst)s '
                 u'%(para)s %(desc)s') % {
-                     'type': instance.r_type,
-                     'src': (instance.foreign_network.name
-                         if instance.direction == '1' else instance.r_type),
-                     'dst': (instance.r_type if instance.direction == '1'
-                         else instance.foreign_network.name),
-                     'para': (u'<span style="color: #00FF00;">' +
+                    'type': instance.r_type,
+                    'src': (instance.foreign_network.name
+                            if instance.direction == '1' else instance.r_type),
+                    'dst': (instance.r_type if instance.direction == '1'
+                            else instance.foreign_network.name),
+                    'para': (u'<span style="color: #00FF00;">' +
                              (('proto=%s ' % instance.proto)
                                  if instance.proto else '') +
                              (('sport=%s ' % instance.sport)
                                  if instance.sport else '') +
                              (('dport=%s ' % instance.dport)
                                  if instance.dport else '') +
-                               '</span>'),
-                     'desc': instance.description}
+                             '</span>'),
+                    'desc': instance.description}
     color_desc.allow_tags = True
 
     @staticmethod
@@ -73,7 +81,7 @@ class RuleAdmin(admin.ModelAdmin):
     @staticmethod
     def used_in(instance):
         for field in [instance.vlan, instance.vlangroup, instance.host,
-                instance.hostgroup, instance.firewall]:
+                      instance.hostgroup, instance.firewall]:
             if field:
                 return unicode(field) + ' ' + field._meta.object_name
 
@@ -81,16 +89,20 @@ class RuleAdmin(admin.ModelAdmin):
 class AliasAdmin(admin.ModelAdmin):
     list_display = ('alias', 'host')
 
+
 class GroupAdmin(admin.ModelAdmin):
     list_display = ('name', 'owner', 'description')
     inlines = (RuleInline, )
 
+
 class FirewallAdmin(admin.ModelAdmin):
     inlines = (RuleInline, )
 
+
 class DomainAdmin(admin.ModelAdmin):
     list_display = ('name', 'owner')
 
+
 class RecordAdmin(admin.ModelAdmin):
     list_display = ('name_', 'type', 'address_', 'ttl', 'host', 'owner')
 
@@ -104,6 +116,7 @@ class RecordAdmin(admin.ModelAdmin):
         a = instance.get_data()
         return a['name'] if a else None
 
+
 class BlacklistAdmin(admin.ModelAdmin):
     list_display = ('ipv4', 'reason', 'created_at', 'modified_at')
 
@@ -116,4 +129,3 @@ admin.site.register(Firewall, FirewallAdmin)
 admin.site.register(Domain, DomainAdmin)
 admin.site.register(Record, RecordAdmin)
 admin.site.register(Blacklist, BlacklistAdmin)
-
diff --git a/circle/firewall/fields.py b/circle/firewall/fields.py
index e2348dc..f0bca9c 100644
--- a/circle/firewall/fields.py
+++ b/circle/firewall/fields.py
@@ -6,12 +6,14 @@ from django.utils.ipv6 import is_valid_ipv6_address
 from south.modelsinspector import add_introspection_rules
 import re
 
-mac_re = re.compile(r'^([0-9a-fA-F]{2}([:-]?|$)){6}$')
+
+mac_re = re.compile(r'^([0-9a-fA-F]{2}(:|$)){6}$')
 alfanum_re = re.compile(r'^[A-Za-z0-9_-]+$')
 domain_re = re.compile(r'^([A-Za-z0-9_-]\.?)+$')
 ipv4_re = re.compile('^[0-9]+\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
 reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$')
 
+
 class MACAddressFormField(fields.RegexField):
     default_error_messages = {
         'invalid': _(u'Enter a valid MAC address.'),
@@ -20,8 +22,10 @@ class MACAddressFormField(fields.RegexField):
     def __init__(self, *args, **kwargs):
         super(MACAddressFormField, self).__init__(mac_re, *args, **kwargs)
 
+
 class MACAddressField(models.Field):
     empty_strings_allowed = False
+
     def __init__(self, *args, **kwargs):
         kwargs['max_length'] = 17
         super(MACAddressField, self).__init__(*args, **kwargs)
@@ -35,47 +39,68 @@ class MACAddressField(models.Field):
         return super(MACAddressField, self).formfield(**defaults)
 add_introspection_rules([], ["firewall\.fields\.MACAddressField"])
 
+
 def val_alfanum(value):
     """Validate whether the parameter is a valid alphanumeric value."""
     if not alfanum_re.match(value):
         raise ValidationError(_(u'%s - only letters, numbers, underscores '
-            'and hyphens are allowed!') % value)
+                                'and hyphens are allowed!') % value)
+
 
 def is_valid_domain(value):
     """Check whether the parameter is a valid domain name."""
     return domain_re.match(value) is not None
 
+
 def val_domain(value):
     """Validate whether the parameter is a valid domin name."""
     if not is_valid_domain(value):
         raise ValidationError(_(u'%s - invalid domain name') % value)
 
+
 def is_valid_reverse_domain(value):
     """Check whether the parameter is a valid reverse domain name."""
     return reverse_domain_re.match(value) is not None
 
+
 def val_reverse_domain(value):
     """Validate whether the parameter is a valid reverse domain name."""
     if not is_valid_reverse_domain(value):
         raise ValidationError(u'%s - invalid reverse domain name' % value)
 
+
 def is_valid_ipv4_address(value):
     """Check whether the parameter is a valid IPv4 address."""
     return ipv4_re.match(value) is not None
 
+
 def val_ipv4(value):
     """Validate whether the parameter is a valid IPv4 address."""
     if not is_valid_ipv4_address(value):
         raise ValidationError(_(u'%s - not an IPv4 address') % value)
 
+
 def val_ipv6(value):
     """Validate whether the parameter is a valid IPv6 address."""
     if not is_valid_ipv6_address(value):
         raise ValidationError(_(u'%s - not an IPv6 address') % value)
 
+
+def val_mx(value):
+    """Validate whether the parameter is a valid MX address definition.
+
+    Expected form is <priority>:<hostname>.
+    """
+    mx = value.split(':', 1)
+    if not (len(mx) == 2 and mx[0].isdigit() and
+            domain_re.match(mx[1])):
+        raise ValidationError(_("Bad MX address format. "
+                                "Should be: <priority>:<hostname>"))
+
+
 def ipv4_2_ipv6(ipv4):
     """Convert IPv4 address string to IPv6 address string."""
     val_ipv4(ipv4)
     m = ipv4_re.match(ipv4)
     return ("2001:738:2001:4031:%s:%s:%s:0" %
-        (m.group(1), m.group(2), m.group(3)))
+            (m.group(1), m.group(2), m.group(3)))
diff --git a/circle/firewall/fw.py b/circle/firewall/fw.py
index c4b2814..1c757c9 100644
--- a/circle/firewall/fw.py
+++ b/circle/firewall/fw.py
@@ -1,23 +1,21 @@
-from django.contrib import auth
 from firewall import models
-import os
 import django.conf
 
 
 import subprocess
 import re
-import json
 from datetime import datetime, timedelta
 from django.db.models import Q
 
 
 settings = django.conf.settings.FIREWALL_SETTINGS
+
+
 class Firewall:
-    IPV6=False
+    IPV6 = False
     RULES = None
     RULES_NAT = []
     vlans = None
-    dmz = None
     pub = None
     hosts = None
     fw = None
@@ -30,13 +28,12 @@ class Firewall:
                 retval += ' --sport %s ' % rule.sport
             if rule.dport:
                 retval += ' --dport %s ' % (rule.nat_dport
-                        if (repl and rule.nat and rule.direction == '1')
-                        else rule.dport)
+                          if (repl and rule.nat and rule.direction == '1')
+                          else rule.dport)
         elif rule.proto == 'icmp':
             retval = '-p %s ' % rule.proto
         return retval
 
-
     def iptables(self, s):
         """Append rule to filter table."""
         self.RULES.append(s)
@@ -61,8 +58,8 @@ class Firewall:
                 if rule.direction == '0' and vlan.name == 'PUB':
                     if rule.dport == 25:
                         self.iptables('-A PUB_OUT -s %s %s -p tcp '
-                                '--dport 25 -j LOG_ACC' %
-                                (ipaddr, rule.extra))
+                                      '--dport 25 -j LOG_ACC' %
+                                      (ipaddr, rule.extra))
                         break
                     action = 'PUB_OUT'
                 else:
@@ -70,13 +67,14 @@ class Firewall:
             else:
                 action = 'LOG_DROP'
 
-            if rule.direction == '1': # going TO host
-                self.iptables('-A %s_%s -d %s %s %s -g %s' % (vlan,
-                    host.vlan, ipaddr, dport_sport, rule.extra, action))
+            if rule.direction == '1':  # going TO host
+                self.iptables('-A %s_%s -d %s %s %s -g %s' %
+                              (vlan, host.vlan, ipaddr, dport_sport,
+                               rule.extra, action))
             else:
-                self.iptables('-A %s_%s -s %s %s %s -g %s' % (host.vlan,
-                    vlan, ipaddr, dport_sport, rule.extra, action))
-
+                self.iptables('-A %s_%s -s %s %s %s -g %s' %
+                              (host.vlan, vlan, ipaddr, dport_sport,
+                               rule.extra, action))
 
     def fw2vlan(self, rule):
         if not rule.foreign_network:
@@ -85,14 +83,14 @@ class Firewall:
         dport_sport = self.dportsport(rule)
 
         for vlan in rule.foreign_network.vlans.all():
-            if rule.direction == '1': # going TO host
+            if rule.direction == '1':  # going TO host
                 self.iptables('-A INPUT -i %s %s %s -g %s' %
-                    (vlan.interface, dport_sport, rule.extra,
-                        'LOG_ACC' if rule.accept else 'LOG_DROP'))
+                              (vlan.interface, dport_sport, rule.extra,
+                               'LOG_ACC' if rule.accept else 'LOG_DROP'))
             else:
                 self.iptables('-A OUTPUT -o %s %s %s -g %s' %
-                    (vlan.interface, dport_sport, rule.extra,
-                        'LOG_ACC' if rule.accept else 'LOG_DROP'))
+                              (vlan.interface, dport_sport, rule.extra,
+                               'LOG_ACC' if rule.accept else 'LOG_DROP'))
 
     def vlan2vlan(self, l_vlan, rule):
         if not rule.foreign_network:
@@ -109,13 +107,13 @@ class Firewall:
             else:
                 action = 'LOG_DROP'
 
-            if rule.direction == '1': # going TO host
-                self.iptables('-A %s_%s %s %s -g %s' % (vlan, l_vlan,
-                    dport_sport, rule.extra, action))
+            if rule.direction == '1':  # going TO host
+                self.iptables('-A %s_%s %s %s -g %s' %
+                              (vlan, l_vlan, dport_sport, rule.extra, action))
             else:
                 self.iptables('-A %s_%s %s %s -g %s' % (l_vlan, vlan,
-                    dport_sport, rule.extra, action))
-
+                                                        dport_sport,
+                                                        rule.extra, action))
 
     def prerun(self):
         self.iptables('*filter')
@@ -129,39 +127,39 @@ class Firewall:
         self.iptables('-A LOG_DROP -p tcp --dport 445 -j DROP')
         self.iptables('-A LOG_DROP -p udp --dport 137 -j DROP')
         self.iptables('-A LOG_DROP -j LOG --log-level 7 '
-                '--log-prefix "[ipt][drop]"')
+                      '--log-prefix "[ipt][drop]"')
         self.iptables('-A LOG_DROP -j DROP')
         self.iptables('-N LOG_ACC')
         self.iptables('-A LOG_ACC -j LOG --log-level 7 '
-                '--log-prefix "[ipt][isok]"')
+                      '--log-prefix "[ipt][isok]"')
         self.iptables('-A LOG_ACC -j ACCEPT')
 
         self.iptables('-N PUB_OUT')
 
-        self.iptables('-A FORWARD -m set --match-set blacklist src,dst -j DROP')
+        self.iptables('-A FORWARD -m set --match-set blacklist src,dst '
+                      '-j DROP')
         self.iptables('-A FORWARD -m state --state INVALID -g LOG_DROP')
         self.iptables('-A FORWARD -m state --state ESTABLISHED,RELATED '
-                '-j ACCEPT')
+                      '-j ACCEPT')
         self.iptables('-A FORWARD -p icmp --icmp-type echo-request '
-                '-g LOG_ACC')
+                      '-g LOG_ACC')
 
         self.iptables('-A INPUT -m set --match-set blacklist src -j DROP')
         self.iptables('-A INPUT -m state --state INVALID -g LOG_DROP')
         self.iptables('-A INPUT -i lo -j ACCEPT')
         self.iptables('-A INPUT -m state --state ESTABLISHED,RELATED '
-                '-j ACCEPT')
+                      '-j ACCEPT')
 
         self.iptables('-A OUTPUT -m state --state INVALID -g LOG_DROP')
         self.iptables('-A OUTPUT -o lo -j ACCEPT')
         self.iptables('-A OUTPUT -m state --state ESTABLISHED,RELATED '
-                '-j ACCEPT')
-
+                      '-j ACCEPT')
 
     def postrun(self):
         self.iptables('-A PUB_OUT -s 152.66.243.160/27 -p tcp --dport 25 '
-                '-j LOG_ACC')
+                      '-j LOG_ACC')
         self.iptables('-A PUB_OUT -s 152.66.243.160/27 -p tcp --dport 445 '
-                '-j LOG_ACC')
+                      '-j LOG_ACC')
         self.iptables('-A PUB_OUT -p tcp --dport 25 -j LOG_DROP')
         self.iptables('-A PUB_OUT -p tcp --dport 445 -j LOG_DROP')
         self.iptables('-A PUB_OUT -p udp --dport 445 -j LOG_DROP')
@@ -172,9 +170,6 @@ class Firewall:
         self.iptables('-A OUTPUT -g LOG_DROP')
         self.iptables('COMMIT')
 
-
-
-
     def ipt_nat(self):
         self.iptablesnat('*nat')
         self.iptablesnat(':PREROUTING ACCEPT [0:0]')
@@ -188,34 +183,37 @@ class Firewall:
                 dport_sport = self.dportsport(rule, False)
                 if host.vlan.snat_ip:
                     self.iptablesnat('-A PREROUTING -d %s %s %s -j DNAT '
-                            '--to-destination %s:%s' % (host.pub_ipv4,
-                                dport_sport, rule.extra, host.ipv4,
-                                rule.nat_dport))
+                                     '--to-destination %s:%s' %
+                                     (host.pub_ipv4, dport_sport, rule.extra,
+                                      host.ipv4, rule.nat_dport))
 
         # rules for machines with dedicated public IP
         for host in self.hosts.exclude(shared_ip=True):
             if host.pub_ipv4:
                 self.iptablesnat('-A PREROUTING -d %s -j DNAT '
-                        '--to-destination %s' % (host.pub_ipv4, host.ipv4))
+                                 '--to-destination %s' %
+                                 (host.pub_ipv4, host.ipv4))
                 self.iptablesnat('-A POSTROUTING -s %s -j SNAT '
-                        '--to-source %s' % (host.ipv4, host.pub_ipv4))
+                                 '--to-source %s' %
+                                 (host.ipv4, host.pub_ipv4))
 
         # default NAT rules for VLANs
         for s_vlan in self.vlans:
             if s_vlan.snat_ip:
                 for d_vlan in s_vlan.snat_to.all():
                     self.iptablesnat('-A POSTROUTING -s %s -o %s -j SNAT '
-                            '--to-source %s' % (s_vlan.net_ipv4(),
-                                d_vlan.interface, s_vlan.snat_ip))
-
+                                     '--to-source %s' %
+                                     (s_vlan.net_ipv4(), d_vlan.interface,
+                                      s_vlan.snat_ip))
 
         # hard-wired rules
         self.iptablesnat('-A POSTROUTING -s 10.5.0.0/16 -o vlan0003 -j SNAT '
-                '--to-source 10.3.255.254') # man elerheto legyen
-        self.iptablesnat('-A POSTROUTING -s 10.5.0.0/16 -o vlan0008 -j SNAT '
-                '--to-source 10.0.0.247') # wolf network for printing
-        self.iptablesnat('-A POSTROUTING -s 10.3.0.0/16 -o vlan0002 -j SNAT '
-                '--to-source %s' % self.pub.ipv4) # kulonben nemmegy a du
+                         '--to-source 10.3.255.254')  # man elerheto legyen
+        self.iptablesnat('-A POSTROUTING -o vlan0008 -j SNAT '
+                         '--to-source 10.0.0.247')  # wolf network for printing
+        self.iptablesnat('-A POSTROUTING -s 10.3.0.0/16 -p udp --dport 53 '
+                         '-o vlan0002 -j SNAT ''--to-source %s' %
+                         self.pub.ipv4)  # kulonben nem megy a dns man-ban
 
         self.iptablesnat('COMMIT')
 
@@ -235,7 +233,8 @@ class Firewall:
             for d_vlan in self.vlans:
                 self.iptables('-N %s_%s' % (s_vlan, d_vlan))
                 self.iptables('-A FORWARD -i %s -o %s -g %s_%s' %
-                    (s_vlan.interface, d_vlan.interface, s_vlan, d_vlan))
+                              (s_vlan.interface, d_vlan.interface, s_vlan,
+                               d_vlan))
 
         # hosts' rules
         for i_vlan in self.vlans:
@@ -264,12 +263,11 @@ class Firewall:
             self.RULES = [x.replace('icmp', 'icmpv6') for x in self.RULES]
 
     def __init__(self, IPV6=False):
-        self.RULES=[]
-        self.RULES_NAT=[]
+        self.RULES = []
+        self.RULES_NAT = []
         self.IPV6 = IPV6
         self.vlans = models.Vlan.objects.all()
         self.hosts = models.Host.objects.all()
-        self.dmz = models.Vlan.objects.get(name='DMZ')
         self.pub = models.Vlan.objects.get(name='PUB')
         self.fw = models.Firewall.objects.all()
         self.ipt_filter()
@@ -279,32 +277,37 @@ class Firewall:
     def reload(self):
         if self.IPV6:
             process = subprocess.Popen(['/usr/bin/ssh', 'fw2',
-                    '/usr/bin/sudo', '/sbin/ip6tables-restore', '-c'],
-                shell=False, stdin=subprocess.PIPE)
+                                        '/usr/bin/sudo',
+                                        '/sbin/ip6tables-restore', '-c'],
+                                       shell=False, stdin=subprocess.PIPE)
             process.communicate('\n'.join(self.RULES) + '\n')
         else:
             process = subprocess.Popen(['/usr/bin/ssh', 'fw2',
-                    '/usr/bin/sudo', '/sbin/iptables-restore', '-c'],
-                shell=False, stdin=subprocess.PIPE)
+                                        '/usr/bin/sudo',
+                                        '/sbin/iptables-restore', '-c'],
+                                       shell=False, stdin=subprocess.PIPE)
             process.communicate('\n'.join(self.RULES) + '\n' +
-                    '\n'.join(self.RULES_NAT) + '\n')
+                                '\n'.join(self.RULES_NAT) + '\n')
 
     def get(self):
         if self.IPV6:
-            return { 'filter': self.RULES, }
+            return {'filter': self.RULES, }
         else:
-            return { 'filter': self.RULES, 'nat': self.RULES_NAT }
+            return {'filter': self.RULES, 'nat': self.RULES_NAT}
 
     def show(self):
         if self.IPV6:
             return '\n'.join(self.RULES) + '\n'
         else:
             return ('\n'.join(self.RULES) + '\n' +
-                '\n'.join(self.RULES_NAT) + '\n')
+                    '\n'.join(self.RULES_NAT) + '\n')
+
 
 def ipset():
-    week = datetime.now()-timedelta(days=2)
-    return models.Blacklist.objects.filter(Q(type='tempban', modified_at__gte=week) | Q(type='permban')).values('ipv4', 'reason')
+    week = datetime.now() - timedelta(days=2)
+    filter_ban = (Q(type='tempban', modified_at__gte=week) |
+                  Q(type='permban')).values('ipv4', 'reason')
+    return models.Blacklist.objects.filter(filter_ban)
 
 
 def ipv6_to_octal(ipv6):
@@ -321,14 +324,16 @@ def ipv6_to_octal(ipv6):
             octets.append(int(part[2:], 16))
     return '\\' + '\\'.join(['%03o' % x for x in octets])
 
+
 def ipv4_to_arpa(ipv4, cname=False):
     m2 = re.search(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$', ipv4)
     if cname:
         return ('%s.dns1.%s.%s.%s.in-addr.arpa' %
-            (m2.group(4), m2.group(3), m2.group(2), m2.group(1)))
+                (m2.group(4), m2.group(3), m2.group(2), m2.group(1)))
     else:
         return ('%s.%s.%s.%s.in-addr.arpa' %
-            (m2.group(4), m2.group(3), m2.group(2), m2.group(1)))
+                (m2.group(4), m2.group(3), m2.group(2), m2.group(1)))
+
 
 def ipv6_to_arpa(ipv6):
     while len(ipv6.split(':')) < 8:
@@ -357,11 +362,11 @@ def ipv6_to_arpa(ipv6):
 
 def dns():
     vlans = models.Vlan.objects.all()
-    regex = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
+    # regex = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
     DNS = []
 
     for i_vlan in vlans:
-        m = regex.search(i_vlan.net4)
+        # m = regex.search(i_vlan.net4)
         rev = i_vlan.reverse_domain
 
         for i_host in i_vlan.host_set.all():
@@ -369,31 +374,32 @@ def dns():
                     not i_host.shared_ip else i_host.ipv4)
             i = ipv4.split('.', 4)
             reverse = (i_host.reverse if i_host.reverse and
-                    len(i_host.reverse) else i_host.get_fqdn())
+                       len(i_host.reverse) else i_host.get_fqdn())
 
             # ipv4
             if i_host.ipv4:
                 DNS.append("^%s:%s:%s" % (
-                    (rev % { 'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]),
-                             'd': int(i[3]) }),
+                    (rev % {'a': int(i[0]), 'b': int(i[1]), 'c': int(i[2]),
+                            'd': int(i[3])}),
                     reverse, models.settings['dns_ttl']))
 
             # ipv6
             if i_host.ipv6:
                 DNS.append("^%s:%s:%s" % (ipv6_to_arpa(i_host.ipv6),
-                    reverse, models.settings['dns_ttl']))
+                                          reverse, models.settings['dns_ttl']))
 
     for domain in models.Domain.objects.all():
-        DNS.append("Z%s:%s:support.ik.bme.hu::::::%s" % (domain.name,
-            settings['dns_hostname'], models.settings['dns_ttl']))
+        DNS.append("Z%s:%s:support.ik.bme.hu::::::%s" %
+                   (domain.name, settings['dns_hostname'],
+                    models.settings['dns_ttl']))
 
     for r in models.Record.objects.all():
         d = r.get_data()
         if d['type'] == 'A':
             DNS.append("+%s:%s:%s" % (d['name'], d['address'], d['ttl']))
         elif d['type'] == 'AAAA':
-            DNS.append(":%s:28:%s:%s" % (d['name'],
-                ipv6_to_octal(d['address']), d['ttl']))
+            DNS.append(":%s:28:%s:%s" %
+                       (d['name'], ipv6_to_octal(d['address']), d['ttl']))
         elif d['type'] == 'NS':
             DNS.append("&%s::%s:%s" % (d['name'], d['address'], d['ttl']))
         elif d['type'] == 'CNAME':
@@ -401,15 +407,16 @@ def dns():
         elif d['type'] == 'MX':
             mx = d['address'].split(':', 2)
             DNS.append("@%(fqdn)s::%(mx)s:%(dist)s:%(ttl)s" %
-                    {'fqdn': d['name'], 'mx': mx[1], 'dist': mx[0],
-                     'ttl': d['ttl']})
+                       {'fqdn': d['name'], 'mx': mx[1], 'dist': mx[0],
+                        'ttl': d['ttl']})
         elif d['type'] == 'PTR':
             DNS.append("^%s:%s:%s" % (d['name'], d['address'], d['ttl']))
 
     return DNS
     process = subprocess.Popen(['/usr/bin/ssh', 'tinydns@%s' %
-        settings['dns_hostname']], shell=False, stdin=subprocess.PIPE)
-    process.communicate("\n".join(DNS)+"\n")
+                                settings['dns_hostname']],
+                               shell=False, stdin=subprocess.PIPE)
+    process.communicate("\n".join(DNS) + "\n")
     # print "\n".join(DNS)+"\n"
 
 
@@ -422,10 +429,11 @@ def prefix_to_mask(prefix):
             t[i] = 256 - (2 ** ((i + 1) * 8 - prefix))
     return ".".join([str(i) for i in t])
 
+
 def dhcp():
     vlans = models.Vlan.objects.all()
     regex = re.compile(r'^([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+\s+'
-            r'([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+$')
+                       r'([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+$')
     DHCP = []
 
 # /tools/dhcp3/dhcpd.conf.generated
@@ -434,7 +442,7 @@ def dhcp():
         if(i_vlan.dhcp_pool):
             m = regex.search(i_vlan.dhcp_pool)
             if(m or i_vlan.dhcp_pool == "manual"):
-                DHCP.append ('''
+                DHCP.append('''
     # %(name)s - %(interface)s
     subnet %(net)s netmask %(netmask)s {
       %(extra)s;
@@ -446,7 +454,7 @@ def dhcp():
       authoritative;
       filename \"pxelinux.0\";
       allow bootp; allow booting;
-    }'''        % {
+    }''' % {
                     'net': i_vlan.net4,
                     'netmask': prefix_to_mask(i_vlan.prefix4),
                     'domain': i_vlan.domain,
@@ -454,14 +462,14 @@ def dhcp():
                     'ntp': i_vlan.ipv4,
                     'dnsserver': settings['rdns_ip'],
                     'extra': ("range %s" % i_vlan.dhcp_pool
-                        if m else "deny unknown-clients"),
+                              if m else "deny unknown-clients"),
                     'interface': i_vlan.interface,
                     'name': i_vlan.name,
                     'tftp': i_vlan.ipv4
                 })
 
                 for i_host in i_vlan.host_set.all():
-                    DHCP.append ('''
+                    DHCP.append('''
                     host %(hostname)s {
                       hardware ethernet %(mac)s;
                       fixed-address %(ipv4)s;
@@ -473,23 +481,8 @@ def dhcp():
 
     return DHCP
     process = subprocess.Popen(['/usr/bin/ssh', 'fw2',
-        'cat > /tools/dhcp3/dhcpd.conf.generated;'
-        'sudo /etc/init.d/isc-dhcp-server restart'], shell=False,
-        stdin=subprocess.PIPE)
+                                'cat > /tools/dhcp3/dhcpd.conf.generated;'
+                                'sudo /etc/init.d/isc-dhcp-server restart'],
+                               shell=False, stdin=subprocess.PIPE)
 #   print "\n".join(DHCP)+"\n"
-    process.communicate("\n".join(DHCP)+"\n")
-
-
-'''
-i=2
-for mac, name, ipend in [("18:a9:05:64:19:aa", "mega6", 16), ("00:1e:0b:e9:79:1e", "blade1", 21), ("00:22:64:9c:fd:34", "blade2", 22), ("00:1e:0b:ec:65:46", "blade3", 23), ("b4:b5:2f:61:d2:5a", "cloud-man", 1)]:
-    h1 = models.Host(hostname= name, vlan=models.Vlan.objects.get(vid=3), mac=mac, ipv4="10.3.1.%d" % ipend, ipv6="2001:738:2001:4031:3:1:%d:0" % ipend, owner=auth.models.User.objects.get(username="bd"))
-    try:
-        h1.save()
-        h1.groups.add(models.Group.objects.get(name="netezhet manbol"))
-        h1.save()
-#       i = i + 1
-    except:
-        print "nemok %s" % name
-'''
-
+    process.communicate("\n".join(DHCP) + "\n")
diff --git a/circle/firewall/locale/hu/LC_MESSAGES/django.po b/circle/firewall/locale/hu/LC_MESSAGES/django.po
new file mode 100644
index 0000000..a49222a
--- /dev/null
+++ b/circle/firewall/locale/hu/LC_MESSAGES/django.po
@@ -0,0 +1,572 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-08-21 12:29+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+#: fields.py:19
+msgid "Enter a valid MAC address."
+msgstr ""
+
+#: fields.py:46
+#, python-format
+msgid "%s - only letters, numbers, underscores and hyphens are allowed!"
+msgstr ""
+
+#: fields.py:58
+#, python-format
+msgid "%s - invalid domain name"
+msgstr ""
+
+#: fields.py:80
+#, python-format
+msgid "%s - not an IPv4 address"
+msgstr "%s - nem egy IPv4 cím"
+
+#: fields.py:86
+#, python-format
+msgid "%s - not an IPv6 address"
+msgstr ""
+
+#: fields.py:97
+msgid "Bad MX address format. Should be: <priority>:<hostname>"
+msgstr ""
+
+#: models.py:33
+msgid "direction"
+msgstr ""
+
+#: models.py:34
+msgid "If the rule matches egress or ingress packets."
+msgstr ""
+
+#: models.py:36 models.py:222 models.py:284 models.py:307 models.py:366
+#: models.py:591 models.py:615
+msgid "description"
+msgstr ""
+
+#: models.py:37
+msgid "Why is the rule needed, or how does it work."
+msgstr ""
+
+#: models.py:40
+msgid "foreign network"
+msgstr ""
+
+#: models.py:41
+msgid ""
+"The group of vlans the matching packet goes to (direction out) or from (in)."
+msgstr ""
+
+#: models.py:45
+msgid "dest. port"
+msgstr ""
+
+#: models.py:47
+msgid "Destination port number of packets that match."
+msgstr ""
+
+#: models.py:49
+msgid "source port"
+msgstr ""
+
+#: models.py:51
+msgid "Source port number of packets that match."
+msgstr ""
+
+#: models.py:53
+msgid "protocol"
+msgstr ""
+
+#: models.py:54
+msgid "Protocol of packets that match."
+msgstr ""
+
+#: models.py:55
+msgid "extra arguments"
+msgstr ""
+
+#: models.py:56
+msgid "Additional arguments passed literally to the iptables-rule."
+msgstr ""
+
+#: models.py:58
+msgid "accept"
+msgstr ""
+
+#: models.py:59
+msgid "Accept the matching packets (or deny if not checked)."
+msgstr ""
+
+#: models.py:62 models.py:253 models.py:287 models.py:310 models.py:377
+#: models.py:585 models.py:614
+msgid "owner"
+msgstr ""
+
+#: models.py:63
+msgid "The user responsible for this rule."
+msgstr ""
+
+#: models.py:65
+msgid "Rule type"
+msgstr ""
+
+#: models.py:67
+msgid "The type of entity the rule belongs to."
+msgstr ""
+
+#: models.py:69
+msgid "NAT"
+msgstr ""
+
+#: models.py:70
+msgid "If network address translation shoud be done."
+msgstr ""
+
+#: models.py:74
+msgid "Rewrite destination port number to."
+msgstr ""
+
+#: models.py:79 models.py:251 models.py:289 models.py:312 models.py:385
+msgid "created at"
+msgstr ""
+
+#: models.py:82 models.py:255 models.py:291 models.py:314 models.py:387
+msgid "modified at"
+msgstr ""
+
+#: models.py:85 models.py:374
+msgid "vlan"
+msgstr ""
+
+#: models.py:86
+msgid "Vlan the rule applies to (if type is vlan)."
+msgstr ""
+
+#: models.py:90
+msgid "vlan group"
+msgstr ""
+
+#: models.py:91
+msgid "Group of vlans the rule applies to (if type is vlan)."
+msgstr ""
+
+#: models.py:94 models.py:608 models.py:730
+msgid "host"
+msgstr ""
+
+#: models.py:95
+msgid "Host the rule applies to (if type is host)."
+msgstr ""
+
+#: models.py:98
+msgid "host group"
+msgstr ""
+
+#: models.py:99
+msgid "Group of hosts the rule applies to (if type is host)."
+msgstr ""
+
+#: models.py:102
+msgid "firewall"
+msgstr ""
+
+#: models.py:103
+msgid "Firewall the rule applies to (if type is firewall)."
+msgstr ""
+
+#: models.py:115
+msgid "Only one field can be selected."
+msgstr ""
+
+#: models.py:136
+msgid "rule"
+msgstr ""
+
+#: models.py:137
+msgid "rules"
+msgstr ""
+
+#: models.py:163
+msgid "VID"
+msgstr ""
+
+#: models.py:164
+msgid "The vlan ID of the subnet."
+msgstr ""
+
+#: models.py:169
+msgid "Name"
+msgstr ""
+
+#: models.py:170
+msgid "The short name of the subnet."
+msgstr ""
+
+#: models.py:173
+msgid "IPv4 prefix length"
+msgstr ""
+
+#: models.py:174
+msgid "The prefix length of the IPv4 subnet."
+msgstr ""
+
+#: models.py:176
+msgid "IPv6 prefix length"
+msgstr ""
+
+#: models.py:177
+msgid "The prefix length of the IPv6 subnet."
+msgstr ""
+
+#: models.py:179
+msgid "interface"
+msgstr ""
+
+#: models.py:180
+msgid ""
+"The name of network interface the gateway should serve this network on. For "
+"example vlan0004 or eth2."
+msgstr ""
+
+#: models.py:184
+msgid "IPv4 network"
+msgstr ""
+
+#: models.py:185
+msgid "The network address of the IPv4 subnet."
+msgstr ""
+
+#: models.py:188
+msgid "IPv6 network"
+msgstr ""
+
+#: models.py:189
+msgid "The network address of the IPv6 subnet."
+msgstr ""
+
+#: models.py:192 models.py:346
+msgid "IPv4 address"
+msgstr ""
+
+#: models.py:194
+msgid ""
+"The IPv4 address of the gateway. Recommended value is the last valid address "
+"of the subnet, for example 10.4.255.254 for 10.4.0.0/16."
+msgstr ""
+
+#: models.py:201 models.py:357
+msgid "IPv6 address"
+msgstr ""
+
+#: models.py:203
+msgid "The IPv6 address of the gateway."
+msgstr ""
+
+#: models.py:207
+msgid "NAT IP address"
+msgstr ""
+
+#: models.py:209
+msgid ""
+"Common IPv4 address used for address translation of connections to the "
+"networks selected below (typically to the internet)."
+msgstr ""
+
+#: models.py:215
+msgid "NAT to"
+msgstr ""
+
+#: models.py:217
+msgid ""
+"Connections to these networks should be network address translated, i.e. "
+"their source address is rewritten to the value of NAT IP address."
+msgstr ""
+
+#: models.py:224
+msgid "Description of the goals and elements of the vlan network."
+msgstr ""
+
+#: models.py:226
+msgid "comment"
+msgstr ""
+
+#: models.py:228
+msgid "Notes, comments about the network"
+msgstr ""
+
+#: models.py:229
+msgid "domain name"
+msgstr ""
+
+#: models.py:230
+msgid "Domain name of the members of this network."
+msgstr ""
+
+#: models.py:234
+msgid "reverse domain"
+msgstr ""
+
+#: models.py:235
+#, python-format
+msgid ""
+"Template of the IPv4 reverse domain name that should be generated for each "
+"host. The template should contain four tokens: \"%(a)d\", \"%(b)d\", \"%(c)d"
+"\", and \"%(d)d\", representing the four bytes of the address, respectively, "
+"in decimal notation. For example, the template for the standard reverse "
+"address is: \"%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa\"."
+msgstr ""
+
+#: models.py:243
+msgid "DHCP pool"
+msgstr ""
+
+#: models.py:245
+msgid ""
+"The address range of the DHCP pool: empty for no DHCP service, \"manual\" "
+"for no DHCP pool, or the first and last address of the range separated by a "
+"space."
+msgstr ""
+
+#: models.py:278 models.py:305 models.py:576 models.py:584 models.py:605
+msgid "name"
+msgstr ""
+
+#: models.py:279 models.py:306
+msgid "The name of the group."
+msgstr ""
+
+#: models.py:281
+msgid "vlans"
+msgstr ""
+
+#: models.py:282
+msgid "The vlans which are members of the group."
+msgstr ""
+
+#: models.py:285 models.py:308
+msgid "Description of the group."
+msgstr ""
+
+#: models.py:330
+msgid "hostname"
+msgstr ""
+
+#: models.py:331
+msgid "The alphanumeric hostname of the host, the first part of the FQDN."
+msgstr ""
+
+#: models.py:336
+msgid "reverse"
+msgstr ""
+
+#: models.py:337
+msgid ""
+"The fully qualified reverse hostname of the host, if different than hostname."
+"domain."
+msgstr ""
+
+#: models.py:341
+msgid "MAC address"
+msgstr ""
+
+#: models.py:342
+msgid ""
+"The MAC (Ethernet) address of the network interface. For example: 99:AA:BB:"
+"CC:DD:EE."
+msgstr ""
+
+#: models.py:348
+msgid "The real IPv4 address of the host, for example 10.5.1.34."
+msgstr ""
+
+#: models.py:352
+msgid "WAN IPv4 address"
+msgstr ""
+
+#: models.py:353
+msgid ""
+"The public IPv4 address of the host on the wide area network, if different."
+msgstr ""
+
+#: models.py:359
+msgid "The global IPv6 address of the host, for example 2001:500:88:200::10."
+msgstr ""
+
+#: models.py:362
+msgid "shared IP"
+msgstr ""
+
+#: models.py:364
+msgid "If the given WAN IPv4 address is used by multiple hosts."
+msgstr ""
+
+#: models.py:367
+msgid "What is this host for, what kind of machine is it."
+msgstr ""
+
+#: models.py:370
+msgid "Notes"
+msgstr ""
+
+#: models.py:371
+msgid "location"
+msgstr ""
+
+#: models.py:373
+msgid "The physical location of the machine."
+msgstr ""
+
+#: models.py:376
+msgid "Vlan network that the host is part of."
+msgstr ""
+
+#: models.py:379
+msgid "The person responsible for this host."
+msgstr ""
+
+#: models.py:381
+msgid "groups"
+msgstr ""
+
+#: models.py:383
+msgid "Host groups the machine is part of."
+msgstr ""
+
+#: models.py:398
+msgid "If shared_ip has been checked, pub_ipv4 has to be unique."
+msgstr ""
+
+#: models.py:401
+msgid "You can't use another host's NAT'd address as your own IPv4."
+msgstr ""
+
+#: models.py:454
+#, python-format
+msgid "All %s ports are already in use."
+msgstr ""
+
+#: models.py:471
+#, python-format
+msgid "Port %(proto)s %(public)s is already in use."
+msgstr ""
+
+#: models.py:479
+msgid "Only ports above 1024 can be used."
+msgstr ""
+
+#: models.py:587 models.py:617 models.py:741
+msgid "created_at"
+msgstr ""
+
+#: models.py:589 models.py:619 models.py:743
+msgid "modified_at"
+msgstr ""
+
+#: models.py:590 models.py:613
+msgid "ttl"
+msgstr ""
+
+#: models.py:606
+msgid "domain"
+msgstr ""
+
+#: models.py:610 models.py:738
+msgid "type"
+msgstr ""
+
+#: models.py:612
+msgid "address"
+msgstr ""
+
+#: models.py:627
+msgid "(empty)"
+msgstr ""
+
+#: models.py:638
+msgid "Can't specify address for A or AAAA records if host is set!"
+msgstr ""
+
+#: models.py:641
+msgid "Can't specify name for A or AAAA records if host is set!"
+msgstr ""
+
+#: models.py:645
+msgid "Name must be specified for CNAME records if host is set!"
+msgstr ""
+
+#: models.py:648
+msgid "Can't specify address for CNAME records if host is set!"
+msgstr ""
+
+#: models.py:656
+msgid "Address must be specified!"
+msgstr ""
+
+#: models.py:666
+msgid "Unknown record type."
+msgstr ""
+
+#: models.py:731
+msgid "reason"
+msgstr ""
+
+#: models.py:733
+msgid "short message"
+msgstr ""
+
+#: views.py:26
+#, python-format
+msgid ""
+"Dear %s, you've signed in as administrator!<br />Reloading in 10 seconds..."
+msgstr ""
+
+#: views.py:30
+#, python-format
+msgid "Dear %s, you've signed in!"
+msgstr ""
+
+#: views.py:32
+msgid "Dear anonymous, you've not signed in yet!"
+msgstr ""
+
+#: views.py:43
+msgid "Wrong password."
+msgstr ""
+
+#: views.py:77 views.py:118
+msgid "OK"
+msgstr ""
+
+#: views.py:80
+msgid "Only vm-net and war can be used."
+msgstr ""
+
+#: views.py:111
+msgid "Unknown command."
+msgstr ""
+
+#: views.py:114
+#, python-format
+msgid ""
+"Something went wrong!\n"
+"%s\n"
+msgstr ""
+
+#: views.py:116
+msgid "Something went wrong!\n"
+msgstr ""
diff --git a/circle/firewall/models.py b/circle/firewall/models.py
index 1508d9e..864e11c 100644
--- a/circle/firewall/models.py
+++ b/circle/firewall/models.py
@@ -1,160 +1,390 @@
-# -*- coding: utf8 -*-
+# -*- coding: utf-8 -*-
 
 from django.contrib.auth.models import User
 from django.db import models
-from django.forms import fields, ValidationError
+from django.forms import ValidationError
 from django.utils.translation import ugettext_lazy as _
-from firewall.fields import *
-from south.modelsinspector import add_introspection_rules
+from firewall.fields import (MACAddressField, val_alfanum, val_reverse_domain,
+                             val_domain, val_ipv4, val_ipv6, val_mx,
+                             ipv4_2_ipv6)
 from django.core.validators import MinValueValidator, MaxValueValidator
 import django.conf
 from django.db.models.signals import post_save
-import re
 import random
 
 settings = django.conf.settings.FIREWALL_SETTINGS
+
+
 class Rule(models.Model):
-    """
-    Common firewall rule
 
-    Rule can be applied to: Host, Firewall, Vlan
+    """
+    A rule of a packet filter, changing the behavior of a host, vlan or
+    firewall.
 
+    Some rules accept or deny packets matching some criteria.
+    Others set address translation or other free-form iptables parameters.
     """
     CHOICES_type = (('host', 'host'), ('firewall', 'firewall'),
-            ('vlan', 'vlan'))
+                   ('vlan', 'vlan'))
     CHOICES_proto = (('tcp', 'tcp'), ('udp', 'udp'), ('icmp', 'icmp'))
     CHOICES_dir = (('0', 'out'), ('1', 'in'))
 
     direction = models.CharField(max_length=1, choices=CHOICES_dir,
-            blank=False)
-    description = models.TextField(blank=True)
-    foreign_network = models.ForeignKey('VlanGroup',
-            related_name="ForeignRules")
-    dport = models.IntegerField(blank=True, null=True,
-            validators=[MinValueValidator(1), MaxValueValidator(65535)])
-    sport = models.IntegerField(blank=True, null=True,
-            validators=[MinValueValidator(1), MaxValueValidator(65535)])
+                                 blank=False, verbose_name=_("direction"),
+                                 help_text=_("If the rule matches egress "
+                                             "or ingress packets."))
+    description = models.TextField(blank=True, verbose_name=_('description'),
+                                   help_text=_("Why is the rule needed, "
+                                               "or how does it work."))
+    foreign_network = models.ForeignKey(
+        'VlanGroup', verbose_name=_("foreign network"),
+        help_text=_("The group of vlans the matching packet goes to "
+                    "(direction out) or from (in)."),
+        related_name="ForeignRules")
+    dport = models.IntegerField(
+        blank=True, null=True, verbose_name=_("dest. port"),
+        validators=[MinValueValidator(1), MaxValueValidator(65535)],
+        help_text=_("Destination port number of packets that match."))
+    sport = models.IntegerField(
+        blank=True, null=True, verbose_name=_("source port"),
+        validators=[MinValueValidator(1), MaxValueValidator(65535)],
+        help_text=_("Source port number of packets that match."))
     proto = models.CharField(max_length=10, choices=CHOICES_proto,
-            blank=True, null=True)
-    extra = models.TextField(blank=True)
-    accept = models.BooleanField(default=False)
-    owner = models.ForeignKey(User, blank=True, null=True)
-    r_type = models.CharField(max_length=10, choices=CHOICES_type)
-    nat = models.BooleanField(default=False)
+                             blank=True, null=True, verbose_name=_("protocol"),
+                             help_text=_("Protocol of packets that match."))
+    extra = models.TextField(blank=True, verbose_name=_("extra arguments"),
+                             help_text=_("Additional arguments passed "
+                                         "literally to the iptables-rule."))
+    accept = models.BooleanField(default=False, verbose_name=_("accept"),
+                                 help_text=_("Accept the matching packets "
+                                             "(or deny if not checked)."))
+    owner = models.ForeignKey(User, blank=True, null=True,
+                              verbose_name=_("owner"),
+                              help_text=_("The user responsible for "
+                                          "this rule."))
+    r_type = models.CharField(max_length=10, verbose_name=_("Rule type"),
+                              choices=CHOICES_type,
+                              help_text=_("The type of entity the rule "
+                                          "belongs to."))
+    nat = models.BooleanField(default=False, verbose_name=_("NAT"),
+                              help_text=_("If network address translation "
+                                          "shoud be done."))
     nat_dport = models.IntegerField(blank=True, null=True,
-            validators=[MinValueValidator(1), MaxValueValidator(65535)])
-    created_at = models.DateTimeField(auto_now_add=True)
-    modified_at = models.DateTimeField(auto_now=True)
+                                    help_text=_(
+                                        "Rewrite destination port number to."),
+                                    validators=[MinValueValidator(1),
+                                                MaxValueValidator(65535)])
+    created_at = models.DateTimeField(
+        auto_now_add=True,
+        verbose_name=_("created at"))
+    modified_at = models.DateTimeField(
+        auto_now=True,
+        verbose_name=_("modified at"))
 
     vlan = models.ForeignKey('Vlan', related_name="rules", blank=True,
-            null=True)
+                             null=True, verbose_name=_("vlan"),
+                             help_text=_("Vlan the rule applies to "
+                                         "(if type is vlan)."))
     vlangroup = models.ForeignKey('VlanGroup', related_name="rules",
-            blank=True, null=True)
+                                  blank=True, null=True, verbose_name=_(
+                                      "vlan group"),
+                                  help_text=_("Group of vlans the rule "
+                                              "applies to (if type is vlan)."))
     host = models.ForeignKey('Host', related_name="rules", blank=True,
-            null=True)
-    hostgroup = models.ForeignKey('Group', related_name="rules",
-            blank=True, null=True)
-    firewall = models.ForeignKey('Firewall', related_name="rules",
-            blank=True, null=True)
+                             verbose_name=_('host'), null=True,
+                             help_text=_("Host the rule applies to "
+                                         "(if type is host)."))
+    hostgroup = models.ForeignKey(
+        'Group', related_name="rules", verbose_name=_("host group"),
+        blank=True, null=True, help_text=_("Group of hosts the rule applies "
+                                           "to (if type is host)."))
+    firewall = models.ForeignKey(
+        'Firewall', related_name="rules", verbose_name=_("firewall"),
+                                 help_text=_("Firewall the rule applies to "
+                                             "(if type is firewall)."),
+        blank=True, null=True)
 
     def __unicode__(self):
         return self.desc()
 
     def clean(self):
         fields = [self.vlan, self.vlangroup, self.host, self.hostgroup,
-                self.firewall]
+                  self.firewall]
         selected_fields = [field for field in fields if field]
         if len(selected_fields) > 1:
             raise ValidationError(_('Only one field can be selected.'))
 
     def desc(self):
+        """Return a short string representation of the current rule.
+        """
         return u'[%(type)s] %(src)s ▸ %(dst)s %(para)s %(desc)s' % {
             'type': self.r_type,
             'src': (unicode(self.foreign_network) if self.direction == '1'
-                else self.r_type),
+                    else self.r_type),
             'dst': (self.r_type if self.direction == '1'
-                else unicode(self.foreign_network)),
+                    else unicode(self.foreign_network)),
             'para': ((("proto=%s " % self.proto) if self.proto else '') +
                      (("sport=%s " % self.sport) if self.sport else '') +
                      (("dport=%s " % self.dport) if self.dport else '')),
             'desc': self.description}
 
+    @models.permalink
+    def get_absolute_url(self):
+        return ('network.rule', None, {'pk': self.pk})
+
+    class Meta:
+        verbose_name = _("rule")
+        verbose_name_plural = _("rules")
+        ordering = (
+            'r_type',
+            'direction',
+            'proto',
+            'sport',
+            'dport',
+            'nat_dport',
+            'host',
+        )
+
+
 class Vlan(models.Model):
-    vid = models.IntegerField(unique=True)
-    name = models.CharField(max_length=20, unique=True,
-            validators=[val_alfanum])
-    prefix4 = models.IntegerField(default=16)
-    prefix6 = models.IntegerField(default=80)
-    interface = models.CharField(max_length=20, unique=True)
-    net4 = models.GenericIPAddressField(protocol='ipv4', unique=True)
-    net6 = models.GenericIPAddressField(protocol='ipv6', unique=True)
-    ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True)
-    ipv6 = models.GenericIPAddressField(protocol='ipv6', unique=True)
+
+    """
+    A vlan of the network,
+
+    Networks controlled by this framework are split into separated subnets.
+    These networks are izolated by the vlan (virtual lan) technology, which is
+    commonly used by managed network switches to partition the network.
+
+    Each vlan network has a unique identifier, a name, a unique IPv4 and IPv6
+    range. The gateway also has an IP address in each range.
+    """
+
+    vid = models.IntegerField(unique=True,
+                              verbose_name=_('VID'),
+                              help_text=_('The vlan ID of the subnet.'),
+                              validators=[MinValueValidator(1),
+                                          MaxValueValidator(4095)])
+    name = models.CharField(max_length=20,
+                            unique=True,
+                            verbose_name=_('Name'),
+                            help_text=_('The short name of the subnet.'),
+                            validators=[val_alfanum])
+    prefix4 = models.IntegerField(
+        default=16, verbose_name=_('IPv4 prefix length'),
+        help_text=_('The prefix length of the IPv4 subnet.'))
+    prefix6 = models.IntegerField(
+        default=80, verbose_name=_('IPv6 prefix length'),
+        help_text=_('The prefix length of the IPv6 subnet.'))
+    interface = models.CharField(max_length=20, unique=True,
+                                 verbose_name=_('interface'), help_text=_(
+                                     'The name of network interface the '
+                                     'gateway should serve this network on. '
+                                     'For example vlan0004 or eth2.'))
+    net4 = models.GenericIPAddressField(protocol='ipv4', unique=True,
+                                        verbose_name=_('IPv4 network'),
+                                        help_text=_('The network address of '
+                                                    'the IPv4 subnet.'))
+    net6 = models.GenericIPAddressField(protocol='ipv6', unique=True,
+                                        verbose_name=_('IPv6 network'),
+                                        help_text=_('The network address of '
+                                                    'the IPv6 subnet.'))
+    ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True,
+                                        verbose_name=_('IPv4 address'),
+                                        help_text=_(
+                                            'The IPv4 address of the gateway. '
+                                            'Recommended value is the last '
+                                            'valid address of the subnet, '
+                                            'for example '
+                                            '10.4.255.254 for 10.4.0.0/16.'))
+    ipv6 = models.GenericIPAddressField(protocol='ipv6',
+                                        unique=True,
+                                        verbose_name=_('IPv6 address'),
+                                        help_text=_(
+                                            'The IPv6 address of the '
+                                            'gateway.'))
     snat_ip = models.GenericIPAddressField(protocol='ipv4', blank=True,
-            null=True)
+                                           null=True,
+                                           verbose_name=_('NAT IP address'),
+                                           help_text=_(
+                                               'Common IPv4 address used for '
+                                               'address translation of '
+                                               'connections to the networks '
+                                               'selected below '
+                                               '(typically to the internet).'))
     snat_to = models.ManyToManyField('self', symmetrical=False, blank=True,
-            null=True)
-    description = models.TextField(blank=True)
-    comment = models.TextField(blank=True)
-    domain = models.ForeignKey('Domain')
-    reverse_domain = models.TextField(validators=[val_reverse_domain])
-    dhcp_pool = models.TextField(blank=True)
-    created_at = models.DateTimeField(auto_now_add=True)
-    owner = models.ForeignKey(User, blank=True, null=True)
-    modified_at = models.DateTimeField(auto_now=True)
+                                     null=True, verbose_name=_('NAT to'),
+                                     help_text=_(
+                                         'Connections to these networks '
+                                         'should be network address '
+                                         'translated, i.e. their source '
+                                         'address is rewritten to the value '
+                                         'of NAT IP address.'))
+    description = models.TextField(blank=True, verbose_name=_('description'),
+                                   help_text=_(
+                                       'Description of the goals and elements '
+                                       'of the vlan network.'))
+    comment = models.TextField(blank=True, verbose_name=_('comment'),
+                               help_text=_(
+                                   'Notes, comments about the network'))
+    domain = models.ForeignKey('Domain', verbose_name=_('domain name'),
+                               help_text=_('Domain name of the members of '
+                                           'this network.'))
+    reverse_domain = models.TextField(
+        validators=[val_reverse_domain],
+        verbose_name=_('reverse domain'),
+        help_text=_('Template of the IPv4 reverse domain name that '
+                    'should be generated for each host. The template '
+                    'should contain four tokens: "%(a)d", "%(b)d", '
+                    '"%(c)d", and "%(d)d", representing the four bytes '
+                    'of the address, respectively, in decimal notation. '
+                    'For example, the template for the standard reverse '
+                    'address is: "%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa".'),
+        default="%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa")
+    dhcp_pool = models.TextField(blank=True, verbose_name=_('DHCP pool'),
+                                 help_text=_(
+                                     'The address range of the DHCP pool: '
+                                     'empty for no DHCP service, "manual" for '
+                                     'no DHCP pool, or the first and last '
+                                     'address of the range separated by a '
+                                     'space.'))
+    created_at = models.DateTimeField(auto_now_add=True,
+                                      verbose_name=_('created at'))
+    owner = models.ForeignKey(User, blank=True, null=True,
+                              verbose_name=_('owner'))
+    modified_at = models.DateTimeField(auto_now=True,
+                                       verbose_name=_('modified at'))
 
     def __unicode__(self):
         return self.name
 
     def net_ipv6(self):
+        """String representation of selected IPv6 network."""
         return self.net6 + "/" + unicode(self.prefix6)
 
     def net_ipv4(self):
+        """String representation of selected IPv4 network."""
         return self.net4 + "/" + unicode(self.prefix4)
 
+    @models.permalink
+    def get_absolute_url(self):
+        return ('network.vlan', None, {'vid': self.vid})
+
+
 class VlanGroup(models.Model):
-    name = models.CharField(max_length=20, unique=True)
+    """
+    A group of Vlans.
+    """
+
+    name = models.CharField(max_length=20, unique=True, verbose_name=_('name'),
+                            help_text=_('The name of the group.'))
     vlans = models.ManyToManyField('Vlan', symmetrical=False, blank=True,
-            null=True)
-    description = models.TextField(blank=True)
-    owner = models.ForeignKey(User, blank=True, null=True)
-    created_at = models.DateTimeField(auto_now_add=True)
-    modified_at = models.DateTimeField(auto_now=True)
+                                   null=True, verbose_name=_('vlans'),
+                                   help_text=_('The vlans which are members '
+                                               'of the group.'))
+    description = models.TextField(blank=True, verbose_name=_('description'),
+                                   help_text=_('Description of the group.'))
+    owner = models.ForeignKey(User, blank=True, null=True,
+                              verbose_name=_('owner'))
+    created_at = models.DateTimeField(auto_now_add=True,
+                                      verbose_name=_('created at'))
+    modified_at = models.DateTimeField(auto_now=True,
+                                       verbose_name=_('modified at'))
 
     def __unicode__(self):
         return self.name
 
+    @models.permalink
+    def get_absolute_url(self):
+        return ('network.vlan_group', None, {'pk': self.pk})
+
+
 class Group(models.Model):
-    name = models.CharField(max_length=20, unique=True)
-    description = models.TextField(blank=True)
-    owner = models.ForeignKey(User, blank=True, null=True)
-    created_at = models.DateTimeField(auto_now_add=True)
-    modified_at = models.DateTimeField(auto_now=True)
+    """
+    A group of hosts.
+    """
+    name = models.CharField(max_length=20, unique=True, verbose_name=_('name'),
+                            help_text=_('The name of the group.'))
+    description = models.TextField(blank=True, verbose_name=_('description'),
+                                   help_text=_('Description of the group.'))
+    owner = models.ForeignKey(User, blank=True, null=True,
+                              verbose_name=_('owner'))
+    created_at = models.DateTimeField(auto_now_add=True,
+                                      verbose_name=_('created at'))
+    modified_at = models.DateTimeField(auto_now=True,
+                                       verbose_name=_('modified at'))
 
     def __unicode__(self):
         return self.name
 
+    @models.permalink
+    def get_absolute_url(self):
+        return ('network.group', None, {'pk': self.pk})
+
+
 class Host(models.Model):
+    """
+    A host of the network.
+    """
+
     hostname = models.CharField(max_length=40, unique=True,
-            validators=[val_alfanum])
+                                verbose_name=_('hostname'),
+                                help_text=_('The alphanumeric hostname of '
+                                            'the host, the first part of '
+                                            'the FQDN.'),
+                                validators=[val_alfanum])
     reverse = models.CharField(max_length=40, validators=[val_domain],
-            blank=True, null=True)
-    mac = MACAddressField(unique=True)
-    ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True)
-    pub_ipv4 = models.GenericIPAddressField(protocol='ipv4', blank=True,
-            null=True)
+                               verbose_name=_('reverse'),
+                               help_text=_('The fully qualified reverse '
+                                           'hostname of the host, if '
+                                           'different than hostname.domain.'),
+                               blank=True, null=True)
+    mac = MACAddressField(unique=True, verbose_name=_('MAC address'),
+                          help_text=_('The MAC (Ethernet) address of the '
+                                      'network interface. For example: '
+                                      '99:AA:BB:CC:DD:EE.'))
+    ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True,
+                                        verbose_name=_('IPv4 address'),
+                                        help_text=_(
+                                            'The real IPv4 address of the '
+                                            'host, for example 10.5.1.34.'))
+    pub_ipv4 = models.GenericIPAddressField(
+        protocol='ipv4', blank=True, null=True,
+        verbose_name=_('WAN IPv4 address'),
+        help_text=_('The public IPv4 address of the host on the wide '
+                    'area network, if different.'))
     ipv6 = models.GenericIPAddressField(protocol='ipv6', unique=True,
-            blank=True, null=True)
-    shared_ip = models.BooleanField(default=False)
-    description = models.TextField(blank=True)
-    comment = models.TextField(blank=True)
-    location = models.TextField(blank=True)
-    vlan = models.ForeignKey('Vlan')
-    owner = models.ForeignKey(User)
+                                        blank=True, null=True,
+                                        verbose_name=_('IPv6 address'),
+                                        help_text=_(
+                                            'The global IPv6 address of the '
+                                            'host, for example '
+                                            '2001:500:88:200::10.'))
+    shared_ip = models.BooleanField(default=False, verbose_name=_('shared IP'),
+                                    help_text=_(
+                                        'If the given WAN IPv4 address is '
+                                        'used by multiple hosts.'))
+    description = models.TextField(blank=True, verbose_name=_('description'),
+                                   help_text=_('What is this host for, what '
+                                               'kind of machine is it.'))
+    comment = models.TextField(blank=True,
+                               verbose_name=_('Notes'))
+    location = models.TextField(blank=True, verbose_name=_('location'),
+                                help_text=_(
+                                    'The physical location of the machine.'))
+    vlan = models.ForeignKey('Vlan', verbose_name=_('vlan'),
+                             help_text=_(
+                                 'Vlan network that the host is part of.'))
+    owner = models.ForeignKey(User, verbose_name=_('owner'),
+                              help_text=_(
+                                  'The person responsible for this host.'))
     groups = models.ManyToManyField('Group', symmetrical=False, blank=True,
-            null=True)
-    created_at = models.DateTimeField(auto_now_add=True)
-    modified_at = models.DateTimeField(auto_now=True)
+                                    null=True, verbose_name=_('groups'),
+                                    help_text=_(
+                                        'Host groups the machine is part of.'))
+    created_at = models.DateTimeField(auto_now_add=True,
+                                      verbose_name=_('created at'))
+    modified_at = models.DateTimeField(auto_now=True,
+                                       verbose_name=_('modified at'))
 
     def __unicode__(self):
         return self.hostname
@@ -166,89 +396,144 @@ class Host(models.Model):
         if (not self.shared_ip and self.pub_ipv4 and Host.objects.
                 exclude(id=self.id).filter(pub_ipv4=self.pub_ipv4)):
             raise ValidationError(_("If shared_ip has been checked, "
-                "pub_ipv4 has to be unique."))
+                                    "pub_ipv4 has to be unique."))
         if Host.objects.exclude(id=self.id).filter(pub_ipv4=self.ipv4):
             raise ValidationError(_("You can't use another host's NAT'd "
-                "address as your own IPv4."))
+                                    "address as your own IPv4."))
         self.full_clean()
         super(Host, self).save(*args, **kwargs)
         if not id:
             Record(domain=self.vlan.domain, host=self, type='A',
-                    owner=self.owner).save()
+                   owner=self.owner).save()
             if self.ipv6:
                 Record(domain=self.vlan.domain, host=self, type='AAAA',
-                        owner=self.owner).save()
+                       owner=self.owner).save()
 
     def enable_net(self):
         self.groups.add(Group.objects.get(name="netezhet"))
 
-    def add_port(self, proto, public=None, private=None):
-        proto = "tcp" if proto == "tcp" else "udp"
-        if self.shared_ip:
-            used_ports = Rule.objects.filter(host__pub_ipv4=self.pub_ipv4,
-                    nat=True, proto=proto).values_list('dport', flat=True)
-
-            if public is None:
-                public = random.randint(1024, 21000)
-                if public in used_ports:
-                    for i in range(1024, 21000) + range(24000, 65535):
-                        if i not in used_ports:
-                            public = i
-                            break
-                    else:
-                        raise ValidationError(_("Port %s %s is already in use.") %
-                                (proto, public))
+    def _get_ports_used(self, proto):
+        """
+        Gives a list of port numbers used for the public IP address of current
+        host for the given protocol.
 
+        :param proto: The transport protocol of the generated port (tcp|udp).
+        :type proto: str.
+        :returns: list -- list of int port numbers used.
+        """
+        if self.shared_ip:
+            ports = Rule.objects.filter(host__pub_ipv4=self.pub_ipv4,
+                                        nat=True, proto=proto)
+        else:
+            ports = self.rules.filter(proto=proto, )
+        return ports.values_list('dport', flat=True)
+
+    def _get_random_port(self, proto, used_ports=None):
+        """
+        Get a random unused port for given protocol for current host's public
+        IP address.
+
+        :param proto: The transport protocol of the generated port (tcp|udp).
+        :type proto: str.
+        :param used_ports: Optional list of used ports returned by
+                           _get_ports_used.
+        :returns: int -- the generated port number.
+        :raises: ValidationError
+        """
+        if used_ports is None:
+            used_ports = self._get_ports_used(proto)
+
+        public = random.randint(1024, 21000)  # pick a random port
+        if public in used_ports:  # if it's in use, select smallest free one
+            for i in range(1024, 21000) + range(24000, 65535):
+                if i not in used_ports:
+                    public = i
+                    break
             else:
-                if public < 1024:
-                    raise ValidationError(_("Only ports above 1024 can be used."))
-                if public in used_ports:
-                    raise ValidationError(_("Port %s %s is already in use.") %
-                            (proto, public))
+                raise ValidationError(
+                    _("All %s ports are already in use.") % proto)
+
+    def add_port(self, proto, public=None, private=None):
+        """
+        Allow inbound traffic to a port.
+
+        If the host uses a shared IP address, also set up port forwarding.
+
+        :param proto: The transport protocol (tcp|udp).
+        :type proto: str.
+        :param public: Preferred public port number for forwarding (optional).
+        :param private: Port number of host in subject.
+        """
+        assert proto in ('tcp', 'udp', )
+        if public:
+            if public in self._get_ports_used(proto):
+                raise ValidationError(
+                    _("Port %(proto)s %(public)s is already in use.") %
+                    {'proto': proto, 'public': public})
+        else:
+            public = self._get_random_port(proto)
 
+        vg = VlanGroup.objects.get(name=settings["default_vlangroup"])
+        if self.shared_ip:
+            if public < 1024:
+                raise ValidationError(_("Only ports above 1024 can be used."))
             rule = Rule(direction='1', owner=self.owner, dport=public,
-                    proto=proto, nat=True, accept=True, r_type="host",
-                    nat_dport=private, host=self, foreign_network=VlanGroup.
-                        objects.get(name=settings["default_vlangroup"]))
+                        proto=proto, nat=True, accept=True, r_type="host",
+                        nat_dport=private, host=self, foreign_network=vg)
         else:
-            if self.rules.filter(proto=proto, dport=public):
-                raise ValidationError(_("Port %s %s is already in use.") %
-                    (proto, public))
             rule = Rule(direction='1', owner=self.owner, dport=public,
-                    proto=proto, nat=False, accept=True, r_type="host",
-                    host=self, foreign_network=VlanGroup.objects
-                        .get(name=settings["default_vlangroup"]))
+                        proto=proto, nat=False, accept=True, r_type="host",
+                        host=self, foreign_network=vg)
 
         rule.full_clean()
         rule.save()
 
     def del_port(self, proto, private):
+        """
+        Remove rules about inbound traffic to a given port.
+
+        If the host uses a shared IP address, also set up port forwarding.
+
+        :param proto: The transport protocol (tcp|udp).
+        :type proto: str.
+        :param private: Port number of host in subject.
+        """
+
         if self.shared_ip:
             self.rules.filter(owner=self.owner, proto=proto, host=self,
-                nat_dport=private).delete()
+                              nat_dport=private).delete()
         else:
             self.rules.filter(owner=self.owner, proto=proto, host=self,
-                dport=private).delete()
+                              dport=private).delete()
 
     def get_hostname(self, proto):
+        """
+        Get a hostname for public ip address.
+
+        :param proto: The IP version (ipv4|ipv6).
+        :type proto: str.
+        """
+        assert proto in ('ipv6', 'ipv4', )
         try:
             if proto == 'ipv6':
                 res = self.record_set.filter(type='AAAA')
             elif proto == 'ipv4':
                 if self.shared_ip:
                     res = Record.objects.filter(type='A',
-                            address=self.pub_ipv4)
+                                                address=self.pub_ipv4)
                 else:
                     res = self.record_set.filter(type='A')
             return unicode(res[0].get_data()['name'])
         except:
-#            raise
             if self.shared_ip:
                 return self.pub_ipv4
             else:
                 return self.ipv4
 
     def list_ports(self):
+        """
+        Return a list of ports with forwarding rules set.
+        """
         retval = []
         for rule in self.rules.filter(owner=self.owner):
             private = rule.nat_dport if self.shared_ip else rule.dport
@@ -267,7 +552,7 @@ class Host(models.Model):
                     'host': self.get_hostname(proto='ipv4'),
                     'port': public4,
                 }
-            if self.ipv6: # ipv6
+            if self.ipv6:  # ipv6
                 forward['ipv6'] = {
                     'host': self.get_hostname(proto='ipv6'),
                     'port': public6,
@@ -276,40 +561,62 @@ class Host(models.Model):
         return retval
 
     def get_fqdn(self):
+        """
+        Get fully qualified host name of host.
+        """
         return self.hostname + u'.' + unicode(self.vlan.domain)
 
+    @models.permalink
+    def get_absolute_url(self):
+        return ('network.host', None, {'pk': self.pk})
+
 
 class Firewall(models.Model):
-    name = models.CharField(max_length=20, unique=True)
+    name = models.CharField(max_length=20, unique=True,
+                            verbose_name=_('name'))
 
     def __unicode__(self):
         return self.name
 
+
 class Domain(models.Model):
-    name = models.CharField(max_length=40, validators=[val_domain])
-    owner = models.ForeignKey(User)
-    created_at = models.DateTimeField(auto_now_add=True)
-    modified_at = models.DateTimeField(auto_now=True)
-    ttl = models.IntegerField(default=600)
-    description = models.TextField(blank=True)
+    name = models.CharField(max_length=40, validators=[val_domain],
+                            verbose_name=_('name'))
+    owner = models.ForeignKey(User, verbose_name=_('owner'))
+    created_at = models.DateTimeField(auto_now_add=True,
+                                      verbose_name=_('created_at'))
+    modified_at = models.DateTimeField(auto_now=True,
+                                       verbose_name=_('modified_at'))
+    ttl = models.IntegerField(default=600, verbose_name=_('ttl'))
+    description = models.TextField(blank=True, verbose_name=_('description'))
 
     def __unicode__(self):
         return self.name
 
+    @models.permalink
+    def get_absolute_url(self):
+        return ('network.domain', None, {'pk': self.pk})
+
+
 class Record(models.Model):
     CHOICES_type = (('A', 'A'), ('CNAME', 'CNAME'), ('AAAA', 'AAAA'),
-            ('MX', 'MX'), ('NS', 'NS'), ('PTR', 'PTR'), ('TXT', 'TXT'))
+                   ('MX', 'MX'), ('NS', 'NS'), ('PTR', 'PTR'), ('TXT', 'TXT'))
     name = models.CharField(max_length=40, validators=[val_domain],
-            blank=True, null=True)
-    domain = models.ForeignKey('Domain')
-    host = models.ForeignKey('Host', blank=True, null=True)
-    type = models.CharField(max_length=6, choices=CHOICES_type)
-    address = models.CharField(max_length=40, blank=True, null=True)
-    ttl = models.IntegerField(default=600)
-    owner = models.ForeignKey(User)
-    description = models.TextField(blank=True)
-    created_at = models.DateTimeField(auto_now_add=True)
-    modified_at = models.DateTimeField(auto_now=True)
+                            blank=True, null=True, verbose_name=_('name'))
+    domain = models.ForeignKey('Domain', verbose_name=_('domain'))
+    host = models.ForeignKey('Host', blank=True, null=True,
+                             verbose_name=_('host'))
+    type = models.CharField(max_length=6, choices=CHOICES_type,
+                            verbose_name=_('type'))
+    address = models.CharField(max_length=40, blank=True, null=True,
+                               verbose_name=_('address'))
+    ttl = models.IntegerField(default=600, verbose_name=_('ttl'))
+    owner = models.ForeignKey(User, verbose_name=_('owner'))
+    description = models.TextField(blank=True, verbose_name=_('description'))
+    created_at = models.DateTimeField(auto_now_add=True,
+                                      verbose_name=_('created_at'))
+    modified_at = models.DateTimeField(auto_now=True,
+                                       verbose_name=_('modified_at'))
 
     def __unicode__(self):
         return self.desc()
@@ -323,46 +630,54 @@ class Record(models.Model):
         self.full_clean()
         super(Record, self).save(*args, **kwargs)
 
+    def _validate_w_host(self):
+        """Validate a record with host set."""
+        assert self.host
+        if self.type in ['A', 'AAAA']:
+            if self.address:
+                raise ValidationError(_("Can't specify address for A "
+                                        "or AAAA records if host is set!"))
+            if self.name:
+                raise ValidationError(_("Can't specify name for A "
+                                        "or AAAA records if host is set!"))
+        elif self.type == 'CNAME':
+            if not self.name:
+                raise ValidationError(_("Name must be specified for "
+                                        "CNAME records if host is set!"))
+            if self.address:
+                raise ValidationError(_("Can't specify address for "
+                                        "CNAME records if host is set!"))
+
+    def _validate_wo_host(self):
+        """Validate a record without a host set."""
+        assert self.host is None
+
+        if not self.address:
+            raise ValidationError(_("Address must be specified!"))
+        if self.type == 'A':
+            val_ipv4(self.address)
+        elif self.type == 'AAAA':
+            val_ipv6(self.address)
+        elif self.type in ['CNAME', 'NS', 'PTR', 'TXT']:
+            val_domain(self.address)
+        elif self.type == 'MX':
+            val_mx(self.address)
+        else:
+            raise ValidationError(_("Unknown record type."))
+
     def clean(self):
+        """Validate the Record to be saved.
+        """
         if self.name:
             self.name = self.name.rstrip(".")    # remove trailing dots
 
         if self.host:
-            if self.type in ['A', 'AAAA']:
-                if self.address:
-                    raise ValidationError(_("Can't specify address for "
-                        "A or AAAA records if host is set!"))
-                if self.name:
-                    raise ValidationError(_("Can't specify name for "
-                        "A or AAAA records if host is set!"))
-            elif self.type == 'CNAME':
-                if not self.name:
-                    raise ValidationError(_("Name must be specified for "
-                        "CNAME records if host is set!"))
-                if self.address:
-                    raise ValidationError(_("Can't specify address for "
-                        "CNAME records if host is set!"))
-        else:    # if self.host is None
-            if not self.address:
-                raise ValidationError(_("Address must be specified!"))
+            self._validate_w_host()
+        else:
+            self._validate_wo_host()
 
-            if self.type == 'A':
-                val_ipv4(self.address)
-            elif self.type == 'AAAA':
-                val_ipv6(self.address)
-            elif self.type in ['CNAME', 'NS', 'PTR', 'TXT']:
-                val_domain(self.address)
-            elif self.type == 'MX':
-                mx = self.address.split(':', 1)
-                if not (len(mx) == 2 and mx[0].isdigit() and
-                        domain_re.match(mx[1])):
-                    raise ValidationError(_("Bad address format. "
-                        "Should be: <priority>:<hostname>"))
-            else:
-                raise ValidationError(_("Unknown record type."))
-    def get_name(self):
-        return self.__get_name()
-    def __get_name(self):
+    @property
+    def fqdn(self):
         if self.host and self.type != 'MX':
             if self.type in ['A', 'AAAA']:
                 return self.host.get_fqdn()
@@ -390,7 +705,7 @@ class Record(models.Model):
         return self.address
 
     def get_data(self):
-        name = self.__get_name()
+        name = self.fqdn
         address = self.__get_address()
         if self.host and self.type == 'AAAA' and not self.host.ipv6:
             return None
@@ -402,22 +717,43 @@ class Record(models.Model):
                     'ttl': self.ttl,
                     'address': address}
 
+    @models.permalink
+    def get_absolute_url(self):
+        return ('network.record', None, {'pk': self.pk})
+
+
 class Blacklist(models.Model):
-    CHOICES_type = (('permban', 'permanent ban'), ('tempban', 'temporary ban'), ('whitelist', 'whitelist'), ('tempwhite', 'tempwhite'))
+    CHOICES_type = (('permban', 'permanent ban'), ('tempban', 'temporary ban'),
+                    ('whitelist', 'whitelist'), ('tempwhite', 'tempwhite'))
     ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True)
-    host = models.ForeignKey('Host', blank=True, null=True)
-    reason = models.TextField(blank=True)
-    snort_message = models.TextField(blank=True)
-    type = models.CharField(max_length=10, choices=CHOICES_type, default='tempban')
-    created_at = models.DateTimeField(auto_now_add=True)
-    modified_at = models.DateTimeField(auto_now=True)
+    host = models.ForeignKey('Host', blank=True, null=True,
+                             verbose_name=_('host'))
+    reason = models.TextField(blank=True, verbose_name=_('reason'))
+    snort_message = models.TextField(blank=True,
+                                     verbose_name=_('short message'))
+    type = models.CharField(
+        max_length=10,
+        choices=CHOICES_type,
+        default='tempban',
+        verbose_name=_('type')
+    )
+    created_at = models.DateTimeField(auto_now_add=True,
+                                      verbose_name=_('created_at'))
+    modified_at = models.DateTimeField(auto_now=True,
+                                       verbose_name=_('modified_at'))
 
     def save(self, *args, **kwargs):
         self.full_clean()
         super(Blacklist, self).save(*args, **kwargs)
+
     def __unicode__(self):
         return self.ipv4
 
+    @models.permalink
+    def get_absolute_url(self):
+        return ('network.blacklist', None, {'pk': self.pk})
+
+
 def send_task(sender, instance, created, **kwargs):
     from firewall.tasks import ReloadTask
     ReloadTask.apply_async(args=[sender.__name__])
diff --git a/circle/firewall/tasks.py b/circle/firewall/tasks.py
index 7735cbf..fbfd879 100644
--- a/circle/firewall/tasks.py
+++ b/circle/firewall/tasks.py
@@ -1,26 +1,32 @@
 from celery.task import Task, PeriodicTask
 import celery
 from django.core.cache import cache
-import os
-import time
 from firewall.fw import *
 import django.conf
 
 settings = django.conf.settings.FIREWALL_SETTINGS
 
+
 @celery.task
 def reload_dns_task(data):
     pass
+
+
 @celery.task
 def reload_firewall_task(data4, data6):
     pass
+
+
 @celery.task
 def reload_dhcp_task(data):
     pass
+
+
 @celery.task
 def reload_blacklist_task(data):
     pass
 
+
 class Periodic(PeriodicTask):
     run_every = timedelta(seconds=10)
 
@@ -48,10 +54,11 @@ class Periodic(PeriodicTask):
             reload_blacklist_task.delay(list(ipset()))
             print "blacklist ujratoltese kesz"
 
+
 class ReloadTask(Task):
     def run(self, type='Host'):
 
-        if type in ["Host", "Records", "Domain", "Vlan"]:
+        if type in ["Host", "Record", "Domain", "Vlan"]:
             cache.add("dns_lock", "true", 30)
 
         if type == "Host":
@@ -64,4 +71,3 @@ class ReloadTask(Task):
             cache.add("blacklist_lock", "true", 30)
 
         print type
-
diff --git a/circle/firewall/tests.py b/circle/firewall/tests.py
index 5ab4383..c9765c3 100644
--- a/circle/firewall/tests.py
+++ b/circle/firewall/tests.py
@@ -1,14 +1,17 @@
 from django.test import TestCase
 from admin import HostAdmin
 
+
 class MockInstance:
     def __init__(self, groups):
         self.groups = MockGroups(groups)
 
+
 class MockGroup:
     def __init__(self, name):
         self.name = name
 
+
 class MockGroups:
     def __init__(self, groups):
         self.groups = groups
@@ -16,6 +19,7 @@ class MockGroups:
     def all(self):
         return self.groups
 
+
 class HostAdminTestCase(TestCase):
     def test_no_groups(self):
         instance = MockInstance([])
@@ -29,6 +33,6 @@ class HostAdminTestCase(TestCase):
 
     def test_multiple_groups(self):
         instance = MockInstance([MockGroup("alma"),
-            MockGroup("korte"), MockGroup("szilva")])
+                                 MockGroup("korte"), MockGroup("szilva")])
         l = HostAdmin.list_groups(instance)
         self.assertEqual(l, "alma, korte, szilva")
diff --git a/circle/firewall/views.py b/circle/firewall/views.py
index 9b1923a..6fd70b5 100644
--- a/circle/firewall/views.py
+++ b/circle/firewall/views.py
@@ -2,12 +2,10 @@ import base64
 import datetime
 import json
 import re
-import sys
 
 from django.conf import settings
 from django.db import IntegrityError
 from django.http import HttpResponse
-from django.shortcuts import render_to_response
 from django.template.loader import render_to_string
 from django.utils import translation
 from django.utils.timezone import utc
@@ -15,13 +13,13 @@ from django.utils.translation import ugettext_lazy as _
 from django.views.decorators.csrf import csrf_exempt
 from django.views.decorators.http import require_POST
 
-from celery.task.control import inspect
 from tasks import *
 
 from firewall.fw import *
 from firewall.models import *
 from one.tasks import SendMailTask
 
+
 def reload_firewall(request):
     if request.user.is_authenticated():
         if request.user.is_superuser:
@@ -34,36 +32,46 @@ def reload_firewall(request):
         html = _("Dear anonymous, you've not signed in yet!")
     return HttpResponse(html)
 
+
 @csrf_exempt
 @require_POST
 def firewall_api(request):
     try:
-        data=json.loads(base64.b64decode(request.POST["data"]))
+        data = json.loads(base64.b64decode(request.POST["data"]))
         command = request.POST["command"]
         if data["password"] != "bdmegintelrontottaanetet":
             raise Exception(_("Wrong password."))
 
         if command == "blacklist":
             obj, created = Blacklist.objects.get_or_create(ipv4=data["ip"])
-            obj.reason=data["reason"]
-            obj.snort_message=data["snort_message"]
+            obj.reason = data["reason"]
+            obj.snort_message = data["snort_message"]
             if created:
                 try:
-                    obj.host = models.Host.objects.get(ipv4=data["ip"])
+                    obj.host = Host.objects.get(ipv4=data["ip"])
                     user = obj.host.owner
                     lang = user.person_set.all()[0].language
                     translation.activate(lang)
-                    msg = render_to_string('mails/notification-ban-now.txt',
-                            { 'user': user,
-                              'bl': obj,
-                              'instance:': obj.host.instance_set.get(),
-                              'url': settings.CLOUD_URL} )
-                    SendMailTask.delay(to=obj.host.owner.email, subject='[IK Cloud] %s' % obj.host.instance_set.get().name, msg=msg, sender=u'cloud@ik.bme.hu')
-                except (Host.DoesNotExist, ValidationError, IntegrityError, AttributeError):
+                    msg = render_to_string(
+                        'mails/notification-ban-now.txt',
+                        {
+                            'user': user,
+                            'bl': obj,
+                            'instance:': obj.host.instance_set.get(),
+                            'url': settings.CLOUD_URL
+                        })
+                    SendMailTask.delay(
+                        to=obj.host.owner.email,
+                        subject='[IK Cloud] %s' %
+                        obj.host.instance_set.get().name,
+                        msg=msg, sender=u'cloud@ik.bme.hu')
+                except (Host.DoesNotExist, ValidationError,
+                        IntegrityError, AttributeError):
                     pass
-            print obj.modified_at + datetime.timedelta(minutes=5)
-            print datetime.datetime.utcnow().replace(tzinfo=utc)
-            if obj.type == 'tempwhite' and obj.modified_at + datetime.timedelta(minutes=1) < datetime.datetime.utcnow().replace(tzinfo=utc):
+
+            modified = obj.modified_at + datetime.timedelta(minutes=1)
+            now = datetime.dateime.utcnow().replace(tzinfo=utc)
+            if obj.type == 'tempwhite' and modified < now:
                 obj.type = 'tempban'
             obj.save()
             return HttpResponse(unicode(_("OK")))
@@ -76,28 +84,27 @@ def firewall_api(request):
         if command == "create":
             data["owner"] = "opennebula"
             owner = auth.models.User.objects.get(username=data["owner"])
-            host = models.Host(hostname=data["hostname"],
-                    vlan=models.Vlan.objects.get(name=data["vlan"]),
-                    mac=data["mac"], ipv4=data["ip"], owner=owner,
-                    description=data["description"], pub_ipv4=models.
+            host = Host(hostname=data["hostname"],
+                        vlan=Vlan.objects.get(name=data["vlan"]),
+                        mac=data["mac"], ipv4=data["ip"], owner=owner,
+                        description=data["description"], pub_ipv4=
                         Vlan.objects.get(name=data["vlan"]).snat_ip,
-                    shared_ip=True)
+                        shared_ip=True)
             host.full_clean()
             host.save()
 
             host.enable_net()
 
             for p in data["portforward"]:
-                host.add_port(proto=p["proto"],
-                        public=int(p["public_port"]),
-                        private=int(p["private_port"]))
+                host.add_port(proto=p["proto"], public=int(p["public_port"]),
+                              private=int(p["private_port"]))
 
         elif command == "destroy":
             data["owner"] = "opennebula"
             print data["hostname"]
             owner = auth.models.User.objects.get(username=data["owner"])
-            host = models.Host.objects.get(hostname=data["hostname"],
-                    owner=owner)
+            host = Host.objects.get(hostname=data["hostname"],
+                                    owner=owner)
 
             host.delete()
         else:
--
libgit2 0.26.0