]> 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, force: bool = False) -> bool:
40     logger.debug("blocked='%s',blocker='%s',command='%s',force='%s' - CALLED!", blocked, blocker, command, force)
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     elif blacklist.is_blacklisted(blocked):
49         raise ValueError(f"blocked='{blocked}' is blacklisted but function was invoked")
50     elif blacklist.is_blacklisted(blocker):
51         raise ValueError(f"blocker='{blocker}' is blacklisted but function was invoked")
52
53     logger.debug("blocked='%s' - BEFORE!", blocked)
54     blocked = utils.deobfuscate(blocked, blocker)
55     logger.debug("blocked='%s' - AFTER!", blocked)
56
57     logger.debug("Checking if blocker='%s' has pending data ...", blocker)
58     if instances.has_pending(blocker):
59         logger.debug("Flushing updates for blocker='%s' ...", blocker)
60         instances.update(blocker)
61
62     logger.debug("Checking blocked='%s' if wanted and recent ...", blocked)
63     if not domain_helper.is_wanted(blocked):
64         logger.debug("blocked='%s' is not wanted - EXIT!", blocked)
65         return False
66     elif not force and instances.is_recent(blocked):
67         logger.debug("blocked='%s' has been recently checked - EXIT!", blocked)
68         return False
69
70     processed = False
71     try:
72         logger.info("Fetching instances for blocked='%s',blocker='%s',command='%s' ...", blocked, blocker, command)
73         federation.fetch_instances(blocked, blocker, None, command)
74
75         logger.debug("Setting processed=True for blocked='%s',blocker='%s' ...", blocked, blocker)
76         processed = True
77     except network.exceptions as exception:
78         logger.warning("Exception '%s' during fetching instances (%s) from blocked='%s'", type(exception), command, blocked)
79         instances.set_last_error(blocked, exception)
80
81     logger.debug("Checking if blocked='%s' has pending updates ...", blocked)
82     if instances.has_pending(blocked):
83         logger.debug("Flushing updates for blocked='%s' ...", blocked)
84         instances.update(blocked)
85
86     logger.debug("processed='%s' - EXIT!", processed)
87     return processed
88
89 def block(blocker: str, blocked: str, reason: str, block_level: str) -> bool:
90     logger.debug("blocker='%s',blocked='%s',reason='%s',block_level='%s' - CALLED!", blocker, blocked, reason, block_level)
91     domain_helper.raise_on(blocker)
92     domain_helper.raise_on(blocked)
93
94     if not isinstance(reason, str) and reason is not None:
95         raise ValueError(f"Parameter reason[]='{type(reason)}' is not of type 'str'")
96     elif not isinstance(block_level, str):
97         raise ValueError(f"Parameter block_level[]='{type(block_level)}' is not of type 'str'")
98     elif block_level == "":
99         raise ValueError("Parameter block_level is empty")
100     elif blacklist.is_blacklisted(blocker):
101         raise ValueError(f"blocker='{blocker}' is blacklisted but function was invoked")
102     elif blacklist.is_blacklisted(blocked):
103         raise ValueError(f"blocked='{blocked}' is blacklisted but function was invoked")
104
105     added = False
106     if not blocks.is_instance_blocked(blocker, blocked, block_level):
107         logger.debug("Invoking blocks.add(%s, %s, %s, %s) ...", blocker, blocked, reason, block_level)
108         blocks.add(blocker, blocked, reason, block_level)
109         added = True
110     else:
111         logger.debug("Updating last_seen for blocker='%s',blocked='%s',block_level='%s' ...", blocker, blocked, block_level)
112         blocks.update_last_seen(blocker, blocked, block_level)
113
114     logger.debug("added='%s' - EXIT!", added)
115     return added
116
117 def csv_block(blocker: str, url: str, command: str):
118     logger.debug("blocker='%s',url='%s',command='%s' - CALLED!", blocker, url, command)
119     domain_helper.raise_on(blocker)
120
121     if not isinstance(url, str):
122         raise ValueError(f"url[]='{url}' is not of type 'str'")
123     elif url == "":
124         raise ValueError("Parameter 'url' is empty")
125     elif not validators.url(url):
126         raise ValueError(f"Parameter url='{url}' is not a valid URL")
127     elif not isinstance(command, str):
128         raise ValueError(f"command[]='{command}' is not of type 'str'")
129     elif command == "":
130         raise ValueError("Parameter 'command' is empty")
131     elif blacklist.is_blacklisted(blocker):
132         raise ValueError(f"blocker='{blocker}' is blacklisted but function was invoked")
133
134     logger.debug("Setting last_blocked for blocker='%s' ...", blocker)
135     instances.set_last_blocked(blocker)
136
137     domains = list()
138
139     # Fetch this URL
140     logger.info("Fetching url='%s' for blocker='%s' ...", url, blocker)
141     response = network.fetch_url(
142         url,
143         network.web_headers,
144         (config.get("connection_timeout"), config.get("read_timeout"))
145     )
146
147     logger.debug("response.ok='%s',response.status_code=%d,response.content()=%d", response.ok, response.status_code, len(response.content))
148     if not response.ok or response.status_code > 200 or response.content == "":
149         logger.warning("Could not fetch url='%s' for blocker='%s' - EXIT!", url, blocker)
150         return
151
152     logger.debug("Fetched %d Bytes, parsing CSV ...", len(response.content))
153     reader = csv.DictReader(response.content.decode("utf-8").splitlines(), dialect="unix")
154
155     blockdict = list()
156
157     cnt = 0
158     for row in reader:
159         logger.debug("row[%s]='%s'", type(row), row)
160         domain = severity = reason = None
161         reject_media = reject_reports = False
162
163         if "#domain" in row:
164             domain = tidyup.domain(row["#domain"]) if row["#domain"] not in [None, ""] else None
165         elif "domain" in row:
166             domain = tidyup.domain(row["domain"]) if row["domain"] not in [None, ""] else None
167         elif "Domain" in row:
168             domain = tidyup.domain(row["Domain"]) if row["Domain"] not in [None, ""] else None
169         else:
170             logger.warning("row='%s' does not contain domain column - SKIPPED!", row)
171             continue
172
173         if "#severity" in row:
174             severity = blocks.alias_block_level(row["#severity"])
175         elif "severity" in row:
176             severity = blocks.alias_block_level(row["severity"])
177         else:
178             logger.debug("row='%s' does not contain severity column, setting 'reject'", row)
179             severity = "reject"
180
181         if "reason" in row:
182             reason = tidyup.reason(row["reason"]) if row["reason"] not in [None, ""] else None
183         elif "comment" in row:
184             reason = tidyup.reason(row["comment"]) if row["comment"] not in [None, ""] else None
185         elif "Comment" in row:
186             reason = tidyup.reason(row["Comment"]) if row["Comment"] not in [None, ""] else None
187         else:
188             logger.debug("row='%s' has no reason/comment key provided", row)
189
190         if "#reject_media" in row and row["#reject_media"].lower() == "true":
191             reject_media = True
192         elif "reject_media" in row and row["reject_media"].lower() == "true":
193             reject_media = True
194
195         if "#reject_reports" in row and row["#reject_reports"].lower() == "true":
196             reject_reports = True
197         elif "reject_reports" in row and row["reject_reports"].lower() == "true":
198             reject_reports = True
199
200         cnt = cnt + 1
201         logger.debug("domain='%s',severity='%s',reject_media='%s',reject_reports='%s'", domain, severity, reject_media, reject_reports)
202         if domain in [None, ""]:
203             logger.debug("domain='%s' is empty - SKIPPED!", domain)
204             continue
205         elif domain.endswith(".onion"):
206             logger.debug("domain='%s' is a TOR .onion domain - SKIPPED", domain)
207             continue
208         elif domain.endswith(".i2p") and not config.get("allow_i2p_domain"):
209             logger.debug("domain='%s' is an I2P .onion domain - SKIPPED", domain)
210             continue
211         elif domain.endswith(".arpa"):
212             logger.debug("domain='%s' is a reverse IP address - SKIPPED", domain)
213             continue
214         elif domain.endswith(".tld"):
215             logger.debug("domain='%s' is a fake domain - SKIPPED", domain)
216             continue
217         elif domain.find("*") >= 0 or domain.find("?") >= 0:
218             logger.debug("domain='%s' is obfuscated - Invoking utils.deobfuscate(%s, %s) ...", domain, domain, blocker)
219             domain = utils.deobfuscate(domain, blocker)
220             logger.debug("domain='%s' - AFTER!", domain)
221
222         logger.debug("Marking domain='%s' as handled", domain)
223         domains.append(domain)
224
225         if not validators.domain(domain):
226             logger.debug("domain='%s' is not a valid domain - SKIPPED!")
227             continue
228         elif blacklist.is_blacklisted(domain):
229             logger.debug("domain='%s' is blacklisted - SKIPPED!", domain)
230             continue
231         elif blocks.is_instance_blocked(blocker, domain, severity):
232             logger.debug("blocker='%s' has already blocked domain='%s' with severity='%s' - SKIPPED!", blocker, domain, severity)
233             continue
234
235         logger.debug("Processing domain='%s',blocker='%s',command='%s' ...", domain, blocker, command)
236         processed = instance(domain, blocker, command)
237         logger.debug("processed='%s'", processed)
238
239         if block(blocker, domain, reason, severity) and config.get("bot_enabled"):
240             logger.debug("Appending blocked='%s',reason='%s' for blocker='%s' ...", domain, reason, blocker)
241             blockdict.append({
242                 "blocked": domain,
243                 "reason" : reason,
244             })
245
246         logger.debug("reject_media='%s',reject_reports='%s'", reject_media, reject_reports)
247         if reject_media:
248             block(blocker, domain, None, "reject_media")
249         if reject_reports:
250             block(blocker, domain, None, "reject_reports")
251
252         logger.debug("Invoking commit() ...")
253         database.connection.commit()
254
255     logger.debug("blocker='%s'", blocker)
256     if blocklists.has(blocker):
257         logger.debug("Invoking instances.set_total_blocks(%s, domains()=%d) ...", blocker, len(domains))
258         instances.set_total_blocks(blocker, domains)
259
260     logger.debug("Checking if blocker='%s' has pending updates ...", blocker)
261     if instances.has_pending(blocker):
262         logger.debug("Flushing updates for blocker='%s' ...", blocker)
263         instances.update(blocker)
264
265     logger.debug("config.get(bot_enabled)='%s',blockdict()=%d", config.get("bot_enabled"), len(blockdict))
266     if config.get("bot_enabled") and len(blockdict) > 0:
267         logger.info("Sending bot POST for blocker='%s',blockdict()=%d ...", blocker, len(blockdict))
268         network.send_bot_post(blocker, blockdict)
269
270     logger.debug("EXIT!")