diff --git a/actions/apache b/actions/apache index 01f5e8e93..a703e1588 100755 --- a/actions/apache +++ b/actions/apache @@ -120,6 +120,7 @@ def subcommand_setup(arguments): webserver.enable('proxy', kind='module') webserver.enable('proxy_http', kind='module') webserver.enable('proxy_fcgi', kind='module') + webserver.enable('proxy_html', kind='module') webserver.enable('rewrite', kind='module') webserver.enable('macro', kind='module') diff --git a/actions/i2p b/actions/i2p new file mode 100755 index 000000000..9bdaacefe --- /dev/null +++ b/actions/i2p @@ -0,0 +1,113 @@ +#!/usr/bin/python3 +# +# This file is part of FreedomBox. +# +# 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 . +# +""" +Wrapper to list and handle system services +""" + +import argparse +import os + +from plinth import action_utils, cfg + +cfg.read() +module_config_path = os.path.join(cfg.config_dir, 'modules-enabled') + +I2P_CONF_DIR = '/var/lib/i2p/i2p-config' + + +def parse_arguments(): + """Return parsed command line arguments as dictionary.""" + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') + + subparsers.add_parser('enable', help='enable i2p service') + subparsers.add_parser('disable', help='disable i2p service') + + subparser = subparsers.add_parser( + 'add-favorite', help='Add an eepsite to the list of favorites') + subparser.add_argument('--name', help='Name of the entry', required=True) + subparser.add_argument('--url', help='URL of the entry', required=True) + + subparsers.required = True + return parser.parse_args() + + +def subcommand_enable(_): + """Enable I2P service.""" + action_utils.service_enable('i2p') + action_utils.webserver_enable('i2p-freedombox') + + +def subcommand_disable(_): + """Disable I2P service.""" + action_utils.service_disable('i2p') + action_utils.webserver_disable('i2p-freedombox') + + +def subcommand_add_favorite(arguments): + """ + Adds a favorite to the router.config + + :param arguments: + :type arguments: + """ + router_config_path = os.path.join(I2P_CONF_DIR, 'router.config') + # Read config + with open(router_config_path) as config_file: + config_lines = config_file.readlines() + + found_favorites = False + url = arguments.url + new_favorite = '{name},{description},{url},{icon},'.format( + name=arguments.name, description='', url=arguments.url, + icon='/themes/console/images/eepsite.png') + for i in range(len(config_lines)): + line = config_lines[i] + + # Find favorites line + if line.startswith('routerconsole.favorites'): + found_favorites = True + if url in line: + print('URL already in favorites') + exit(0) + + # Append favorite + config_lines[i] = line.strip() + new_favorite + '\n' + break + + if not found_favorites: + config_lines.append('routerconsole.favorites=' + new_favorite + '\n') + + # Update config + with open(router_config_path, mode='w') as config_file: + config_file.writelines(config_lines) + + print('Added {} to favorites'.format(url)) + + +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/functional_tests/features/i2p.feature b/functional_tests/features/i2p.feature new file mode 100644 index 000000000..4b27ad189 --- /dev/null +++ b/functional_tests/features/i2p.feature @@ -0,0 +1,34 @@ +# +# This file is part of FreedomBox. +# +# 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 . +# + +@apps @i2p-app +Feature: I2P Anonymity Network + Manage I2P configuration. + +Background: + Given I'm a logged in user + Given the i2p application is installed + +Scenario: Enable i2p application + Given the i2p application is disabled + When I enable the i2p application + Then the i2p service should be running + +Scenario: Disable i2p application + Given the i2p application is enabled + When I disable the i2p application + Then the i2p service should not be running diff --git a/plinth/modules/apache/__init__.py b/plinth/modules/apache/__init__.py index 581d36979..9d2a96fae 100644 --- a/plinth/modules/apache/__init__.py +++ b/plinth/modules/apache/__init__.py @@ -20,7 +20,7 @@ FreedomBox app for Apache server. from plinth import actions -version = 6 +version = 7 is_essential = True diff --git a/plinth/modules/diagnostics/urls.py b/plinth/modules/diagnostics/urls.py index 7e31b8a22..56ff7ef0b 100644 --- a/plinth/modules/diagnostics/urls.py +++ b/plinth/modules/diagnostics/urls.py @@ -26,6 +26,6 @@ from . import diagnostics as views urlpatterns = [ url(r'^sys/diagnostics/$', views.index, name='index'), - url(r'^sys/diagnostics/(?P[a-z\-]+)/$', views.module, + url(r'^sys/diagnostics/(?P[1-9a-z\-]+)/$', views.module, name='module'), ] diff --git a/plinth/modules/i2p/__init__.py b/plinth/modules/i2p/__init__.py new file mode 100644 index 000000000..33ca296bf --- /dev/null +++ b/plinth/modules/i2p/__init__.py @@ -0,0 +1,153 @@ +# +# This file is part of FreedomBox. +# +# 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 . +# +""" +FreedomBox app to configure I2P. +""" + +from django.utils.translation import ugettext_lazy as _ + +from plinth import action_utils, actions, frontpage +from plinth import service as service_module +from plinth.menu import main_menu +from plinth.modules.users import register_group + +from .manifest import backup, clients + +version = 1 + +service_name = 'i2p' + +managed_services = [service_name] + +managed_packages = ['i2p'] + +name = _('I2P') + +short_description = _('Anonymity Network') + +description = [ + _('The Invisible Internet Project is an anonymous network layer intended ' + 'to protect communication from censorship and surveillance. I2P ' + 'provides anonymity by sending encrypted traffic through a ' + 'volunteer-run network distributed around the world.'), + _('Find more information about I2P on their project ' + 'homepage.'), + _('The first visit to the provided web interface will initiate the ' + 'configuration process.') +] + +clients = clients + +group = ('i2p', _('Manage I2P application')) + +service = None + +manual_page = 'I2P' + +additional_favorites = [ + ('Searx instance', 'http://ransack.i2p'), + ('Torrent tracker', 'http://tracker2.postman.i2p'), + ('YaCy Legwork', 'http://legwork.i2p'), + ('YaCy Seeker', 'http://seeker.i2p'), +] + + +def init(): + """Intialize the module.""" + menu = main_menu.get('apps') + menu.add_urlname(name, 'i2p', 'i2p:index', short_description) + register_group(group) + + global service + setup_helper = globals()['setup_helper'] + if setup_helper.get_state() != 'needs-setup': + service = service_module.Service(managed_services[0], name, ports=[ + 'http', 'https' + ], is_external=True, is_enabled=is_enabled, enable=enable, + disable=disable, + is_running=is_running) + + if is_enabled(): + add_shortcut() + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(managed_packages) + + # Add favorites to the configuration + for fav_name, fav_url in additional_favorites: + helper.call('post', actions.superuser_run, 'i2p', [ + 'add-favorite', + '--name', + fav_name, + '--url', + fav_url, + ]) + helper.call('post', enable) + global service + if service is None: + service = service_module.Service(managed_services[0], name, ports=[ + 'http', 'https' + ], is_external=True, is_enabled=is_enabled, enable=enable, + disable=disable, + is_running=is_running) + + helper.call('post', service.notify_enabled, None, True) + helper.call('post', add_shortcut) + + +def add_shortcut(): + """Helper method to add a shortcut to the frontpage.""" + frontpage.add_shortcut('i2p', name, short_description=short_description, + url='/i2p/', login_required=True, + allowed_groups=[group[0]]) + + +def is_running(): + """Return whether the service is running.""" + return action_utils.service_is_running('i2p') + + +def is_enabled(): + """Return whether the module is enabled.""" + return action_utils.service_is_enabled('i2p') and \ + action_utils.webserver_is_enabled('i2p-freedombox') + + +def enable(): + """Enable the module.""" + actions.superuser_run('i2p', ['enable']) + add_shortcut() + + +def disable(): + """Enable the module.""" + actions.superuser_run('i2p', ['disable']) + frontpage.remove_shortcut('i2p') + + +def diagnose(): + """Run diagnostics and return the results.""" + results = [] + + results.append(action_utils.diagnose_port_listening(7657, 'tcp6')) + results.extend( + action_utils.diagnose_url_on_all('https://{host}/i2p/', + check_certificate=False)) + + return results diff --git a/plinth/modules/i2p/data/etc/apache2/conf-available/i2p-freedombox.conf b/plinth/modules/i2p/data/etc/apache2/conf-available/i2p-freedombox.conf new file mode 100644 index 000000000..54f3c45f2 --- /dev/null +++ b/plinth/modules/i2p/data/etc/apache2/conf-available/i2p-freedombox.conf @@ -0,0 +1,30 @@ +## +## On all sites, provide I2P on a default path: /i2p +## +## Requires the following Apache modules to be enabled: +## mod_headers +## mod_proxy +## mod_proxy_http +## mod_proxy_html +## + + # Disable compression + # As soon as it has to be chunked, it doesn't work + RequestHeader unset Accept-Encoding + + ProxyPass http://localhost:7657 + ProxyPassReverse http://localhost:7657 + + # Rewrite absolute urls from i2p to pass through apache + ProxyHTMLEnable On + ProxyHTMLURLMap / /i2p/ + + Include includes/freedombox-single-sign-on.conf + + TKTAuthToken "admin" "i2p" + + + +# Catch some other root i2p addresses +# These are most likely generated by javascript +RedirectMatch "^/(i2p[^/]+.*)" "/i2p/$1" diff --git a/plinth/modules/i2p/data/etc/plinth/modules-enabled/i2p b/plinth/modules/i2p/data/etc/plinth/modules-enabled/i2p new file mode 100644 index 000000000..a9be526d5 --- /dev/null +++ b/plinth/modules/i2p/data/etc/plinth/modules-enabled/i2p @@ -0,0 +1 @@ +#plinth.modules.i2p diff --git a/plinth/modules/i2p/manifest.py b/plinth/modules/i2p/manifest.py new file mode 100644 index 000000000..c65bdebe7 --- /dev/null +++ b/plinth/modules/i2p/manifest.py @@ -0,0 +1,56 @@ +# +# This file is part of FreedomBox. +# +# 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 . +# + +from django.utils.translation import ugettext_lazy as _ + +from plinth.clients import validate +from plinth.modules.backups.api import validate as validate_backup + +_package_id = 'net.geti2p.i2p' +_download_url = 'https://geti2p.net/download' + +clients = validate([{ + 'name': + _('I2P'), + 'platforms': [{ + 'type': 'web', + 'url': '/i2p/' + }, { + 'type': 'package', + 'format': 'deb', + 'name': 'i2p', + }, { + 'type': 'download', + 'os': 'gnu-linux', + 'url': _download_url, + }, { + 'type': 'download', + 'os': 'macos', + 'url': _download_url, + }, { + 'type': 'download', + 'os': 'windows', + 'url': _download_url, + }] +}]) + +backup = validate_backup({ + 'secrets': { + 'directories': ['/var/lib/i2p/i2p-config'] + }, + 'services': ['i2p'] +}) diff --git a/plinth/modules/i2p/templates/i2p.html b/plinth/modules/i2p/templates/i2p.html new file mode 100644 index 000000000..84a154ca3 --- /dev/null +++ b/plinth/modules/i2p/templates/i2p.html @@ -0,0 +1,63 @@ +{% extends "service-subsubmenu.html" %} +{% comment %} +# +# This file is part of FreedomBox. +# +# 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 . +# +{% endcomment %} + +{% load bootstrap %} +{% load i18n %} + +{% block configuration %} + {% block status %} + {% if show_status_block %} +

{% trans "Status" %}

+

+ {% with service_name=service.name %} + {% if service.is_running %} + + {% blocktrans trimmed %} + Service {{ service_name }} is running. + {% endblocktrans %} + {% else %} + + {% blocktrans trimmed %} + Service {{ service_name }} is not running. + {% endblocktrans %} + {% endif %} + {% endwith %} +

+ {% endif %} + {% endblock %} + + {% block diagnostics %} + {% if diagnostics_module_name %} + {% include "diagnostics_button.html" with module=diagnostics_module_name enabled=service.is_enabled %} + {% endif %} + {% endblock %} + +

{% trans "Configuration" %}

+ +
+ {% csrf_token %} + + {{ form|bootstrap }} + + +
+ +{% endblock %} diff --git a/plinth/modules/i2p/templates/i2p_service.html b/plinth/modules/i2p/templates/i2p_service.html new file mode 100644 index 000000000..364ba0f38 --- /dev/null +++ b/plinth/modules/i2p/templates/i2p_service.html @@ -0,0 +1,16 @@ +{% extends "service-subsubmenu.html" %} + +{% load i18n %} + +{% block configuration %} + {% for line in service_description %} +

{{ line|safe }}

+ {% endfor %} + +

+ + {% trans "Launch" %} + +

+{% endblock %} diff --git a/plinth/modules/i2p/urls.py b/plinth/modules/i2p/urls.py new file mode 100644 index 000000000..8a62a9f76 --- /dev/null +++ b/plinth/modules/i2p/urls.py @@ -0,0 +1,29 @@ +# +# This file is part of FreedomBox. +# +# 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 . +# +""" +URLs for the I2P module. +""" + +from django.conf.urls import url + +from plinth.modules.i2p import views + +urlpatterns = [ + url(r'^apps/i2p/$', views.I2PServiceView.as_view(), name='index'), + url(r'^apps/i2p/tunnels/?$', views.TunnelsView.as_view(), name='tunnels'), + url(r'^apps/i2p/torrents/?$', views.TorrentsView.as_view(), name='torrents'), +] diff --git a/plinth/modules/i2p/views.py b/plinth/modules/i2p/views.py new file mode 100644 index 000000000..ed279a739 --- /dev/null +++ b/plinth/modules/i2p/views.py @@ -0,0 +1,106 @@ +# +# This file is part of FreedomBox. +# +# 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 . +# +""" +Views for I2P application. +""" + +from django.urls import reverse_lazy +from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy +from django.views.generic import TemplateView + +import plinth.modules.i2p as i2p +from plinth.views import ServiceView + +subsubmenu = [{ + 'url': reverse_lazy('i2p:index'), + 'text': ugettext_lazy('Configure') +}, { + 'url': reverse_lazy('i2p:tunnels'), + 'text': ugettext_lazy('Proxies') +}, + { + 'url': reverse_lazy('i2p:torrents'), + 'text': ugettext_lazy('Anonymous torrents') + }] + + +class I2PServiceView(ServiceView): + """Serve configuration page.""" + service_id = i2p.service_name + clients = i2p.clients + description = i2p.description + diagnostics_module_name = i2p.service_name + show_status_block = True + template_name = 'i2p.html' + + def get_context_data(self, **kwargs): + """Return the context data for rendering the template view.""" + context = super().get_context_data(**kwargs) + context['title'] = i2p.name + context['description'] = i2p.description + context['clients'] = i2p.clients + context['manual_page'] = i2p.manual_page + context['subsubmenu'] = subsubmenu + return context + + +class ServiceBaseView(TemplateView): + """View to describe and launch a service.""" + service_description = None + service_title = None + service_path = None + + def get_context_data(self, **kwargs): + """Add context data for template.""" + context = super().get_context_data(**kwargs) + context['title'] = i2p.name + context['description'] = i2p.description + context['clients'] = i2p.clients + context['manual_page'] = i2p.manual_page + context['subsubmenu'] = subsubmenu + context['service_title'] = self.service_title + context['service_path'] = self.service_path + context['service_description'] = self.service_description + return context + + +class TunnelsView(ServiceBaseView): + """View to describe and launch tunnel configuration.""" + template_name = 'i2p_service.html' + service_title = _('I2P Proxies and Tunnels') + service_path = '/i2p/i2ptunnel/' + service_description = [ + _('I2P lets you browse the Internet and hidden services (eepsites) ' + 'anonymously. For this, your browser, preferably a Tor Browser, ' + 'needs to be configured for a proxy.'), + _('By default HTTP, HTTPS and SOCKS5 proxies are available. Additional ' + 'proxies and tunnels may be configured using the tunnel ' + 'configuration interface.'), + ] + + +class TorrentsView(ServiceBaseView): + """View to describe and launch I2P torrents application.""" + template_name = 'i2p_service.html' + service_title = _('Anonymous Torrents') + service_path = '/i2p/i2psnark/' + service_description = [ + _('I2P provides an application to download files anonymously in a ' + 'peer-to-peer network. Download files by adding torrents or create a ' + 'new torrent to share a file.'), + ] diff --git a/static/themes/default/icons/i2p.png b/static/themes/default/icons/i2p.png new file mode 100644 index 000000000..63e9aacfb Binary files /dev/null and b/static/themes/default/icons/i2p.png differ diff --git a/static/themes/default/icons/i2p.svg b/static/themes/default/icons/i2p.svg new file mode 100644 index 000000000..1d057dbb3 --- /dev/null +++ b/static/themes/default/icons/i2p.svg @@ -0,0 +1,196 @@ + + + + + I2P + + + + + + image/svg+xml + + I2P + + + + Sunil Mohan Adapa <sunil@medhas.org> + + + 2019-04-01 + + https://commons.wikimedia.org/wiki/File:I2P_logo.svg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +