#!/usr/bin/python3 -OO # Copyright 2007-2023 The SABnzbd-Team (sabnzbd.org) # # 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 2 # 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, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ tests.test_interface - Testing functions in interface.py """ import cherrypy from sabnzbd import interface from sabnzbd.misc import is_local_addr, is_loopback_addr from tests.testhelper import * class TestInterfaceFunctions: @pytest.mark.parametrize( "remote_ip, local_ranges, xff_header, result_with_xff", [ ("10.11.12.13", None, None, True), ("10.11.12.13", None, "127.0.0.1", True), ("10.11.12.13", None, "127.1.2.3", True), ("10.11.12.13", None, "127.0.0.1:8080", False), # Port number in XFF ("10.11.12.13", None, "::1", True), ("10.11.12.13", None, "[::1]", True), ("10.11.12.13", None, "[::1]:8080", False), # Port number in XFF ("10.11.12.13", None, "localhost", False), # Hostname in XFF ("10.11.12.13", None, "example.org", False), # Hostname in XFF ("10.11.12.13", None, "192.168.1.1", True), ("10.11.12.13", None, "10.11.12.99", True), ("10.11.12.13", None, "8.7.6.5", False), # XFF IP isn't local ("10.11.12.13", None, "192.168.1.1, 10.11.12.13", True), ("10.11.12.13", None, "192.168.1.1, 10.11.12.13, 9.8.7.6", False), # Last XFF IP isn't local ("10.11.12.13", None, "192.168.1.1, 10.11.12.13, ::1", True), ("10.11.12.13", None, "192.168.1.1, 10.11.12.13, sabrules.example.org", False), # Hostname in XFF ("10.11.12.13", "192.168.1.0/24", None, False), # Remote IP not part of local ranges ("10.11.12.13", "192.168.1.0/24", "192.168.1.23", False), ("10.11.12.13", "192.168.1.0/24", "192.168.1.23, 10.11.12.1", False), ("10.11.12.13", "192.168.1.0/24, 10.0.0.0/8", "192.168.1.23", True), ("10.11.12.13", "192.168.2.0/24, 10.0.0.0/8", "192.168.1.23", False), ("10.11.12.13", "192.168.1.0/24, 10.0.0.0/24", "192.168.1.23", False), ("10.11.12.13", "10.11.12.0/24", "192.168.1.23", False), ("10.11.12.13", "2001:ffff::/64", None, False), ("10.11.12.13", "2001:ffff::/64, 192.168.1.0/24", None, False), ("13.12.11.10", None, None, False), # Public remote IP doesn't have access, XFF ignored altogether ("13.12.11.10", None, "127.0.0.1", False), ("13.12.11.10", None, "127.1.2.3", False), ("13.12.11.10", None, "::1", False), ("13.12.11.10", None, "[::1]", False), ("13.12.11.10", None, "localhost", False), ("13.12.11.10", None, "192.168.1.1", False), ("13.12.11.10", None, "192.168.1.1, 13.12.11.10", False), ("13.12.11.10", None, "192.168.1.1, 13.12.11.10, ::1", False), ("13.12.11.10", None, "2001::/16", False), ("13.12.11.10", None, "2001::/16, 13.12.11.10", False), ("13.12.11.10", None, "2001::/16, 13.0.0.0/9", False), ("13.12.11.10", "13.12.11.10", None, True), # Local ranges include a public IP ("13.12.11.10", "13.12.11.10, 192.168.255.0/24", None, True), ("13.12.11.10", "13.12.11.10", "192.168.1.1", False), # XFF not in local ranges ("13.12.11.10", "13.12.11.10, 192.168.255.0/24", "192.168.1.1", False), ("13.12.11.10", "13.12.11.10", "192.168.1.1, 9.8.7.6", False), ("13.12.11.10", "13.12.11.10, 192.168.255.0/24", "192.168.1.1, 9.8.7.6", False), ("13.12.11.10", "13.0.0.0/12", None, True), ("13.12.11.10", "13.0.0.0/12, 192.168.255.0/24", None, True), ("13.12.11.10", "13.0.0.0/12", "192.168.1.1", False), # XFF not in local ranges ("13.12.11.10", "13.0.0.0/12, 192.168.255.0/24", "192.168.1.1", False), ("13.12.11.10", "13.0.0.0/12", "192.168.1.1, 9.8.7.6", False), ("13.12.11.10", "13.0.0.0/12, 192.168.255.0/24", "192.168.1.1, 9.8.7.6", False), ("127.6.6.6", None, None, True), ("127.6.6.6", None, "127.0.0.1", True), ("127.6.6.6", None, "127.1.2.3", True), ("127.6.6.6", None, "127.0.0.1:8080", False), # Port number in XFF ("127.6.6.6", None, "::1", True), ("127.6.6.6", None, "[::1]", True), ("127.6.6.6", None, "[::1]:8080", False), # Port number in XFF ("127.6.6.6", None, "localhost", False), # Hostname in XFF ("127.6.6.6", None, "example.org", False), # Hostname in XFF ("127.6.6.6", None, "192.168.1.1", True), ("127.6.6.6", None, "10.11.12.99", True), ("127.6.6.6", None, "8.7.6.5", False), # XFF IP isn't local ("127.6.6.6", None, "192.168.1.1, 127.6.6.6", True), ("127.6.6.6", None, "192.168.1.1, 127.6.6.6, 9.8.7.6", False), # Last XFF IP isn't local ("127.6.6.6", None, "192.168.1.1, 127.6.6.6, ::1", True), ("127.6.6.6", None, "192.168.1.1, 127.6.6.6, sabrules.example.org", False), # Hostname in XFF ("127.6.6.6", "192.168.1.0/24", None, True), # Remote IP is loopback, local ranges be damned ("127.6.6.6", "192.168.1.0/24", "192.168.1.23", True), ("127.6.6.6", "192.168.1.0/24", "192.168.1.23, 127.0.0.1", True), ("127.6.6.6", "192.168.1.0/24, 127.0.0.0/8", "192.168.1.23", True), ("127.6.6.6", "192.168.2.0/24, 127.0.0.0/8", "192.168.1.23", False), # Access denied by XFF ("127.6.6.6", "192.168.2.0/24, 127.0.0.0/8", "5.6.7.8", False), # Idem ("127.6.6.6", "192.168.1.0/24, 127.0.0.0/8", "192.168.1.23, 5.6.7.8", False), # Idem ("127.6.6.6", "192.168.1.0/24, 10.0.0.0/24", "::1", True), ("127.6.6.6", "127.6.6.0/24", "192.168.1.23", False), # Access denied by XFF ("127.6.6.6", "2001:ffff::/32", None, True), ("127.6.6.6", "2001:ffff::/32, 192.168.1.0/24", None, True), ("127.6.6.6", "2001:ffff::/32", "2001:ffff:a:b:c:d:e:f", True), ("127.6.6.6", "2001:ffff::/32, 192.168.1.0/24", "2001:ffff:a:b:c:d:e:f, 192.168.1.1", True), ("127.6.6.6", "2001:ffff::/32", "666:ffff:a:b:c:d:e:f", False), # Access denied by XFF ("127.6.6.6", "2001:ffff::/32, 192.168.1.0/24", "666:ffff:a:b:c:d:e:f, 192.168.1.1", False), # Idem ("DEAD:BEEF:2023:007::1", None, None, False), # Back to ignoring XFF altogether ("DEAD:BEEF:2023:007::1", None, "127.0.0.1", False), # XFF is loopback ("DEAD:BEEF:2023:007::1", None, "127.1.2.3", False), ("DEAD:BEEF:2023:007::1", None, "::1", False), ("DEAD:BEEF:2023:007::1", None, "[::1]", False), ("DEAD:BEEF:2023:007::1", None, "localhost", False), # Hostname in XFF ("DEAD:BEEF:2023:007::1", None, "192.168.1.1", False), ("DEAD:BEEF:2023:007::1", None, "192.168.1.1, DEAD:BEEF:2023:0007::1", False), ("DEAD:BEEF:2023:007::1", None, "192.168.1.1, DEAD:BEEF:2023:0007::1, ::1", False), ("DEAD:BEEF:2023:007::1", None, "2001::/16", False), ("DEAD:BEEF:2023:007::1", "dead:beef::/32", None, True), # Local ranges include a public IPv6 ("DEAD:BEEF:2023:007::1", "dead:beef::/32", "127.0.0.1", True), # XFF is loopback ("DEAD:BEEF:2023:007::1", "dead:beef::/32", "127.1.2.3", True), ("DEAD:BEEF:2023:007::1", "dead:beef::/32", "::1", True), ("DEAD:BEEF:2023:007::1", "dead:beef::/32", "[::1]", True), ("DEAD:BEEF:2023:007::1", "dead:beef::/32", "localhost", False), # Hostname in XFF ("DEAD:BEEF:2023:007::1", "dead:beef::/32", "192.168.1.1", False), ("DEAD:BEEF:2023:007::1", "dead:beef::/32", "192.168.1.1, DEAD:BEEF:2023:0007::1", False), ("DEAD:BEEF:2023:007::1", "dead:beef::/32", "192.168.1.1, DEAD:BEEF:2023:0007::1, ::1", False), ("DEAD:BEEF:2023:007::1", "dead:beef::/32", "DEAD::/16", False), # Netmask in XFF ("DEAD:BEEF:2023:007::1", "dead:beef::/32", "DEAD:BEEF:2023:7::42", True), # XFF in local ranges ], ) @pytest.mark.parametrize("access_type", [1, 2, 3, 4, 5, 6]) @pytest.mark.parametrize("inet_exposure", [0, 1, 2, 3, 4, 5]) @pytest.mark.parametrize("verify_xff_header", [False, True]) def test_check_access( self, access_type, inet_exposure, local_ranges, remote_ip, xff_header, verify_xff_header, result_with_xff ): @set_config( { "local_ranges": local_ranges, "inet_exposure": inet_exposure, "verify_xff_header": verify_xff_header, } ) def _func(): # Insert fake request data cherrypy.request.remote.ip = remote_ip cherrypy.request.headers.update({"X-Forwarded-For": xff_header}) if verify_xff_header: result = result_with_xff else: # Without XFF, only the remote IP and the local ranges setting matter result = is_loopback_addr(remote_ip) or is_local_addr(remote_ip) if access_type <= inet_exposure: assert interface.check_access(access_type) is True else: assert interface.check_access(access_type) is result _func()