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