diff --git a/Makefile b/Makefile
index 6b29b092b..5135d4edc 100644
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@ default: config dirs template css docs
all: default
predepend:
- sudo sh -c "apt-get install augeas-tools libpython2.7 pandoc psmisc python2.7 python-augeas python-bcrypt python-bjsonrpc python-cheetah python-cherrypy3 python-simplejson sudo"
+ sudo sh -c "apt-get install augeas-tools libpython2.7 pandoc psmisc python2.7 python-augeas python-passlib python-bcrypt python-bjsonrpc python-cheetah python-cherrypy3 python-simplejson python-contract sudo"
git submodule init
git submodule update
touch predepend
diff --git a/actions/pagekite-configure b/actions/pagekite-configure
new file mode 100755
index 000000000..229c0532c
--- /dev/null
+++ b/actions/pagekite-configure
@@ -0,0 +1,302 @@
+#!/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 Plint PageKite inteface
+
+TODO: Use augeas for manipulating /etc/pagekite.d/* files
+"""
+
+# Disable warning about invalid module name # pylint: disable-msg=C0103
+
+import argparse
+import os
+import re
+import subprocess
+
+CONFIG_DIR = '/etc/pagekite.d'
+
+SERVICE_FILE_MAP = {
+ 'http': {
+ 'file': '80_httpd.rc',
+ 'match': 'http:',
+ 'line': 'service_on = http:@kitename : localhost:80 : @kitesecret'},
+ 'ssh': {
+ 'file': '80_sshd.rc',
+ 'match': 'raw/22:',
+ 'line': 'service_on = raw/22:@kitename : localhost:22 : @kitesecret'}}
+
+
+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 PakeKite is installed')
+
+ # Start PageKite
+ subparsers.add_parser('start', help='Start PageKite service')
+
+ # Stop PageKite
+ subparsers.add_parser('stop', help='Stop PageKite service')
+
+ # Get status
+ subparsers.add_parser('get-status', help='Get whether PakeKite is enabled')
+
+ # Set status
+ set_status = subparsers.add_parser('set-status',
+ help='Enable/disable PageKite')
+ set_status.add_argument('enable', choices=['enable', 'disable'])
+
+ # Get kite details
+ subparsers.add_parser('get-kite',
+ help='Get configured kite name and secret')
+
+ # Set kite details
+ set_kite = subparsers.add_parser('set-kite',
+ help='Configure kite name and its secret')
+ set_kite.add_argument('--kite-name',
+ help='Name of the kite (eg: mybox.pagekite.me)')
+ set_kite.add_argument('--kite-secret', help='Secret for the kite')
+
+ # Get service status
+ get_service = subparsers.add_parser('get-service-status',
+ help='Get whether service is enabled')
+ get_service.add_argument('service', choices=['http', 'ssh'])
+
+ # Set service status
+ set_service = subparsers.add_parser('set-service-status',
+ help='Enable/disable a service')
+ set_service.add_argument('service', choices=['http', 'ssh'])
+ set_service.add_argument('enable', choices=['enable', 'disable'])
+
+ return parser.parse_args()
+
+
+def subcommand_get_installed(_):
+ """Print whether PageKite is installed"""
+ with open('/dev/null', 'w') as file_handle:
+ status = subprocess.call(['which', 'pagekite'], stdout=file_handle)
+
+ print 'installed' if not status else 'not installed'
+
+
+def subcommand_start(_):
+ """Start PageKite service"""
+ status = subprocess.call(['service', 'pagekite', 'start'])
+ if status:
+ raise Exception('Unable to start PageKite server')
+
+
+def subcommand_stop(_):
+ """Stop PageKite service"""
+ status = subprocess.call(['service', 'pagekite', 'stop'])
+ if status:
+ raise Exception('Unable to stop PageKite server')
+
+
+def subcommand_get_status(_):
+ """Print status of the pagekite service"""
+ is_enabled = is_pagekite_enabled()
+ print 'enabled' if is_enabled else 'disabled'
+
+
+def is_pagekite_enabled():
+ """Return if pagekite is enabled"""
+ service_file_path = os.path.join(CONFIG_DIR, '10_account.rc')
+ try:
+ with open(service_file_path, 'r') as file_object:
+ for line in file_object:
+ regex = r'^[ \t]*abort_not_configured'
+ if re.match(regex, line):
+ return False
+ except Exception:
+ return True
+
+ return True
+
+
+def subcommand_set_status(arguments):
+ """Enable/disable the pagekite service"""
+ enable = arguments.enable == 'enable'
+ is_enabled = is_pagekite_enabled()
+ if enable and is_enabled:
+ print 'already enabled'
+ return
+
+ if not enable and not is_enabled:
+ print 'already disabled'
+ return
+
+ if enable:
+ pagekite_enable()
+ print 'enabled'
+ else:
+ pagekite_disable()
+ print 'disabled'
+
+
+def pagekite_enable():
+ """Enable the pagekite daemon"""
+ file_path = os.path.join(CONFIG_DIR, '10_account.rc')
+ file_path_new = os.path.join(CONFIG_DIR, '10_account.rc.new')
+ with open(file_path, 'r') as read_file_object, \
+ open(file_path_new, 'w') as write_file_object:
+ for line in read_file_object:
+ if not re.match('^[ \t]*abort_not_configured', line):
+ write_file_object.write(line)
+
+ os.rename(file_path_new, file_path)
+
+
+def pagekite_disable():
+ """Disable the pagekite daemon"""
+ file_path = os.path.join(CONFIG_DIR, '10_account.rc')
+ with open(file_path, 'a') as file_object:
+ file_object.write('abort_not_configured\n')
+
+
+def subcommand_get_kite(_):
+ """Print details of the currently configure kite"""
+ kite_name = ''
+ kite_secret = ''
+
+ file_path = os.path.join(CONFIG_DIR, '10_account.rc')
+ with open(file_path, 'r') as file_object:
+ for line in file_object:
+ match = re.match(r'[ \t]*kitename[ \t]*=[ \t]*(.*)', line)
+ if match:
+ kite_name = match.group(1)
+ continue
+
+ match = re.match(r'[ \t]*kitesecret[ \t]*=[ \t]*(.*)', line)
+ if match:
+ kite_secret = match.group(1)
+ continue
+
+ print kite_name
+ print kite_secret
+
+
+def subcommand_set_kite(arguments):
+ """Set details of the kite"""
+ kite_name = arguments.kite_name
+ kite_secret = arguments.kite_secret
+
+ file_path = os.path.join(CONFIG_DIR, '10_account.rc')
+ file_path_new = os.path.join(CONFIG_DIR, '10_account.rc.new')
+ with open(file_path, 'r') as read_file_object, \
+ os.fdopen(os.open(file_path_new, os.O_WRONLY | os.O_CREAT,
+ 0400), 'w') as write_file_object:
+ for line in read_file_object:
+ if re.match(r'[ \t]*kitename[ \t]*=.*', line):
+ write_file_object.write(
+ 'kitename = {kite_name}\n'.format(kite_name=kite_name))
+ continue
+
+ if re.match(r'[ \t]*kitesecret[ \t]*=.*', line):
+ write_file_object.write('kitesecret = {kite_secret}\n'
+ .format(kite_secret=kite_secret))
+ continue
+
+ write_file_object.write(line)
+
+ os.rename(file_path_new, file_path)
+
+
+def subcommand_get_service_status(arguments):
+ """Print status of the pagekite service"""
+ is_enabled = is_service_enabled(arguments.service)
+ print 'enabled' if is_enabled else 'disabled'
+
+
+def is_service_enabled(service):
+ """Return if a service is enabled"""
+ service = SERVICE_FILE_MAP[service]
+ service_file_path = os.path.join(CONFIG_DIR, service['file'])
+ if not os.path.isfile(service_file_path):
+ return False
+
+ try:
+ with open(service_file_path, 'r') as file_object:
+ for line in file_object:
+ regex = '[ \t]*service_on[ \t]*=[ \t]*{match}'
+ regex = regex.format(match=service['match'])
+ if re.match(regex, line):
+ return True
+ except Exception:
+ return False
+
+ return False
+
+
+def subcommand_set_service_status(arguments):
+ """Enable/disable a pagekite service"""
+ enable = arguments.enable == 'enable'
+ is_enabled = is_service_enabled(arguments.service)
+ if enable and is_enabled:
+ print 'already enabled'
+ return
+
+ if not enable and not is_enabled:
+ print 'already disabled'
+ return
+
+ if enable:
+ service_enable(arguments.service)
+ print 'enabled'
+ else:
+ service_disable(arguments.service)
+ print 'disabled'
+
+
+def service_enable(service_name):
+ """Enable a service"""
+ service = SERVICE_FILE_MAP[service_name]
+ service_file_path = os.path.join(CONFIG_DIR, service['file'])
+ with open(service_file_path, 'w') as file_object:
+ file_object.write('''
+# Expose the local {service_name} daemon
+# File auto-generated by Plinth
+
+{line}
+'''.format(service_name=service_name, line=service['line']))
+
+
+def service_disable(service_name):
+ """Disable a service"""
+ service = SERVICE_FILE_MAP[service_name]
+ service_file_path = os.path.join(CONFIG_DIR, service['file'])
+ service_file_path_new = os.path.join(CONFIG_DIR,
+ service['file'] + '.plinthbak')
+ os.rename(service_file_path, service_file_path_new)
+
+
+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/cfg.py b/cfg.py
index 408d3df3d..01aeee2d7 100644
--- a/cfg.py
+++ b/cfg.py
@@ -8,8 +8,8 @@ def get_item(parser, section, name):
try:
return parser.get(section, name)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
- print ("The config file {} does not contain the {}.{} option.".format(
- parser[0], section, name))
+ print ("Configuration does not contain the {}.{} option.".format(
+ section, name))
raise
parser = SafeConfigParser(
diff --git a/modules/installed/router/pagekite.py b/modules/installed/router/pagekite.py
new file mode 100644
index 000000000..be767560b
--- /dev/null
+++ b/modules/installed/router/pagekite.py
@@ -0,0 +1,291 @@
+#
+# 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 for configuring PageKite service
+"""
+
+import cherrypy
+from gettext import gettext as _
+
+import actions
+import cfg
+from forms import Form
+from modules.auth import require
+from plugin_mount import PagePlugin, FormPlugin
+import re
+import util
+
+
+class PageKite(PagePlugin):
+ """PageKite menu entry and introduction page"""
+ order = 60
+
+ def __init__(self, *args, **kwargs):
+ PagePlugin.__init__(self, *args, **kwargs)
+
+ self.register_page("router.setup.pagekite")
+ self.register_page("router.setup.pagekite.configure")
+ cfg.html_root.router.setup.menu.add_item(
+ "Public Visibility (PageKite)", "icon-flag",
+ "/router/setup/pagekite", 50)
+
+ @cherrypy.expose
+ @require()
+ def index(self, **kwargs):
+ """Serve introcution page"""
+ del kwargs # Unused
+
+ main = _("""
+
PageKite is a system for exposing FreedomBox services when you
+don't have a direct connection to the Internet. You only need this
+service if your FreedomBox services are unreachable from the rest of
+the Internet. This includes the following situations:
+
+
+ - FreedomBox is behind a restricted firewall.
+
+ - FreedomBox is connected to a (wireless) router which you don't
+ control.
+
+ - Your ISP does not provide you an external IP address and instead
+ provides Internet connection through NAT.
+
+ - Your ISP does not provide you a static IP address and your IP
+ address changes evertime you connect to Internet.
+
+ - Your ISP limits incoming connections.
+
+
+PageKite works around NAT, firewalls and IP-address limitations by
+using a combination of tunnels and reverse proxies. Currently,
+exposing web server and SSH server are supported. An intermediary
+server with direct Internet access is required. Currently, only
+pagekite.net server is supported and you will need an account
+there. In future, it might be possible to use your buddy's FreedomBox
+for this.
+
+Configure
+PageKite
+""".format(server_dir=cfg.server_dir))
+
+ sidebar_right = _('''
+PageKite
+Configure
+PageKite
'''.format(server_dir=cfg.server_dir))
+
+ return self.fill_template(title="Public Visibility (PageKite)",
+ main=main, sidebar_right=sidebar_right)
+
+
+class configure(FormPlugin, PagePlugin): # pylint: disable-msg=C0103
+ """Main configuration form"""
+ order = 65
+
+ url = ["/router/setup/pagekite/configure"]
+
+ js = """
+
+"""
+
+ def get_status(self):
+ """
+ Return the current status of PageKite configuration by
+ executing various actions.
+ """
+ status = {}
+
+ # Check if PageKite is installed
+ output = self._run(['get-installed'])
+ cfg.log('Output - %s' % output)
+ if output.split()[0] != 'installed':
+ return None
+
+ # PageKite service enabled/disabled
+ output = self._run(['get-status'])
+ status['enabled'] = (output.split()[0] == 'enabled')
+
+ # PageKite kite details
+ output = self._run(['get-kite'])
+ kite_details = output.split()
+ status['kite_name'] = kite_details[0]
+ status['kite_secret'] = kite_details[1]
+
+ # Service status
+ status['service'] = {}
+ for service in ('http', 'ssh'):
+ output = self._run(['get-service-status', service])
+ status['service'][service] = (output.split()[0] == 'enabled')
+
+ return status
+
+ def main(self, *args, **kwargs):
+ """Build and return the main content area which is the form"""
+ del args # unused
+
+ status = self.get_status()
+
+ if not status:
+ return _('''
+PageKite is not installed, please install it. PageKite comes
+pre-installed with FreedomBox. On any Debian based system (such as
+FreedomBox) you may install it using the command 'aptitude install
+pagekite'
''')
+
+ try:
+ message = kwargs['message'].text
+ except KeyError:
+ message = None
+ form = Form(
+ title="Configure PageKite",
+ action=cfg.server_dir + "/router/setup/pagekite/configure/",
+ name="configure_pagekite_form", message=message)
+
+ form.checkbox(_("Enable PageKite"), name="pagekite_enable",
+ id="pagekite-enable", checked=status['enabled'])
+
+ show_form = "block" if status['enabled'] else "none"
+ form.html('''
+'''.format(show_form=show_form))
+
+ form.html(_("
PageKite Account
"))
+ form.text_input(_("Server"), name="pagekite_server",
+ id="pagekite-server", value="pagekite.net")
+ form.text_input(_("Kite name"), name="pagekite_kite_name",
+ id="pagekite-kite-name", value=status['kite_name'])
+ form.text_input(_("Kite secret"), name="pagekite_kite_secret",
+ id="pagekite-kite-secret", value=status['kite_secret'])
+
+ form.html(_("Services
"))
+ form.checkbox(_("Web Server (HTTP)"), name="pagekite_http_enable",
+ id="pagekite-http-enable",
+ checked=status['service']['http'])
+ form.checkbox(_("Secure Shell (SSH)"), name="pagekite_ssh_enable",
+ id="pagekite-ssh-enable",
+ checked=status['service']['ssh'])
+
+ form.html("") # pagekite-post-enable-form
+
+ form.submit(_("Update setup"))
+ return form.render()
+
+ def process_form(self, **kwargs):
+ """Handle form submission"""
+ status = self.get_status()
+
+ message, new_status = self.validate_form(**kwargs)
+ if not message.text:
+ self.apply_changes(status, new_status, message)
+
+ return message
+
+ @staticmethod
+ def validate_form(**kwargs):
+ """Check whether all the input form values are correct"""
+ new_status = {}
+ message = util.Message()
+
+ domain_name_re = r'^[\w-]{1,63}(\.[\w-]{1,63})*$'
+ pagekite_kite_name = kwargs.get('pagekite_kite_name', '').strip()
+ if not re.match(domain_name_re, pagekite_kite_name):
+ message.add(_('Invalid kite name'))
+ else:
+ new_status['kite_name'] = pagekite_kite_name
+
+ pagekite_kite_secret = kwargs.get('pagekite_kite_secret', '').strip()
+ if not pagekite_kite_secret:
+ message.add(_('Invalid kite secret'))
+ else:
+ new_status['kite_secret'] = pagekite_kite_secret
+
+ new_status['enabled'] = (kwargs.get('pagekite_enable') == 'on')
+ new_status['service'] = {
+ 'http': (kwargs.get('pagekite_http_enable') == 'on'),
+ 'ssh': (kwargs.get('pagekite_ssh_enable') == 'on')
+ }
+
+ return message, new_status
+
+ def apply_changes(self, old_status, new_status, message):
+ """Apply the changes to PageKite configuration"""
+ cfg.log.info('New status is - %s' % new_status)
+
+ if old_status != new_status:
+ self._run(['stop'])
+
+ if old_status['enabled'] != new_status['enabled']:
+ if new_status['enabled']:
+ self._run(['set-status', 'enable'])
+ message.add(_('PageKite enabled'))
+ else:
+ self._run(['set-status', 'disable'])
+ message.add(_('PageKite disabled'))
+
+ if old_status['kite_name'] != new_status['kite_name'] or \
+ old_status['kite_secret'] != new_status['kite_secret']:
+ self._run(['set-kite', '--kite-name', new_status['kite_name'],
+ '--kite-secret', new_status['kite_secret']])
+ message.add(_('Kite details set'))
+
+ for service, old_value in old_status['service'].items():
+ if old_value != new_status['service'][service]:
+ if new_status['service'][service]:
+ self._run(['set-service-status', service, 'enable'])
+ message.add(_('Service enabled: {service}')
+ .format(service=service))
+ else:
+ self._run(['set-service-status', service, 'disable'])
+ message.add(_('Service disabled: {service}')
+ .format(service=service))
+
+ if old_status != new_status:
+ self._run(['start'])
+
+ @staticmethod
+ def _run(arguments, superuser=True):
+ """Run an given command and raise exception if there was an error"""
+ command = 'pagekite-configure'
+
+ 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 PageKite confguration - %s'
+ % error)
+
+ return output
diff --git a/modules/installed/router/router.py b/modules/installed/router/router.py
index 278309f7b..6c1f42df0 100644
--- a/modules/installed/router/router.py
+++ b/modules/installed/router/router.py
@@ -88,19 +88,19 @@ class wan(FormPlugin, PagePlugin):
url = ["/router/setup"]
order = 10
- js = """"""
+ $(document).ready(function() {
+ $('#connect_type').change(hideshow_static);
+ hideshow_static();
+ });
+ })(jQuery);
+"""
def sidebar_right(self, *args, **kwargs):
side=''
@@ -141,7 +141,7 @@ class wan(FormPlugin, PagePlugin):
action=cfg.server_dir + "/router/setup/wan/index",
name="wan_connection_form",
message=message)
- form.dropdown('Connection Type', vals=["DHCP", "Static IP"], id="connect_type", onchange="hideshow_static()")
+ form.dropdown('Connection Type', vals=["DHCP", "Static IP"], id="connect_type")
form.html('')
form.dotted_quad("WAN IP Address", name="wan_ip", quad=[wan_ip0, wan_ip1, wan_ip2, wan_ip3])
form.dotted_quad("Subnet Mask", name="subnet", quad=[subnet0, subnet1, subnet2, subnet3])
@@ -150,11 +150,6 @@ class wan(FormPlugin, PagePlugin):
form.dotted_quad("Static DNS 2", name="dns2", quad=[dns20, dns21, dns22, dns23])
form.dotted_quad("Static DNS 3", name="dns3", quad=[dns30, dns31, dns32, dns33])
form.html('
')
- form.html(""" """)
form.submit("Set Wan")
return form.render()
diff --git a/modules/pagekite.py b/modules/pagekite.py
new file mode 120000
index 000000000..2981955ca
--- /dev/null
+++ b/modules/pagekite.py
@@ -0,0 +1 @@
+installed/router/pagekite.py
\ No newline at end of file
diff --git a/plugin_mount.py b/plugin_mount.py
index cf0f26880..4983ea28a 100644
--- a/plugin_mount.py
+++ b/plugin_mount.py
@@ -140,7 +140,7 @@ class FormPlugin():
"""If the user has tried to fill in the form, process it, otherwise, just display a default form."""
if kwargs:
kwargs['message'] = self.process_form(**kwargs)
- parts = get_parts(self)
+ parts = get_parts(self, **kwargs)
return self.fill_template(**parts)
def process_form(self, **kwargs):
diff --git a/templates/base.tmpl b/templates/base.tmpl
index 5ec88d4c4..ebfe48db6 100644
--- a/templates/base.tmpl
+++ b/templates/base.tmpl
@@ -96,7 +96,6 @@
- $js
$main_menu_js
$sub_menu_js
-->
+ $js