1 # Fedi API Block - An aggregator for fetching blocking data from fediverse nodes
2 # Copyright (C) 2023 Free Software Foundation
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published
6 # by the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <https://www.gnu.org/licenses/>.
20 from fba import blacklist
21 from fba import blocks
23 from fba import federation
24 from fba import instances
25 from fba import network
27 from fba.helpers import tidyup
29 def fetch_blocks(domain: str, origin: str, nodeinfo_url: str):
30 # DEBUG: print(f"DEBUG: domain='{domain}',origin='{origin}',nodeinfo_url='{nodeinfo_url}' - CALLED!")
31 if not isinstance(domain, str):
32 raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
34 raise ValueError("Parameter 'domain' is empty")
35 elif not isinstance(origin, str) and origin is not None:
36 raise ValueError(f"Parameter origin[]='{type(origin)}' is not 'str'")
38 raise ValueError("Parameter 'origin' is empty")
39 elif not isinstance(nodeinfo_url, str):
40 raise ValueError(f"Parameter nodeinfo_url[]='{type(nodeinfo_url)}' is not 'str'")
41 elif nodeinfo_url == "":
42 raise ValueError("Parameter 'nodeinfo_url' is empty")
48 rows = federation.fetch_nodeinfo(domain, nodeinfo_url)
49 except network.exceptions as exception:
50 print(f"WARNING: Exception '{type(exception)}' during fetching nodeinfo")
53 print("WARNING: Could not fetch nodeinfo from domain:", domain)
55 elif "metadata" not in rows:
56 print(f"WARNING: rows()={len(rows)} does not have key 'metadata', domain='{domain}'")
58 elif "federation" not in rows["metadata"]:
59 print(f"WARNING: rows()={len(rows['metadata'])} does not have key 'federation', domain='{domain}'")
62 # DEBUG: print("DEBUG: Updating nodeinfo:", domain)
63 instances.update_last_nodeinfo(domain)
65 data = rows["metadata"]["federation"]
67 if "mrf_simple" in data:
68 # DEBUG: print("DEBUG: Found mrf_simple:", domain)
69 for block_level, blocklist in (
73 "quarantined_instances": data["quarantined_instances"]
77 # DEBUG: print("DEBUG: block_level, blocklist():", block_level, len(blocklist))
78 block_level = tidyup.domain(block_level)
79 # DEBUG: print("DEBUG: BEFORE block_level:", block_level)
82 print("WARNING: block_level is now empty!")
85 # DEBUG: print(f"DEBUG: Checking {len(blocklist)} entries from domain='{domain}',block_level='{block_level}' ...")
86 if len(blocklist) > 0:
87 for blocked in blocklist:
88 # DEBUG: print("DEBUG: BEFORE blocked:", blocked)
89 blocked = tidyup.domain(blocked)
90 # DEBUG: print("DEBUG: AFTER blocked:", blocked)
93 print("WARNING: blocked is empty after tidyup.domain():", domain, block_level)
95 elif blacklist.is_blacklisted(blocked):
96 # DEBUG: print(f"DEBUG: blocked='{blocked}' is blacklisted - skipping!")
98 elif blocked.count("*") > 0:
99 # Obsured domain name with no hash
100 # DEBUG: print(f"DEBUG: Trying to de-obscure blocked='{blocked}' ...")
102 "SELECT domain, nodeinfo_url FROM instances WHERE domain LIKE ? ORDER BY rowid LIMIT 1", [blocked.replace("*", "_")]
104 searchres = fba.cursor.fetchone()
106 # DEBUG: print(f"DEBUG: searchres[]='{type(searchres)}'")
107 if searchres is None:
108 print(f"WARNING: Cannot deobsfucate blocked='{blocked}' - SKIPPED!")
111 # DEBUG: print(f"DEBUG: blocked='{blocked}' de-obscured to '{searchres[0]}'")
112 blocked = searchres[0]
113 nodeinfo_url = searchres[1]
115 # DEBUG: print(f"DEBUG: blocked='{blocked}'")
116 if not validators.domain(blocked):
117 print(f"WARNING: blocked='{blocked}',software='pleroma' is not a valid domain name - skipped!")
119 elif blocked.split(".")[-1] == "arpa":
120 print(f"WARNING: blocked='{blocked}' is a reversed .arpa domain and should not be used generally.")
122 elif not instances.is_registered(blocked):
124 fba.connection.commit()
126 # DEBUG: print(f"DEBUG: Domain blocked='{blocked}' wasn't found, adding ..., domain='{domain}',origin='{origin}',nodeinfo_url='{nodeinfo_url}'")
127 instances.add(blocked, domain, inspect.currentframe().f_code.co_name, nodeinfo_url)
129 if not blocks.is_instance_blocked(domain, blocked, block_level):
130 # DEBUG: print("DEBUG: Blocking:", domain, blocked, block_level)
131 blocks.add_instance(domain, blocked, "unknown", block_level)
133 if block_level == "reject":
134 # DEBUG: print("DEBUG: Adding to blockdict:", blocked)
140 # DEBUG: print(f"DEBUG: Updating block last seen for domain='{domain}',blocked='{blocked}' ...")
141 blocks.update_last_seen(domain, blocked, block_level)
143 # DEBUG: print("DEBUG: Committing changes ...")
144 fba.connection.commit()
147 if "mrf_simple_info" in data:
148 # DEBUG: print("DEBUG: Found mrf_simple_info:", domain)
149 for block_level, info in (
151 **data["mrf_simple_info"],
152 **(data["quarantined_instances_info"] if "quarantined_instances_info" in data else {})
155 # DEBUG: print("DEBUG: block_level, info.items():", block_level, len(info.items()))
156 block_level = tidyup.domain(block_level)
157 # DEBUG: print("DEBUG: BEFORE block_level:", block_level)
159 if block_level == "":
160 print("WARNING: block_level is now empty!")
163 # DEBUG: print(f"DEBUG: Checking {len(info.items())} entries from domain='{domain}',software='pleroma',block_level='{block_level}' ...")
164 for blocked, reason in info.items():
165 # DEBUG: print(f"DEBUG: blocked='{blocked}',reason[{type(reason)}]='{reason}' - BEFORE!")
166 blocked = tidyup.domain(blocked)
168 if isinstance(reason, str):
169 # DEBUG: print("DEBUG: reason[] is a string")
170 reason = tidyup.reason(reason)
171 elif isinstance(reason, dict) and "reason" in reason:
172 # DEBUG: print("DEBUG: reason[] is a dict")
173 reason = tidyup.reason(reason["reason"])
174 elif reason is not None:
175 raise ValueError(f"Cannot handle reason[]='{type(reason)}'")
177 # DEBUG: print(f"DEBUG: blocked='{blocked}',reason='{reason}' - AFTER!")
180 print("WARNING: blocked is empty after tidyup.domain():", domain, block_level)
182 elif blacklist.is_blacklisted(blocked):
183 # DEBUG: print(f"DEBUG: blocked='{blocked}' is blacklisted - skipping!")
185 elif blocked.count("*") > 0:
186 # Obsured domain with no hash
187 # DEBUG: print(f"DEBUG: Trying to de-obscure blocked='{blocked}' ...")
189 "SELECT domain, origin, nodeinfo_url FROM instances WHERE domain LIKE ? ORDER BY rowid LIMIT 1", [blocked.replace("*", "_")]
191 searchres = fba.cursor.fetchone()
193 # DEBUG: print(f"DEBUG: searchres[]='{type(searchres)}'")
194 if searchres is None:
195 print(f"WARNING: Cannot deobsfucate blocked='{blocked}' - SKIPPED!")
198 # DEBUG: print(f"DEBUG: blocked='{blocked}' de-obscured to '{searchres[0]}'")
199 blocked = searchres[0]
200 origin = searchres[1]
201 nodeinfo_url = searchres[2]
202 elif not validators.domain(blocked):
203 print(f"WARNING: blocked='{blocked}',software='pleroma' is not a valid domain name - skipped!")
206 # DEBUG: print(f"DEBUG: blocked='{blocked}'")
207 if blocked.split(".")[-1] == "arpa":
208 print(f"WARNING: blocked='{blocked}' is a reversed .arpa domain and should not be used generally.")
210 elif not instances.is_registered(blocked):
211 # DEBUG: print(f"DEBUG: Domain blocked='{blocked}' wasn't found, adding ..., domain='{domain}',origin='{origin}',nodein
212 instances.add(blocked, domain, inspect.currentframe().f_code.co_name, nodeinfo_url)
214 # DEBUG: print(f"DEBUG: Updating block reason: reason='{reason}',domain='{domain}',blocked='{blocked}',block_level='{block_level}'")
215 blocks.update_reason(reason, domain, blocked, block_level)
217 # DEBUG: print(f"DEBUG: blockdict()={len(blockdict)}")
218 for entry in blockdict:
219 if entry["blocked"] == blocked:
220 # DEBUG: print(f"DEBUG: Updating entry reason: blocked='{blocked}',reason='{reason}'")
221 entry["reason"] = reason
223 fba.connection.commit()
224 # DEBUG: print("DEBUG: EXIT!")