diff --git a/actions/firewall b/actions/firewall
new file mode 100755
index 000000000..88a921d2b
--- /dev/null
+++ b/actions/firewall
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+# -*- mode: python -*-
+#
+# This file is part of Plinth.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+"""
+Configuration helper for Plinth firewall inteface
+"""
+
+import argparse
+import os
+import re
+import subprocess
+
+
+def parse_arguments():
+ """Return parsed command line arguments as dictionary"""
+ parser = argparse.ArgumentParser()
+ subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
+
+ # Get installed status
+ subparsers.add_parser('get-installed',
+ help='Get whether firewalld is installed')
+
+ # Get status
+ subparsers.add_parser('get-status', help='Get whether firewalld is running')
+
+ # Get service status
+ subparsers.add_parser('get-enabled-services', help='Get list of enabled services')
+
+ # Add a service
+ add_service = subparsers.add_parser('add-service', help='Add a service')
+ add_service.add_argument('service', help='Name of the service to add')
+
+ # Remove a service status
+ remove_service = subparsers.add_parser('remove-service', help='Remove a service')
+ remove_service.add_argument('service', help='Name of the service to remove')
+
+ return parser.parse_args()
+
+
+def subcommand_get_installed(_):
+ """Print whether firewalld is installed"""
+ with open('/dev/null', 'w') as file_handle:
+ status = subprocess.call(['which', 'firewalld'], stdout=file_handle)
+
+ print 'installed' if not status else 'not installed'
+
+
+def subcommand_get_status(_):
+ """Print status of the firewalld service"""
+ subprocess.call(['firewall-cmd', '--state'])
+
+
+def subcommand_get_enabled_services(_):
+ """Print the status of variours services"""
+ subprocess.call(['firewall-cmd', '--list-services'])
+
+
+def subcommand_add_service(arguments):
+ """Permit a service in the firewall"""
+ subprocess.call(['firewall-cmd', '--add-service', arguments.service])
+ subprocess.call(['firewall-cmd', '--permanent', '--add-service', arguments.service])
+
+
+def subcommand_remove_service(arguments):
+ """Block a service in the firewall"""
+ subprocess.call(['firewall-cmd', '--remove-service', arguments.service])
+ subprocess.call(['firewall-cmd', '--permanent', '--remove-service', arguments.service])
+
+
+def main():
+ """Parse arguments and perform all duties"""
+ arguments = parse_arguments()
+
+ subcommand = arguments.subcommand.replace('-', '_')
+ subcommand_method = globals()['subcommand_' + subcommand]
+ subcommand_method(arguments)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/modules/firewall.py b/modules/firewall.py
new file mode 120000
index 000000000..1d198303f
--- /dev/null
+++ b/modules/firewall.py
@@ -0,0 +1 @@
+installed/system/firewall.py
\ No newline at end of file
diff --git a/modules/installed/system/firewall.py b/modules/installed/system/firewall.py
new file mode 100644
index 000000000..f3faf78ea
--- /dev/null
+++ b/modules/installed/system/firewall.py
@@ -0,0 +1,191 @@
+#
+# This file is part of Plinth.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+"""
+Plinth module to configure a firewall
+"""
+
+import cherrypy
+from gettext import gettext as _
+
+import actions
+import cfg
+from modules.auth import require
+from plugin_mount import PagePlugin
+import service as service_module
+
+
+class Firewall(PagePlugin):
+ """Firewall menu entry and introduction page"""
+ order = 40
+
+ def __init__(self, *args, **kwargs):
+ PagePlugin.__init__(self, *args, **kwargs)
+
+ self.register_page('sys.firewall')
+ cfg.html_root.sys.menu.add_item(_('Firewall'), 'icon-flag',
+ '/sys/firewall', 50)
+
+ service_module.ENABLED.connect(self.on_service_enabled)
+
+ @cherrypy.expose
+ @require()
+ def index(self, **kwargs):
+ """Serve introcution page"""
+ del kwargs # Unused
+
+ # XXX: Use templates here instead of generating HTML
+ main = _('''
+
Firewall is a network security system that controls the incoming
+and outgoing network traffic on your {box_name}. Keeping a firewall
+enabled and properly configured reduces risk of security threat from
+the Internet.
+
+The following the current status:
+''').format(box_name=cfg.box_name)
+
+ if not self.get_installed_status():
+ status = _('''
+Firewall is not installed. Please install it. Firewall comes
+pre-installed with {box_name}. On any Debian based system (such as
+{box_name}) you may install it using the command 'aptitude install
+firewalld'
''').format(box_name=cfg.box_name)
+
+ return self.fill_template(title=_("Firewall"), main=main + status)
+
+ if not self.get_enabled_status():
+ status = _('''
+Firewall daemon is not running. Please run it. Firewall comes
+enabled by default on {box_name}. On any Debian based system (such as
+{box_name}) you may run it using the command 'service firewalld start'
+or in case of system with systemd 'systemctl start firewalld'
+''').format(box_name=cfg.box_name)
+
+ return self.fill_template(title=_("Firewall"), main=main + status)
+
+ enabled_services = self.get_enabled_services()
+ services_info = ''
+ for service in service_module.SERVICES.values():
+ if service.is_enabled():
+ service_text = _('Enabled')
+ service_class = 'firewall-permitted'
+ else:
+ service_text = _('Disabled')
+ service_class = 'firewall-blocked'
+
+ port_info = []
+ for port in service.ports:
+ if port in enabled_services:
+ text = _('Permitted')
+ css_class = 'firewall-permitted'
+ else:
+ text = _('Blocked')
+ css_class = 'firewall-blocked'
+
+ info = _('''
+- {port}: {text}
+''').format(port=port, css_class=css_class, text=text)
+ port_info.append(info)
+
+ port_info = ''.format(
+ port_info=''.join(port_info))
+
+ services_info += _('''
+-
+{name}: {service_text}
+ {port_info}
+
+''').format(
+ name=service.name, service_class=service_class,
+ service_text=service_text, port_info=port_info)
+
+ services_info += '
'
+
+ footnote = '''
+The operation of the firewall is automatic. When you enable a
+service it is automatically permitted in the firewall and you disable
+a service is automatically disabled in the firewall.
'''
+
+ return self.fill_template(title=_("Firewall"), main=main +
+ services_info + footnote)
+
+ def get_installed_status(self):
+ """Return whether firewall is installed"""
+ output = self._run(['get-installed'])
+ return output.split()[0] == 'installed'
+
+ def get_enabled_status(self):
+ """Return whether firewall is installed"""
+ output = self._run(['get-status'])
+ return output.split()[0] == 'running'
+
+ def get_enabled_services(self):
+ """Return the status of various services currently enabled"""
+ output = self._run(['get-enabled-services'])
+ return output.split()
+
+ def add_service(self, port):
+ """Enable a service in firewall"""
+ self._run(['add-service', port])
+
+ def remove_service(self, port):
+ """Remove a service in firewall"""
+ self._run(['remove-service', port])
+
+ def on_service_enabled(self, sender, service_id, enabled, **kwargs):
+ """
+ Enable/disable firewall ports when a service is
+ enabled/disabled.
+ """
+ del sender # Unused
+ del kwargs # Unused
+
+ enabled_services = self.get_enabled_services()
+
+ cfg.log.info('Service enabled - %s, %s' % (service_id, enabled))
+ for port in service_module.SERVICES[service_id].ports:
+ if enabled:
+ if port not in enabled_services:
+ self.add_service(port)
+ else:
+ if port in enabled_services:
+ enabled_services_on_port = [
+ service_.is_enabled()
+ for service_ in service_module.SERVICES.values()
+ if port in service_.ports and
+ service_id != service_.service_id]
+ if not any(enabled_services_on_port):
+ self.remove_service(port)
+
+ @staticmethod
+ def _run(arguments, superuser=True):
+ """Run an given command and raise exception if there was an error"""
+ command = 'firewall'
+
+ cfg.log.info('Running command - %s, %s, %s' % (command, arguments,
+ superuser))
+
+ if superuser:
+ output, error = actions.superuser_run(command, arguments)
+ else:
+ output, error = actions.run(command, arguments)
+
+ if error:
+ raise Exception('Error setting/getting firewalld confguration - %s'
+ % error)
+
+ return output
diff --git a/themes/default/css/bootstrap.css b/themes/default/css/bootstrap.css
index c3e0c0053..e1d175d79 100644
--- a/themes/default/css/bootstrap.css
+++ b/themes/default/css/bootstrap.css
@@ -3494,3 +3494,11 @@ a.thumbnail:hover {
.invisible {
visibility: hidden;
}
+
+.firewall-permitted {
+ color: green;
+}
+
+.firewall-blocked {
+ color: red;
+}