From eb28f47053e474bcfadecf9277a1a0a65b0c58b2 Mon Sep 17 00:00:00 2001 From: LoveIsGrief Date: Fri, 5 Apr 2019 15:47:25 +0200 Subject: [PATCH] i2p: Add helper to modify the tunnel config We will want to set the 'interface' property of certain tunnels to 0.0.0.0 and the handle the rest with the firewall. This is just prep to do so. Reviewed-by: Sunil Mohan Adapa --- plinth/modules/i2p/helpers.py | 119 +++++++++++ plinth/modules/i2p/tests/__init__.py | 16 ++ .../modules/i2p/tests/data/i2ptunnel.config | 192 ++++++++++++++++++ plinth/modules/i2p/tests/test_helpers.py | 64 ++++++ 4 files changed, 391 insertions(+) create mode 100644 plinth/modules/i2p/helpers.py create mode 100644 plinth/modules/i2p/tests/__init__.py create mode 100644 plinth/modules/i2p/tests/data/i2ptunnel.config create mode 100644 plinth/modules/i2p/tests/test_helpers.py diff --git a/plinth/modules/i2p/helpers.py b/plinth/modules/i2p/helpers.py new file mode 100644 index 000000000..76b1301cd --- /dev/null +++ b/plinth/modules/i2p/helpers.py @@ -0,0 +1,119 @@ +# 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 . +# +import os +import re + +import augeas + +I2P_CONF_DIR = '/var/lib/i2p/i2p-config' +FILE_TUNNEL_CONF = os.path.join(I2P_CONF_DIR, 'i2ptunnel.config') +TUNNEL_IDX_REGEX = re.compile(r'tunnel.(\d+).name$') + + +class TunnelEditor(object): + """ + + :type aug: augeas.Augeas + """ + + def __init__(self, conf_filename=None, idx=None): + self.conf_filename = conf_filename or FILE_TUNNEL_CONF + self.idx = idx + self.aug = None + + @property + def lines(self): + if self.aug: + return self.aug.match('/files{}/*'.format(self.conf_filename)) + else: + return [] + + def read_conf(self): + """Return an instance of Augeaus for processing APT configuration.""" + self.aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + + augeas.Augeas.NO_MODL_AUTOLOAD) + self.aug.set('/augeas/load/Properties/lens', 'Properties.lns') + self.aug.set('/augeas/load/Properties/incl[last() + 1]', self.conf_filename) + self.aug.load() + + return self + + def write_conf(self): + self.aug.save() + return self + + def set_tunnel_idx(self, name): + + """ + Finds the index of the tunnel with the given name. + + Chainable method. + + :type name: basestring + """ + + for prop in self.aug.match('/files{}/*'.format(self.conf_filename)): + match = TUNNEL_IDX_REGEX.search(prop) + + if match and self.aug.get(prop) == name: + self.idx = int(match.group(1)) + return self + raise ValueError('No tunnel with that name') + + def calc_prop_path(self, tunnel_prop): + """ + Calculates the property name as found in the properties files + :type tunnel_prop: str + :rtype: basestring + """ + calced_prop_path = '/files{filepath}/tunnel.{idx}.{tunnel_prop}'.format( + idx=self.idx, tunnel_prop=tunnel_prop, + filepath=self.conf_filename + ) + return calced_prop_path + + def set_tunnel_prop(self, tunnel_prop, value): + """ + Updates a tunnel's property. + + The idx has to be set and the property has to exist in the config file and belong to the tunnel's properties. + + see calc_prop_path + + Chainable method. + + :param tunnel_prop: + :type tunnel_prop: str + :param value: + :type value: basestring | int + :return: + :rtype: + """ + if self.idx is None: + raise ValueError('Please init the tunnel index before calling this method') + + calc_prop_path = self.calc_prop_path(tunnel_prop) + self.aug.set(calc_prop_path, value) + return self + + def __getitem__(self, tunnel_prop): + ret = self.aug.get(self.calc_prop_path(tunnel_prop)) + if ret is None: + raise KeyError('Unknown property {}'.format(tunnel_prop)) + return ret + + def __setitem__(self, tunnel_prop, value): + self.aug.set(self.calc_prop_path(tunnel_prop), value) diff --git a/plinth/modules/i2p/tests/__init__.py b/plinth/modules/i2p/tests/__init__.py new file mode 100644 index 000000000..019e3bfd7 --- /dev/null +++ b/plinth/modules/i2p/tests/__init__.py @@ -0,0 +1,16 @@ +# 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 . +# + diff --git a/plinth/modules/i2p/tests/data/i2ptunnel.config b/plinth/modules/i2p/tests/data/i2ptunnel.config new file mode 100644 index 000000000..467980059 --- /dev/null +++ b/plinth/modules/i2p/tests/data/i2ptunnel.config @@ -0,0 +1,192 @@ +# NOTE: This I2P config file must use UTF-8 encoding +tunnel.0.description=HTTP proxy for browsing eepsites and the web +tunnel.0.interface=127.0.0.1 +tunnel.0.listenPort=4444 +tunnel.0.name=I2P HTTP Proxy +tunnel.0.option.i2cp.closeIdleTime=1800000 +tunnel.0.option.i2cp.closeOnIdle=false +tunnel.0.option.i2cp.delayOpen=false +tunnel.0.option.i2cp.destination.sigType=EdDSA_SHA512_Ed25519 +tunnel.0.option.i2cp.newDestOnResume=false +tunnel.0.option.i2cp.reduceIdleTime=900000 +tunnel.0.option.i2cp.reduceOnIdle=true +tunnel.0.option.i2cp.reduceQuantity=1 +tunnel.0.option.i2p.streaming.connectDelay=1000 +tunnel.0.option.i2ptunnel.httpclient.SSLOutproxies=false.i2p +tunnel.0.option.i2ptunnel.httpclient.allowInternalSSL=true +tunnel.0.option.i2ptunnel.httpclient.jumpServers=http://stats.i2p/cgi-bin/jump.cgi?a=,http://no.i2p/jump/,http://i2pjump.i2p/jump/ +tunnel.0.option.i2ptunnel.httpclient.sendAccept=false +tunnel.0.option.i2ptunnel.httpclient.sendReferer=false +tunnel.0.option.i2ptunnel.httpclient.sendUserAgent=false +tunnel.0.option.i2ptunnel.useLocalOutproxy=false +tunnel.0.option.inbound.backupQuantity=0 +tunnel.0.option.inbound.length=3 +tunnel.0.option.inbound.lengthVariance=0 +tunnel.0.option.inbound.nickname=shared clients +tunnel.0.option.inbound.quantity=2 +tunnel.0.option.outbound.backupQuantity=0 +tunnel.0.option.outbound.length=3 +tunnel.0.option.outbound.lengthVariance=0 +tunnel.0.option.outbound.nickname=shared clients +tunnel.0.option.outbound.priority=10 +tunnel.0.option.outbound.quantity=2 +tunnel.0.option.outproxyAuth=false +tunnel.0.option.persistentClientKey=false +tunnel.0.option.sslManuallySet=true +tunnel.0.option.useSSL=false +tunnel.0.proxyList=false.i2p +tunnel.0.sharedClient=true +tunnel.0.startOnLoad=true +tunnel.0.type=httpclient +tunnel.1.description=IRC tunnel to access the Irc2P network +tunnel.1.i2cpHost=127.0.0.1 +tunnel.1.i2cpPort=7654 +tunnel.1.interface=127.0.0.1 +tunnel.1.listenPort=6668 +tunnel.1.name=Irc2P +tunnel.1.option.crypto.lowTagThreshold=14 +tunnel.1.option.crypto.tagsToSend=20 +tunnel.1.option.i2cp.closeIdleTime=1200000 +tunnel.1.option.i2cp.closeOnIdle=true +tunnel.1.option.i2cp.delayOpen=true +tunnel.1.option.i2cp.destination.sigType=ECDSA_SHA256_P256 +tunnel.1.option.i2cp.newDestOnResume=false +tunnel.1.option.i2cp.reduceIdleTime=600000 +tunnel.1.option.i2cp.reduceOnIdle=true +tunnel.1.option.i2cp.reduceQuantity=1 +tunnel.1.option.i2p.streaming.connectDelay=1000 +tunnel.1.option.i2p.streaming.maxWindowSize=16 +tunnel.1.option.inbound.length=3 +tunnel.1.option.inbound.lengthVariance=0 +tunnel.1.option.inbound.nickname=Irc2P +tunnel.1.option.outbound.length=3 +tunnel.1.option.outbound.lengthVariance=0 +tunnel.1.option.outbound.nickname=Irc2P +tunnel.1.option.outbound.priority=15 +tunnel.1.sharedClient=false +tunnel.1.startOnLoad=true +tunnel.1.targetDestination=irc.00.i2p:6667,irc.postman.i2p:6667,irc.echelon.i2p:6667 +tunnel.1.type=ircclient +tunnel.2.description=I2P Monotone Server +tunnel.2.i2cpHost=127.0.0.1 +tunnel.2.i2cpPort=7654 +tunnel.2.interface=127.0.0.1 +tunnel.2.listenPort=8998 +tunnel.2.name=mtn.i2p-projekt.i2p +tunnel.2.option.i2cp.destination.sigType=EdDSA_SHA512_Ed25519 +tunnel.2.option.i2cp.reduceIdleTime=900000 +tunnel.2.option.i2cp.reduceOnIdle=true +tunnel.2.option.i2cp.reduceQuantity=1 +tunnel.2.option.inbound.backupQuantity=0 +tunnel.2.option.inbound.length=3 +tunnel.2.option.inbound.lengthVariance=0 +tunnel.2.option.inbound.nickname=shared clients +tunnel.2.option.inbound.quantity=2 +tunnel.2.option.outbound.backupQuantity=0 +tunnel.2.option.outbound.length=3 +tunnel.2.option.outbound.lengthVariance=0 +tunnel.2.option.outbound.nickname=shared clients +tunnel.2.option.outbound.quantity=2 +tunnel.2.sharedClient=true +tunnel.2.startOnLoad=false +tunnel.2.targetDestination=mtn.i2p-projekt.i2p:4691 +tunnel.2.type=client +tunnel.3.description=My eepsite +tunnel.3.i2cpHost=127.0.0.1 +tunnel.3.i2cpPort=7654 +tunnel.3.name=I2P webserver +tunnel.3.option.i2cp.destination.sigType=7 +tunnel.3.option.i2p.streaming.limitAction=http +tunnel.3.option.i2p.streaming.maxConcurrentStreams=20 +tunnel.3.option.i2p.streaming.maxConnsPerDay=100 +tunnel.3.option.i2p.streaming.maxConnsPerHour=40 +tunnel.3.option.i2p.streaming.maxConnsPerMinute=10 +tunnel.3.option.i2p.streaming.maxTotalConnsPerMinute=25 +tunnel.3.option.inbound.length=3 +tunnel.3.option.inbound.lengthVariance=0 +tunnel.3.option.inbound.nickname=eepsite +tunnel.3.option.maxPosts=3 +tunnel.3.option.maxTotalPosts=10 +tunnel.3.option.outbound.length=3 +tunnel.3.option.outbound.lengthVariance=0 +tunnel.3.option.outbound.nickname=eepsite +tunnel.3.option.shouldBundleReplyInfo=false +tunnel.3.privKeyFile=eepsite/eepPriv.dat +tunnel.3.spoofedHost=mysite.i2p +tunnel.3.startOnLoad=false +tunnel.3.targetHost=127.0.0.1 +tunnel.3.targetPort=7658 +tunnel.3.type=httpserver +tunnel.4.description=smtp server +tunnel.4.i2cpHost=127.0.0.1 +tunnel.4.i2cpPort=7654 +tunnel.4.interface=127.0.0.1 +tunnel.4.listenPort=7659 +tunnel.4.name=smtp.postman.i2p +tunnel.4.option.i2cp.destination.sigType=EdDSA_SHA512_Ed25519 +tunnel.4.option.i2cp.reduceIdleTime=900000 +tunnel.4.option.i2cp.reduceOnIdle=true +tunnel.4.option.i2cp.reduceQuantity=1 +tunnel.4.option.inbound.backupQuantity=0 +tunnel.4.option.inbound.length=3 +tunnel.4.option.inbound.lengthVariance=0 +tunnel.4.option.inbound.nickname=shared clients +tunnel.4.option.inbound.quantity=2 +tunnel.4.option.outbound.backupQuantity=0 +tunnel.4.option.outbound.length=3 +tunnel.4.option.outbound.lengthVariance=0 +tunnel.4.option.outbound.nickname=shared clients +tunnel.4.option.outbound.quantity=2 +tunnel.4.sharedClient=true +tunnel.4.startOnLoad=true +tunnel.4.targetDestination=smtp.postman.i2p:25 +tunnel.4.type=client +tunnel.5.description=pop3 server +tunnel.5.i2cpHost=127.0.0.1 +tunnel.5.i2cpPort=7654 +tunnel.5.interface=127.0.0.1 +tunnel.5.listenPort=7660 +tunnel.5.name=pop3.postman.i2p +tunnel.5.option.i2cp.destination.sigType=EdDSA_SHA512_Ed25519 +tunnel.5.option.i2cp.reduceIdleTime=900000 +tunnel.5.option.i2cp.reduceOnIdle=true +tunnel.5.option.i2cp.reduceQuantity=1 +tunnel.5.option.i2p.streaming.connectDelay=1000 +tunnel.5.option.inbound.backupQuantity=0 +tunnel.5.option.inbound.length=3 +tunnel.5.option.inbound.lengthVariance=0 +tunnel.5.option.inbound.nickname=shared clients +tunnel.5.option.inbound.quantity=2 +tunnel.5.option.outbound.backupQuantity=0 +tunnel.5.option.outbound.length=3 +tunnel.5.option.outbound.lengthVariance=0 +tunnel.5.option.outbound.nickname=shared clients +tunnel.5.option.outbound.quantity=2 +tunnel.5.sharedClient=true +tunnel.5.startOnLoad=true +tunnel.5.targetDestination=pop.postman.i2p:110 +tunnel.5.type=client +tunnel.6.description=HTTPS proxy for browsing eepsites and the web +tunnel.6.i2cpHost=127.0.0.1 +tunnel.6.i2cpPort=7654 +tunnel.6.interface=127.0.0.1 +tunnel.6.listenPort=4445 +tunnel.6.name=I2P HTTPS Proxy +tunnel.6.option.i2cp.reduceIdleTime=900000 +tunnel.6.option.i2cp.reduceOnIdle=true +tunnel.6.option.i2cp.reduceQuantity=1 +tunnel.6.option.i2p.streaming.connectDelay=1000 +tunnel.6.option.inbound.backupQuantity=0 +tunnel.6.option.inbound.length=3 +tunnel.6.option.inbound.lengthVariance=0 +tunnel.6.option.inbound.nickname=shared clients +tunnel.6.option.inbound.quantity=2 +tunnel.6.option.outbound.backupQuantity=0 +tunnel.6.option.outbound.length=3 +tunnel.6.option.outbound.lengthVariance=0 +tunnel.6.option.outbound.nickname=shared clients +tunnel.6.option.outbound.quantity=2 +tunnel.6.proxyList=outproxy-tor.meeh.i2p +tunnel.6.sharedClient=true +tunnel.6.startOnLoad=true +tunnel.6.type=connectclient diff --git a/plinth/modules/i2p/tests/test_helpers.py b/plinth/modules/i2p/tests/test_helpers.py new file mode 100644 index 000000000..dc817c396 --- /dev/null +++ b/plinth/modules/i2p/tests/test_helpers.py @@ -0,0 +1,64 @@ +# 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 . +# +import unittest + +from pathlib import Path + +from plinth.modules.i2p.helpers import TunnelEditor + +DATA_DIR = Path(__file__).parent / 'data' +TUNNEL_CONF_PATH = DATA_DIR / 'i2ptunnel.config' +TUNNEL_HTTP_NAME = 'I2P HTTP Proxy' + + +class TunnelEditorTests(unittest.TestCase): + + def setUp(self): + self.editor = TunnelEditor(str(TUNNEL_CONF_PATH)) + + def test_reading_conf(self): + self.editor.read_conf() + self.assertGreater(len(self.editor.lines), 1) + + def test_setting_idx(self): + self.editor.read_conf() + self.assertIsNone(self.editor.idx) + self.editor.set_tunnel_idx(TUNNEL_HTTP_NAME) + self.assertEqual(self.editor.idx, 0) + + def test_setting_tunnel_props(self): + self.editor.read_conf() + self.editor.set_tunnel_idx('I2P HTTP Proxy') + interface = '0.0.0.0' + self.editor.set_tunnel_prop('interface', interface) + self.assertEqual(self.editor['interface'], interface) + + def test_getting_inexistent_props(self): + self.editor.read_conf() + self.editor.idx = 0 + self.assertRaises(KeyError, self.editor.__getitem__, 'blabla') + + def test_setting_new_props(self): + self.editor.read_conf() + self.editor.idx = 0 + value = 'lol' + prop = 'blablabla' + self.editor[prop] = value + self.assertEqual(self.editor[prop], value) + + +if __name__ == '__main__': + unittest.main()