]> git.mxchange.org Git - fba.git/blob - fba/helpers/processing.py
Continued:
[fba.git] / fba / helpers / processing.py
1         # Copyright (C) 2023 Free Software Foundation
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License as published
5 # by the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU Affero General Public License for more details.
12 #
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16 import csv
17 import logging
18
19 import validators
20
21 from fba import database
22 from fba import utils
23
24 from fba.helpers import blacklist
25 from fba.helpers import blocklists
26 from fba.helpers import config
27 from fba.helpers import domain as domain_helper
28 from fba.helpers import tidyup
29
30 from fba.http import federation
31 from fba.http import network
32
33 from fba.models import blocks
34 from fba.models import instances
35
36 logging.basicConfig(level=logging.INFO)
37 logger = logging.getLogger(__name__)
38
39 def instance(blocked: str, blocker: str, command: str) -> bool:
40     logger.debug("blocked='%s',blocker='%s',command='%s' - CALLED!", blocked, blocker, command)
41     domain_helper.raise_on(blocked)
42     domain_helper.raise_on(blocker)
43
44     if not isinstance(command, str):
45         raise ValueError(f"Parameter command[]='{type(command)}' is not of type 'str'")
46     elif command == "":
47         raise ValueError("Parameter 'command' is empty")
48
49     logger.debug("blocked='%s' - BEFORE!", blocked)
50     blocked = utils.deobfuscate(blocked, blocker)
51
52     logger.debug("blocked='%s' - DEOBFUSCATED!", blocked)
53     if instances.has_pending(blocker):
54         logger.debug("Flushing updates for blocker='%s' ...", blocker)
55         instances.update(blocker)
56
57     if not domain_helper.is_wanted(blocked):
58         logger.debug("blocked='%s' is not wanted - SKIPPED!", blocked)
59         return False
60     elif instances.is_recent(blocked):
61         logger.debug("blocked='%s' has been recently checked - SKIPPED!", blocked)
62         return False
63
64     processed = False
65     try:
66         logger.info("Fetching instances for blocked='%s',blocker='%s',command='%s' ...", blocked, blocker, command)
67         federation.fetch_instances(blocked, blocker, None, command)
68         processed = True
69     except network.exceptions as exception:
70         logger.warning("Exception '%s' during fetching instances (%s) from blocked='%s'", type(exception), command, blocked)
71         instances.set_last_error(blocked, exception)
72
73     logger.debug("Checking if blocked='%s' has pending updates ...", blocked)
74     if instances.has_pending(blocked):
75         logger.debug("Flushing updates for blocked='%s' ...", blocked)
76         instances.update(blocked)
77
78     logger.debug("processed='%s' - EXIT!", processed)
79     return processed
80
81 def block(blocker: str, blocked: str, reason: str, block_level: str) -> bool:
82     logger.debug("blocker='%s',blocked='%s',reason='%s',block_level='%s' - CALLED!", blocker, blocked, reason, block_level)
83     domain_helper.raise_on(blocker)
84     domain_helper.raise_on(blocked)
85
86     if not isinstance(reason, str) and reason is not None:
87         raise ValueError(f"Parameter reason[]='{type(reason)}' is not of type 'str'")
88     elif not isinstance(block_level, str):
89         raise ValueError(f"Parameter block_level[]='{type(block_level)}' is not of type 'str'")
90     elif block_level == "":
91         raise ValueError("Parameter block_level is empty")
92     elif blacklist.is_blacklisted(blocked):
93         raise ValueError(f"blocked='{blocked}' is blacklisted but function was invoked")
94
95     added = False
96     if not blocks.is_instance_blocked(blocker, blocked, block_level):
97         logger.debug("Invoking blocks.add(%s, %s, %s, %s) ...", blocker, blocked, reason, block_level)
98         blocks.add(blocker, blocked, reason, block_level)
99         added = True
100     else:
101         logger.debug("Updating last_seen for blocker='%s',blocked='%s',block_level='%s' ...", blocker, blocked, block_level)
102         blocks.update_last_seen(blocker, blocked, block_level)
103
104     logger.debug("added='%s' - EXIT!", added)
105     return added
106
107 def csv_block(blocker: str, url: str, command: str):
108     logger.debug("blocker='%s',url='%s',command='%s' - CALLED!", blocker, url, command)
109     domain_helper.raise_on(blocker)
110
111     if not isinstance(url, str):
112         raise ValueError(f"url[]='{url}' is not of type 'str'")
113     elif url == "":
114         raise ValueError("Parameter 'url' is empty")
115     elif not isinstance(command, str):
116         raise ValueError(f"command[]='{command}' is not of type 'str'")
117     elif command == "":
118         raise ValueError("Parameter 'command' is empty")
119
120     logger.debug("Setting last_blocked for blocker='%s' ...", blocker)
121     instances.set_last_blocked(blocker)
122
123     domains = list()
124
125     # Fetch this URL
126     logger.info("Fetching url='%s' for blocker='%s' ...", url, blocker)
127     response = utils.fetch_url(
128         url,
129         network.web_headers,
130         (config.get("connection_timeout"), config.get("read_timeout"))
131     )
132
133     logger.debug("response.ok='%s',response.status_code=%d,response.content()=%d", response.ok, response.status_code, len(response.content))
134     if not response.ok or response.status_code > 200 or response.content == "":
135         logger.warning("Could not fetch url='%s' for blocker='%s' - EXIT!", url, blocker)
136         return
137
138     logger.debug("Fetched %d Bytes, parsing CSV ...", len(response.content))
139     reader = csv.DictReader(response.content.decode("utf-8").splitlines(), dialect="unix")
140
141     blockdict = list()
142
143     cnt = 0
144     for row in reader:
145         logger.debug("row[%s]='%s'", type(row), row)
146         domain = severity = reason = None
147         reject_media = reject_reports = False
148
149         if "#domain" in row:
150             domain = tidyup.domain(row["#domain"]) if row["#domain"] != None and row["#domain"] != "" else None
151         elif "domain" in row:
152             domain = tidyup.domain(row["domain"]) if row["domain"] != None and row["domain"] != "" else None
153         elif "Domain" in row:
154             domain = tidyup.domain(row["Domain"]) if row["Domain"] != None and row["Domain"] != "" else None
155         else:
156             logger.warning("row='%s' does not contain domain column - SKIPPED!", row)
157             continue
158
159         if "#severity" in row:
160             severity = blocks.alias_block_level(row["#severity"])
161         elif "severity" in row:
162             severity = blocks.alias_block_level(row["severity"])
163         else:
164             logger.debug("row='%s' does not contain severity column, setting 'reject'", row)
165             severity = "reject"
166
167         if "reason" in row:
168             reason = tidup.reason(row["reason"]) if row["reason"] != None and row["reason"] != "" else None
169         elif "comment" in row:
170             reason = tidup.reason(row["comment"]) if row["comment"] != None and row["comment"] != "" else None
171         else:
172             logger.debug("row='%s' has no reason/comment key provided", row)
173
174         if "#reject_media" in row and row["#reject_media"].lower() == "true":
175             reject_media = True
176         elif "reject_media" in row and row["reject_media"].lower() == "true":
177             reject_media = True
178
179         if "#reject_reports" in row and row["#reject_reports"].lower() == "true":
180             reject_reports = True
181         elif "reject_reports" in row and row["reject_reports"].lower() == "true":
182             reject_reports = True
183
184         cnt = cnt + 1
185         logger.debug("domain='%s',severity='%s',reject_media='%s',reject_reports='%s'", domain, severity, reject_media, reject_reports)
186         if domain is None or domain == "":
187             logger.debug("domain='%s' is empty - SKIPPED!", domain)
188             continue
189         elif domain.endswith(".onion"):
190             logger.debug("domain='%s' is a TOR .onion domain - SKIPPED", domain)
191             continue
192         elif domain.endswith(".arpa"):
193             logger.debug("domain='%s' is a reverse IP address - SKIPPED", domain)
194             continue
195         elif domain.endswith(".tld"):
196             logger.debug("domain='%s' is a fake domain - SKIPPED", domain)
197             continue
198         elif domain.find("*") >= 0 or domain.find("?") >= 0:
199             logger.debug("domain='%s' is obfuscated - Invoking utils.deobfuscate(%s, %s) ...", domain, domain, blocker)
200             domain = utils.deobfuscate(domain, blocker)
201             logger.debug("domain='%s' - AFTER!", domain)
202
203         if not validators.domain(domain):
204             logger.debug("domain='%s' is not a valid domain - SKIPPED!")
205             continue
206         elif blacklist.is_blacklisted(domain):
207             logger.warning("domain='%s' is blacklisted - SKIPPED!", domain)
208             continue
209         elif blocks.is_instance_blocked(blocker, domain, severity):
210             logger.debug("blocker='%s' has already blocked domain='%s' with severity='%s' - SKIPPED!", blocker, domain, severity)
211             continue
212
213         logger.debug("Marking domain='%s' as handled", domain)
214         domains.append(domain)
215
216         logger.debug("Processing domain='%s',blocker='%s',command='%s' ...", domain, blocker, command)
217         processed = instance(domain, blocker, command)
218         logger.debug("processed='%s'", processed)
219
220         if block(blocker, domain, reason, severity) and config.get("bot_enabled"):
221             logger.debug("Appending blocked='%s',reason='%s' for blocker='%s' ...", domain, reason, blocker)
222             blockdict.append({
223                 "blocked": domain,
224                 "reason" : reason,
225             })
226
227         if reject_media:
228             block(blocker, domain, None, "reject_media")
229         if reject_reports:
230             block(blocker, domain, None, "reject_reports")
231
232     logger.debug("blocker='%s'", blocker)
233     if not blocklists.has(blocker):
234         logger.debug("Invoking instances.set_total_blocks(%s, domains()=%d) ...", blocker, len(domains))
235         instances.set_total_blocks(blocker, domains)
236
237     logger.debug("Checking if blocker='%s' has pending updates ...", blocker)
238     if instances.has_pending(blocker):
239         logger.debug("Flushing updates for blocker='%s' ...", blocker)
240         instances.update(blocker)
241
242     logger.debug("Invoking commit() ...")
243     database.connection.commit()
244
245     logger.debug("config.get(bot_enabled)='%s',blockdict()=%d", config.get("bot_enabled"), len(blockdict))
246     if config.get("bot_enabled") and len(blockdict) > 0:
247         logger.info("Sending bot POST for blocker='%s',blockdict()=%d ...", blocker, len(blockdict))
248         network.send_bot_post(blocker, blockdict)
249
250     logger.debug("EXIT!")