mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-02-11 08:23:49 +00:00
shadowsocks: Add shadowsocks client with socks5 proxy
Signed-off-by: James Valleroy <jvalleroy@mailbox.org> Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
parent
bf1664aa9d
commit
6bf4eb1483
1
LICENSES
1
LICENSES
@ -58,6 +58,7 @@ otherwise.
|
||||
- static/themes/default/icons/repro.png :: [[https://www.resiprocate.org/Main_Page][BSD-3-clause]]
|
||||
- static/themes/default/icons/roundcube.png :: [[https://roundcube.net/][GPL-3+]]
|
||||
- static/themes/default/icons/shaarli.png :: [[https://github.com/shaarli/Shaarli][zlib/libpng]]
|
||||
- static/themes/default/icons/shadowsocks.png :: [[https://commons.wikimedia.org/wiki/File:Shadowsocks_logo.png][Apache 2.0]]
|
||||
- static/themes/default/icons/syncthing.png :: [[https://github.com/syncthing/syncthing/][Mozilla Public License Version 2.0]]
|
||||
- static/themes/default/icons/tahoe.png :: [[https://github.com/thekishanraval/Logos][GPLv3+]]
|
||||
- static/themes/default/icons/transmission.png :: [[https://transmissionbt.com/][GPL]]
|
||||
|
||||
97
actions/shadowsocks
Executable file
97
actions/shadowsocks
Executable file
@ -0,0 +1,97 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
"""
|
||||
Helper script for configuring Shadowsocks.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
from plinth import action_utils
|
||||
from plinth.modules import shadowsocks
|
||||
from plinth.modules.shadowsocks.views import SHADOWSOCKS_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('setup',
|
||||
help='Perform initial setup steps')
|
||||
subparsers.add_parser('enable',
|
||||
help='Enable Shadowsocks client socks5 proxy')
|
||||
subparsers.add_parser('disable',
|
||||
help='Disable Shadowsocks client socks5 proxy')
|
||||
subparsers.add_parser(
|
||||
'merge-config', help='Merge JSON config from stdin with existing')
|
||||
|
||||
subparsers.required = True
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def subcommand_setup(_):
|
||||
"""Perform initial setup steps."""
|
||||
# Only client socks5 proxy is supported for now. Disable the
|
||||
# server component.
|
||||
action_utils.service_disable('shadowsocks-libev')
|
||||
|
||||
|
||||
def subcommand_enable(_):
|
||||
"""Enable Shadowsocks client socks5 proxy."""
|
||||
action_utils.service_enable(shadowsocks.managed_services[0])
|
||||
|
||||
|
||||
def subcommand_disable(_):
|
||||
"""Disable Shadowsocks client socks5 proxy."""
|
||||
action_utils.service_disable(shadowsocks.managed_services[0])
|
||||
|
||||
|
||||
def subcommand_merge_config(arguments):
|
||||
"""Configure Shadowsocks."""
|
||||
config = sys.stdin.read()
|
||||
config = json.loads(config)
|
||||
|
||||
try:
|
||||
current_config = open(SHADOWSOCKS_CONFIG, 'r').read()
|
||||
current_config = json.loads(current_config)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
current_config = {}
|
||||
|
||||
new_config = current_config
|
||||
new_config.update(config)
|
||||
new_config = json.dumps(new_config, indent=4, sort_keys=True)
|
||||
|
||||
open(SHADOWSOCKS_CONFIG, 'w').write(new_config)
|
||||
|
||||
action_utils.service_reload(shadowsocks.managed_services[0])
|
||||
|
||||
|
||||
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()
|
||||
1
data/etc/plinth/modules-enabled/shadowsocks
Normal file
1
data/etc/plinth/modules-enabled/shadowsocks
Normal file
@ -0,0 +1 @@
|
||||
plinth.modules.shadowsocks
|
||||
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<service>
|
||||
<short>Shadowsocks client socks5 proxy</short>
|
||||
<description>Shadowsocks is a lightweight and secure socks5 proxy, designed to protect your Internet traffic. Enable this service if you are running a Shadowsocks client, and want to provide socks5 proxy.</description>
|
||||
<port protocol="tcp" port="1080"/>
|
||||
</service>
|
||||
126
plinth/modules/shadowsocks/__init__.py
Normal file
126
plinth/modules/shadowsocks/__init__.py
Normal file
@ -0,0 +1,126 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Plinth module to configure Shadowsocks.
|
||||
"""
|
||||
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from plinth import actions
|
||||
from plinth import action_utils
|
||||
from plinth import frontpage
|
||||
from plinth import service as service_module
|
||||
from plinth.menu import main_menu
|
||||
|
||||
|
||||
version = 1
|
||||
|
||||
name = _('Shadowsocks')
|
||||
|
||||
short_description = _('Socks5 Proxy')
|
||||
|
||||
service = None
|
||||
|
||||
managed_services = ['shadowsocks-libev-local@freedombox']
|
||||
|
||||
managed_packages = ['shadowsocks-libev']
|
||||
|
||||
description = [
|
||||
_('Shadowsocks is a lightweight and secure socks5 proxy, designed to '
|
||||
'protect your Internet traffic. It can be used to bypass Internet '
|
||||
'filtering and censorship.'),
|
||||
_('Your FreedomBox can run a Shadowsocks client, that can connect '
|
||||
'to a Shadowsocks server. The FreedomBox will also run a socks5 '
|
||||
'server. Local devices can connect to the socks5 server, and '
|
||||
'their data will be encrypted and proxied through the Shadowsocks '
|
||||
'server.'),
|
||||
]
|
||||
|
||||
|
||||
def init():
|
||||
"""Intialize the module."""
|
||||
menu = main_menu.get('apps')
|
||||
menu.add_urlname(name, 'glyphicon-send', 'shadowsocks:index',
|
||||
short_description)
|
||||
|
||||
global service
|
||||
setup_helper = globals()['setup_helper']
|
||||
if setup_helper.get_state() != 'needs-setup':
|
||||
service = service_module.Service(
|
||||
'shadowsocks', name,
|
||||
ports=['shadowsocks-local-plinth'], is_external=False,
|
||||
is_enabled=is_enabled, is_running=is_running,
|
||||
enable=enable, disable=disable)
|
||||
|
||||
if service.is_enabled():
|
||||
add_shortcut()
|
||||
|
||||
|
||||
def setup(helper, old_version=None):
|
||||
"""Install and configure the module."""
|
||||
helper.install(managed_packages)
|
||||
helper.call('post', actions.superuser_run, 'shadowsocks', ['setup'])
|
||||
global service
|
||||
if service is None:
|
||||
service = service_module.Service(
|
||||
'shadowsocks', name,
|
||||
ports=['shadowsocks-local-plinth'], is_external=False,
|
||||
is_enabled=is_enabled, is_running=is_running,
|
||||
enable=enable, disable=disable)
|
||||
|
||||
|
||||
def add_shortcut():
|
||||
"""Helper method to add a shortcut to the frontpage."""
|
||||
frontpage.add_shortcut('shadowsocks', name,
|
||||
short_description=short_description,
|
||||
details=description,
|
||||
configure_url=reverse_lazy('shadowsocks:index'),
|
||||
login_required=False)
|
||||
|
||||
|
||||
def is_enabled():
|
||||
"""Return whether service is enabled."""
|
||||
return action_utils.service_is_enabled(managed_services[0])
|
||||
|
||||
|
||||
def is_running():
|
||||
"""Return whether service is running."""
|
||||
return action_utils.service_is_running(managed_services[0])
|
||||
|
||||
|
||||
def enable():
|
||||
"""Enable service."""
|
||||
actions.superuser_run('shadowsocks', ['enable'])
|
||||
add_shortcut()
|
||||
|
||||
|
||||
def disable():
|
||||
"""Disable service."""
|
||||
actions.superuser_run('shadowsocks', ['disable'])
|
||||
frontpage.remove_shortcut('shadowsocks')
|
||||
|
||||
|
||||
def diagnose():
|
||||
"""Run diagnostics and return the results."""
|
||||
results = []
|
||||
|
||||
results.append(action_utils.diagnose_port_listening(1080, 'tcp4'))
|
||||
results.append(action_utils.diagnose_port_listening(1080, 'tcp6'))
|
||||
|
||||
return results
|
||||
61
plinth/modules/shadowsocks/forms.py
Normal file
61
plinth/modules/shadowsocks/forms.py
Normal file
@ -0,0 +1,61 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Plinth module for configuring Shadowsocks.
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from plinth.forms import ServiceForm
|
||||
|
||||
METHODS = ['chacha20-ietf-poly1305', 'aes-256-gcm']
|
||||
|
||||
|
||||
class TrimmedCharField(forms.CharField):
|
||||
"""Trim the contents of a CharField"""
|
||||
|
||||
def clean(self, value):
|
||||
"""Clean and validate the field value"""
|
||||
if value:
|
||||
value = value.strip()
|
||||
|
||||
return super(TrimmedCharField, self).clean(value)
|
||||
|
||||
|
||||
class ShadowsocksForm(ServiceForm):
|
||||
"""Shadowsocks configuration form"""
|
||||
server = TrimmedCharField(
|
||||
label=_('Server'),
|
||||
help_text=_('Server hostname or IP address'))
|
||||
|
||||
server_port = forms.IntegerField(
|
||||
label=_('Server port'),
|
||||
min_value=0,
|
||||
max_value=65535,
|
||||
help_text=_('Server port number'))
|
||||
|
||||
password = forms.CharField(
|
||||
label=_('Password'),
|
||||
help_text=_('Password used to encrypt data. '
|
||||
'Must match server password.'))
|
||||
|
||||
method = forms.ChoiceField(
|
||||
label=_('Method'),
|
||||
choices=[(x, x) for x in METHODS],
|
||||
help_text=_('Encryption method. Must match setting on server.'))
|
||||
0
plinth/modules/shadowsocks/tests/__init__.py
Normal file
0
plinth/modules/shadowsocks/tests/__init__.py
Normal file
29
plinth/modules/shadowsocks/urls.py
Normal file
29
plinth/modules/shadowsocks/urls.py
Normal file
@ -0,0 +1,29 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Shadowsocks module.
|
||||
"""
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import ShadowsocksServiceView
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^apps/shadowsocks/$', ShadowsocksServiceView.as_view(),
|
||||
name='index'),
|
||||
]
|
||||
82
plinth/modules/shadowsocks/views.py
Normal file
82
plinth/modules/shadowsocks/views.py
Normal file
@ -0,0 +1,82 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Plinth module for configuring Shadowsocks.
|
||||
"""
|
||||
|
||||
import json
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .forms import ShadowsocksForm
|
||||
from plinth import actions
|
||||
from plinth import views
|
||||
from plinth.modules import shadowsocks
|
||||
|
||||
SHADOWSOCKS_CONFIG = '/etc/shadowsocks-libev/freedombox.json'
|
||||
|
||||
|
||||
class ShadowsocksServiceView(views.ServiceView):
|
||||
"""Configuration view for Shadowsocks local socks5 proxy."""
|
||||
service_id = 'shadowsocks'
|
||||
diagnostics_module_name = 'shadowsocks'
|
||||
form_class = ShadowsocksForm
|
||||
description = shadowsocks.description
|
||||
|
||||
def get_initial(self, *args, **kwargs):
|
||||
"""Get initial values for form."""
|
||||
try:
|
||||
configuration = open(SHADOWSOCKS_CONFIG, 'r').read()
|
||||
status = json.loads(configuration)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
status = {
|
||||
'server': '',
|
||||
'server_port': 8388,
|
||||
'password': '',
|
||||
'method': 'chacha20-ietf-poly1305',
|
||||
}
|
||||
|
||||
status['is_enabled'] = self.service.is_enabled()
|
||||
status['is_running'] = self.service.is_running()
|
||||
|
||||
return status
|
||||
|
||||
def form_valid(self, form):
|
||||
"""Configure Shadowsocks."""
|
||||
old_status = form.initial
|
||||
new_status = form.cleaned_data
|
||||
|
||||
if (old_status['server'] != new_status['server'] or
|
||||
old_status['server_port'] != new_status['server_port'] or
|
||||
old_status['password'] != new_status['password'] or
|
||||
old_status['method'] != new_status['method']):
|
||||
new_config = {
|
||||
'local_address': '::0',
|
||||
'local_port': 1080,
|
||||
'server': new_status['server'],
|
||||
'server_port': new_status['server_port'],
|
||||
'password': new_status['password'],
|
||||
'method': new_status['method'],
|
||||
}
|
||||
|
||||
actions.superuser_run(
|
||||
'shadowsocks', ['merge-config'],
|
||||
input=json.dumps(new_config).encode())
|
||||
messages.success(self.request, _('Configuration updated'))
|
||||
|
||||
return super().form_valid(form)
|
||||
BIN
static/themes/default/icons/shadowsocks.png
Normal file
BIN
static/themes/default/icons/shadowsocks.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Loading…
x
Reference in New Issue
Block a user