diff --git a/src/httpapi/security/lan.py b/src/httpapi/security/lan.py new file mode 100644 index 00000000..1989188c --- /dev/null +++ b/src/httpapi/security/lan.py @@ -0,0 +1,66 @@ +"""Onionr - Private P2P Communication. + +Process incoming requests to the public api server for certain attacks +""" +from flask import Blueprint, request, abort, g +from onionrservices import httpheaders +from onionrutils import epoch +from lan import getip +""" + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +""" + + +class LANAPISecurity: + def __init__(self, lan_client): + lan_api_security_bp = Blueprint('lanapisecurity', __name__) + self.lan_api_security_bp = lan_api_security_bp + + @lan_api_security_bp.before_app_request + def validate_request(): + """Validate request has the correct hostname""" + # If high security level, deny requests to public + # (HS should be disabled anyway for Tor, but might not be for I2P) + transports = getip.lan_ips + if lan_client.config.get('general.security_level', default=1) > 0: + abort(403) + if request.host not in transports: + # Abort conn if wrong HTTP hostname, to prevent DNS rebinding + abort(403) + lan_client.hitCount += 1 # raise hit count for valid requests + try: + if 'onionr' in request.headers['User-Agent'].lower(): + g.is_onionr_client = True + else: + g.is_onionr_client = False + except KeyError: + g.is_onionr_client = False + + @lan_api_security_bp.after_app_request + def send_headers(resp): + """Send api, access control headers""" + resp = httpheaders.set_default_onionr_http_headers(resp) + # Network API version + resp.headers['X-API'] = lan_client.API_VERSION + # Delete some HTTP headers for Onionr user agents + NON_NETWORK_HEADERS = ('Content-Security-Policy', 'X-Frame-Options', + 'X-Content-Type-Options', 'Feature-Policy', + 'Clear-Site-Data', 'Referrer-Policy') + try: + if g.is_onionr_client: + for header in NON_NETWORK_HEADERS: del resp.headers[header] + except AttributeError: + abort(403) + lan_client.lastRequest = epoch.get_rounded_epoch(roundS=5) + return resp diff --git a/src/lan/client/__init__.py b/src/lan/client/__init__.py index 8604f12b..74e904c1 100644 --- a/src/lan/client/__init__.py +++ b/src/lan/client/__init__.py @@ -4,7 +4,9 @@ LAN transport client thread """ from typing import List +from onionrcrypto.cryptoutils.randomshuffle import random_shuffle from utils.bettersleep import better_sleep +from onionrutils.basicrequests import do_post_request, do_get_request """ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,9 +26,20 @@ from utils.bettersleep import better_sleep class Client: def __init__(self): self.peers = [] - return + self.lookup_time = {} + self.poll_delay = 10 + + def get_lookup_time(self, peer): + try: + return self.lookup_time[peer] + except KeyError: + return 0 def start(self): while True: - #print(1, self.peers, type(self.peers)) - better_sleep(1) + self.peers = random_shuffle(self.peers) + + + + better_sleep(self.pull_delay) + diff --git a/src/onionrutils/basicrequests.py b/src/onionrutils/basicrequests.py index 13d65769..702cf0b2 100644 --- a/src/onionrutils/basicrequests.py +++ b/src/onionrutils/basicrequests.py @@ -1,8 +1,14 @@ -''' - Onionr - Private P2P Communication +'''Onionr - Private P2P Communication. Do HTTP GET or POST requests through a proxy ''' +from ipaddress import IPv4Address +from urllib.parse import urlparse + +import requests, streamedrequests +import logger, onionrexceptions +from etc import onionrvalues +from . import localcommand ''' This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,20 +23,22 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import requests, streamedrequests -import logger, onionrexceptions -from etc import onionrvalues -from . import localcommand + + def do_post_request(url, data={}, port=0, proxyType='tor', max_size=10000, content_type: str = ''): - ''' - Do a POST request through a local tor or i2p instance - ''' + '''Do a POST request through a local tor or i2p instance.''' if proxyType == 'tor': if port == 0: port = localcommand.local_command('/gettorsocks') proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)} elif proxyType == 'i2p': proxies = {'http': 'http://127.0.0.1:4444'} + elif proxyType == 'lan': + address = urlparse(url).hostname + if IPv4Address(address).is_private and not IPv4Address(address).is_loopback: + proxies = {} + else: + return else: return headers = {'User-Agent': 'PyOnionr', 'Connection':'close'} @@ -48,6 +56,7 @@ def do_post_request(url, data={}, port=0, proxyType='tor', max_size=10000, conte retData = False return retData + def do_get_request(url, port=0, proxyType='tor', ignoreAPI=False, returnHeaders=False, max_size=5242880): ''' Do a get request through a local tor or i2p instance @@ -56,10 +65,16 @@ def do_get_request(url, port=0, proxyType='tor', ignoreAPI=False, returnHeaders= retData = False if proxyType == 'tor': if port == 0: - raise onionrexceptions.MissingPort('Socks port required for Tor HTTP get request') + port = localcommand.local_command('/gettorsocks') proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)} elif proxyType == 'i2p': proxies = {'http': 'http://127.0.0.1:4444'} + elif proxyType == 'lan': + address = urlparse(url).hostname + if IPv4Address(address).is_private and not IPv4Address(address).is_loopback: + proxies = {} + else: + return else: return headers = {'User-Agent': 'PyOnionr', 'Connection':'close'}