| 
									
										
										
										
											2023-06-03 13:43:34 +02:00
										 |  |  | # SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | """.. _botdetection.ip_lists:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Method ``ip_lists`` | 
					
						
							|  |  |  | ------------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The ``ip_lists`` method implements IP :py:obj:`block- <block_ip>` and | 
					
						
							|  |  |  | :py:obj:`pass-lists <pass_ip>`. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. code:: toml | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |    [botdetection.ip_lists] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |    pass_ip = [ | 
					
						
							| 
									
										
										
										
											2023-12-11 14:22:10 +01:00
										 |  |  |     '167.235.158.251', # IPv4 of check.searx.space | 
					
						
							| 
									
										
										
										
											2023-06-03 13:43:34 +02:00
										 |  |  |     '192.168.0.0/16',  # IPv4 private network | 
					
						
							|  |  |  |     'fe80::/10'        # IPv6 linklocal | 
					
						
							|  |  |  |    ] | 
					
						
							|  |  |  |    block_ip = [ | 
					
						
							|  |  |  |       '93.184.216.34', # IPv4 of example.org | 
					
						
							|  |  |  |       '257.1.1.1',     # invalid IP --> will be ignored, logged in ERROR class | 
					
						
							|  |  |  |    ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | # pylint: disable=unused-argument | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from __future__ import annotations | 
					
						
							|  |  |  | from typing import Tuple | 
					
						
							|  |  |  | from ipaddress import ( | 
					
						
							|  |  |  |     ip_network, | 
					
						
							|  |  |  |     IPv4Address, | 
					
						
							|  |  |  |     IPv6Address, | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-02 16:36:07 +02:00
										 |  |  | from . import config | 
					
						
							| 
									
										
										
										
											2023-06-03 13:43:34 +02:00
										 |  |  | from ._helpers import logger | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | logger = logger.getChild('ip_limit') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SEARXNG_ORG = [ | 
					
						
							|  |  |  |     # https://github.com/searxng/searxng/pull/2484#issuecomment-1576639195 | 
					
						
							| 
									
										
										
										
											2023-12-11 14:22:10 +01:00
										 |  |  |     '167.235.158.251',  # IPv4 check.searx.space | 
					
						
							|  |  |  |     '2a01:04f8:1c1c:8fc2::/64',  # IPv6 check.searx.space | 
					
						
							| 
									
										
										
										
											2023-06-03 13:43:34 +02:00
										 |  |  | ] | 
					
						
							|  |  |  | """Passlist of IPs from the SearXNG organization, e.g. `check.searx.space`.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def pass_ip(real_ip: IPv4Address | IPv6Address, cfg: config.Config) -> Tuple[bool, str]: | 
					
						
							|  |  |  |     """Checks if the IP on the subnet is in one of the members of the
 | 
					
						
							|  |  |  |     ``botdetection.ip_lists.pass_ip`` list. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if cfg.get('botdetection.ip_lists.pass_searxng_org', default=True): | 
					
						
							|  |  |  |         for net in SEARXNG_ORG: | 
					
						
							|  |  |  |             net = ip_network(net, strict=False) | 
					
						
							|  |  |  |             if real_ip.version == net.version and real_ip in net: | 
					
						
							|  |  |  |                 return True, f"IP matches {net.compressed} in SEARXNG_ORG list." | 
					
						
							|  |  |  |     return ip_is_subnet_of_member_in_list(real_ip, 'botdetection.ip_lists.pass_ip', cfg) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def block_ip(real_ip: IPv4Address | IPv6Address, cfg: config.Config) -> Tuple[bool, str]: | 
					
						
							|  |  |  |     """Checks if the IP on the subnet is in one of the members of the
 | 
					
						
							|  |  |  |     ``botdetection.ip_lists.block_ip`` list. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     block, msg = ip_is_subnet_of_member_in_list(real_ip, 'botdetection.ip_lists.block_ip', cfg) | 
					
						
							|  |  |  |     if block: | 
					
						
							|  |  |  |         msg += " To remove IP from list, please contact the maintainer of the service." | 
					
						
							|  |  |  |     return block, msg | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def ip_is_subnet_of_member_in_list( | 
					
						
							|  |  |  |     real_ip: IPv4Address | IPv6Address, list_name: str, cfg: config.Config | 
					
						
							|  |  |  | ) -> Tuple[bool, str]: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for net in cfg.get(list_name, default=[]): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             net = ip_network(net, strict=False) | 
					
						
							|  |  |  |         except ValueError: | 
					
						
							|  |  |  |             logger.error("invalid IP %s in %s", net, list_name) | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         if real_ip.version == net.version and real_ip in net: | 
					
						
							|  |  |  |             return True, f"IP matches {net.compressed} in {list_name}." | 
					
						
							|  |  |  |     return False, f"IP is not a member of an item in the f{list_name} list" |