]> 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         elif "Comment" in row:
172             reason = tidup.reason(row["Comment"]) if row["Comment"] != None and row["Comment"] != "" else None
173         else:
174             logger.debug("row='%s' has no reason/comment key provided", row)
175
176         if "#reject_media" in row and row["#reject_media"].lower() == "true":
177             reject_media = True
178         elif "reject_media" in row and row["reject_media"].lower() == "true":
179             reject_media = True
180
181         if "#reject_reports" in row and row["#reject_reports"].lower() == "true":
182             reject_reports = True
183         elif "reject_reports" in row and row["reject_reports"].lower() == "true":
184             reject_reports = True
185
186         cnt = cnt + 1
187         logger.debug("domain='%s',severity='%s',reject_media='%s',reject_reports='%s'", domain, severity, reject_media, reject_reports)
188         if domain is None or domain == "":
189             logger.debug("domain='%s' is empty - SKIPPED!", domain)
190             continue
191         elif domain.endswith(".onion"):
192             logger.debug("domain='%s' is a TOR .onion domain - SKIPPED", domain)
193             continue
194         elif domain.endswith(".arpa"):
195             logger.debug("domain='%s' is a reverse IP address - SKIPPED", domain)
196             continue
197         elif domain.endswith(".tld"):
198             logger.debug("domain='%s' is a fake domain - SKIPPED", domain)
199             continue
200         elif domain.find("*") >= 0 or domain.find("?") >= 0:
201             logger.debug("domain='%s' is obfuscated - Invoking utils.deobfuscate(%s, %s) ...", domain, domain, blocker)
202             domain = utils.deobfuscate(domain, blocker)
203             logger.debug("domain='%s' - AFTER!", domain)
204
205         if not validators.domain(domain):
206             logger.debug("domain='%s' is not a valid domain - SKIPPED!")
207             continue
208         elif blacklist.is_blacklisted(domain):
209             logger.warning("domain='%s' is blacklisted - SKIPPED!", domain)
210             continue
211         elif blocks.is_instance_blocked(blocker, domain, severity):
212             logger.debug("blocker='%s' has already blocked domain='%s' with severity='%s' - SKIPPED!", blocker, domain, severity)
213             continue
214
215         logger.debug("Marking domain='%s' as handled", domain)
216         domains.append(domain)
217
218         logger.debug("Processing domain='%s',blocker='%s',command='%s' ...", domain, blocker, command)
219         processed = instance(domain, blocker, command)
220         logger.debug("processed='%s'", processed)
221
222         if block(blocker, domain, reason, severity) and config.get("bot_enabled"):
223             logger.debug("Appending blocked='%s',reason='%s' for blocker='%s' ...", domain, reason, blocker)
224             blockdict.append({
225                 "blocked": domain,
226                 "reason" : reason,
227             })
228
229         if reject_media:
230             block(blocker, domain, None, "reject_media")
231         if reject_reports:
232             block(blocker, domain, None, "reject_reports")
233
234     logger.debug("blocker='%s'", blocker)
235     if not blocklists.has(blocker):
236         logger.debug("Invoking instances.set_total_blocks(%s, domains()=%d) ...", blocker, len(domains))
237         instances.set_total_blocks(blocker, domains)
238
239     logger.debug("Checking if blocker='%s' has pending updates ...", blocker)
240     if instances.has_pending(blocker):
241         logger.debug("Flushing updates for blocker='%s' ...", blocker)
242         instances.update(blocker)
243
244     logger.debug("Invoking commit() ...")
245     database.connection.commit()
246
247     logger.debug("config.get(bot_enabled)='%s',blockdict()=%d", config.get("bot_enabled"), len(blockdict))
248     if config.get("bot_enabled") and len(blockdict) > 0:
249         logger.info("Sending bot POST for blocker='%s',blockdict()=%d ...", blocker, len(blockdict))
250         network.send_bot_post(blocker, blockdict)
251
252     logger.debug("EXIT!")