]>
Commit | Line | Data |
---|---|---|
87804149 | 1 | #!/usr/bin/env/python |
6f7f2f85 NC |
2 | # -*- coding: utf-8 -*- |
3 | ||
4 | ### | |
5 | # Copyright (c) 2016, Nicolas Coevoet | |
6 | # All rights reserved. | |
7 | # | |
8 | # Redistribution and use in source and binary forms, with or without | |
9 | # modification, are permitted provided that the following conditions are met: | |
10 | # | |
11 | # * Redistributions of source code must retain the above copyright notice, | |
12 | # this list of conditions, and the following disclaimer. | |
13 | # * Redistributions in binary form must reproduce the above copyright notice, | |
14 | # this list of conditions, and the following disclaimer in the | |
15 | # documentation and/or other materials provided with the distribution. | |
16 | # * Neither the name of the author of this software nor the name of | |
17 | # contributors to this software may be used to endorse or promote products | |
18 | # derived from this software without specific prior written consent. | |
19 | # | |
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | ### | |
d39d269b | 33 | # coding: utf-8 |
6f7f2f85 | 34 | import os |
42f3350d | 35 | import re |
18e7d2c8 | 36 | import sys |
6f7f2f85 | 37 | import time |
54deeac6 NC |
38 | import requests |
39 | from urllib.parse import urlencode | |
18e7d2c8 | 40 | import sqlite3 |
54deeac6 | 41 | import http.client |
18e7d2c8 | 42 | import threading |
b78d8847 | 43 | import dns.resolver |
f7007307 | 44 | import json |
13f59897 | 45 | import ipaddress |
992db209 | 46 | import random |
18e7d2c8 VL |
47 | import supybot.log as log |
48 | import supybot.conf as conf | |
6f7f2f85 | 49 | import supybot.utils as utils |
18e7d2c8 VL |
50 | import supybot.ircdb as ircdb |
51 | import supybot.world as world | |
6f7f2f85 | 52 | from supybot.commands import * |
18e7d2c8 | 53 | import supybot.ircmsgs as ircmsgs |
6f7f2f85 | 54 | import supybot.plugins as plugins |
18e7d2c8 | 55 | import supybot.commands as commands |
6f7f2f85 | 56 | import supybot.ircutils as ircutils |
6f7f2f85 | 57 | import supybot.callbacks as callbacks |
6f7f2f85 NC |
58 | import supybot.schedule as schedule |
59 | import supybot.registry as registry | |
54deeac6 NC |
60 | from ftfy.badness import sequence_weirdness |
61 | from ftfy.badness import text_cost | |
6f7f2f85 NC |
62 | try: |
63 | from supybot.i18n import PluginInternationalization | |
64 | _ = PluginInternationalization('Sigyn') | |
65 | except: | |
66 | _ = lambda x:x | |
67 | ||
6f7f2f85 NC |
68 | def repetitions(s): |
69 | # returns a list of (pattern,count), used to detect a repeated pattern inside a single string. | |
70 | r = re.compile(r"(.+?)\1+") | |
71 | for match in r.finditer(s): | |
72 | yield (match.group(1), len(match.group(0))/len(match.group(1))) | |
73 | ||
4165a619 NC |
74 | def isCloaked (prefix,sig): |
75 | if sig.registryValue('useWhoWas'): | |
76 | return False | |
6f7f2f85 NC |
77 | if not ircutils.isUserHostmask(prefix): |
78 | return False | |
79 | (nick,ident,host) = ircutils.splitHostmask(prefix) | |
80 | if '/' in host: | |
45ac582a | 81 | if host.startswith('gateway/') or host.startswith('nat/'): |
6f7f2f85 NC |
82 | return False |
83 | return True | |
84 | return False | |
85 | ||
6f7f2f85 | 86 | def compareString (a,b): |
6a2d5137 | 87 | """return 0 to 1 float percent of similarity ( 0.85 seems to be a good average )""" |
6f7f2f85 NC |
88 | if a == b: |
89 | return 1 | |
90 | sa, sb = set(a), set(b) | |
91 | n = len(sa.intersection(sb)) | |
92 | if float(len(sa) + len(sb) - n) == 0: | |
93 | return 0 | |
94 | jacc = n / float(len(sa) + len(sb) - n) | |
95 | return jacc | |
96 | ||
97 | def largestString (s1,s2): | |
6a2d5137 VL |
98 | """return largest pattern available in 2 strings""" |
99 | # From https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Longest_common_substring#Python2 | |
100 | # License: CC BY-SA | |
54deeac6 | 101 | m = [[0] * (1 + len(s2)) for i in range(1 + len(s1))] |
6f7f2f85 | 102 | longest, x_longest = 0, 0 |
54deeac6 NC |
103 | for x in range(1, 1 + len(s1)): |
104 | for y in range(1, 1 + len(s2)): | |
6f7f2f85 NC |
105 | if s1[x - 1] == s2[y - 1]: |
106 | m[x][y] = m[x - 1][y - 1] + 1 | |
107 | if m[x][y] > longest: | |
108 | longest = m[x][y] | |
109 | x_longest = x | |
110 | else: | |
111 | m[x][y] = 0 | |
112 | return s1[x_longest - longest: x_longest] | |
113 | ||
114 | def floatToGMT (t): | |
115 | f = None | |
116 | try: | |
117 | f = float(t) | |
118 | except: | |
119 | return None | |
120 | return time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(f)) | |
121 | ||
122 | def _getRe(f): | |
123 | def get(irc, msg, args, state): | |
124 | original = args[:] | |
125 | s = args.pop(0) | |
126 | def isRe(s): | |
127 | try: | |
128 | foo = f(s) | |
129 | return True | |
130 | except ValueError: | |
131 | return False | |
132 | try: | |
133 | while len(s) < 512 and not isRe(s): | |
134 | s += ' ' + args.pop(0) | |
135 | if len(s) < 512: | |
136 | state.args.append([s,f(s)]) | |
137 | else: | |
138 | state.errorInvalid('regular expression', s) | |
139 | except IndexError: | |
140 | args[:] = original | |
141 | state.errorInvalid('regular expression', s) | |
142 | return get | |
143 | ||
144 | getPatternAndMatcher = _getRe(utils.str.perlReToPythonRe) | |
145 | ||
146 | addConverter('getPatternAndMatcher', getPatternAndMatcher) | |
147 | ||
148 | class Ircd (object): | |
a2395d14 | 149 | |
ff95c471 | 150 | __slots__ = ('irc', 'channels','whowas','klines','queues','opered','defcon','pending','logs','limits','netsplit','ping','servers','resolving','stats','patterns','throttled','lastDefcon','god','mx','tokline','toklineresults','dlines', 'invites', 'nicks', 'domains', 'cleandomains', 'ilines', 'klinednicks', 'lastKlineOper') |
3e6d4c6d | 151 | |
6f7f2f85 NC |
152 | def __init__(self,irc): |
153 | self.irc = irc | |
154 | # contains Chan instances | |
155 | self.channels = {} | |
156 | # contains Pattern instances | |
157 | self.patterns = {} | |
158 | # contains whowas requested for a short period of time | |
159 | self.whowas = {} | |
160 | # contains klines requested for a short period of time | |
161 | self.klines = {} | |
a2395d14 | 162 | # contains various TimeoutQueue for detection purpose |
6f7f2f85 NC |
163 | # often it's [host] { with various TimeOutQueue and others elements } |
164 | self.queues = {} | |
165 | # flag or time | |
166 | self.opered = False | |
167 | # flag or time | |
168 | self.defcon = False | |
169 | # used for temporary storage of outgoing actions | |
170 | self.pending = {} | |
171 | self.logs = {} | |
172 | # contains servers notices when full or in bad state | |
173 | # [servername] = time.time() | |
174 | self.limits = {} | |
6f7f2f85 | 175 | # flag or time |
99ebdad5 | 176 | self.netsplit = time.time() + 300 |
28164a19 NC |
177 | self.ping = None |
178 | self.servers = {} | |
66b6dfb5 | 179 | self.resolving = {} |
92740f96 | 180 | self.stats = {} |
ff95c471 | 181 | self.ilines = {} |
b888f64b | 182 | self.throttled = False |
f7007307 | 183 | self.lastDefcon = False |
47e9a74a | 184 | self.god = False |
70bd3e0c | 185 | self.mx = {} |
03ed488b | 186 | self.tokline = {} |
1c77429f | 187 | self.toklineresults = {} |
c27f7c2a | 188 | self.dlines = [] |
21efdd22 | 189 | self.invites = {} |
4165a619 | 190 | self.nicks = {} |
d8d19c3a | 191 | self.cleandomains = {} |
4ea839a3 | 192 | self.klinednicks = utils.structures.TimeoutQueue(86400*2) |
ff95c471 | 193 | self.lastKlineOper = '' |
7256d3f2 | 194 | |
6f7f2f85 | 195 | def __repr__(self): |
45ac582a NC |
196 | return '%s(patterns=%r, queues=%r, channels=%r, pending=%r, logs=%r, limits=%r, whowas=%r, klines=%r)' % (self.__class__.__name__, |
197 | self.patterns, self.queues, self.channels, self.pending, self.logs, self.limits, self.whowas, self.klines) | |
b78d8847 | 198 | |
6f7f2f85 NC |
199 | def restore (self,db): |
200 | c = db.cursor() | |
201 | c.execute("""SELECT id, pattern, regexp, mini, life FROM patterns WHERE removed_at is NULL""") | |
202 | items = c.fetchall() | |
203 | if len(items): | |
204 | for item in items: | |
205 | (uid,pattern,regexp,limit,life) = item | |
f12749a8 | 206 | regexp = int(regexp) |
6f7f2f85 NC |
207 | if regexp == 1: |
208 | regexp = True | |
209 | else: | |
210 | regexp = False | |
211 | self.patterns[uid] = Pattern(uid,pattern,regexp,limit,life) | |
212 | c.close() | |
b78d8847 | 213 | |
6f7f2f85 NC |
214 | def add (self,db,prefix,pattern,limit,life,regexp): |
215 | c = db.cursor() | |
216 | t = 0 | |
217 | if regexp: | |
218 | t = 1 | |
219 | c.execute("""INSERT INTO patterns VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL)""", (pattern,t,limit,life,prefix,'',0,float(time.time()))) | |
220 | uid = int(c.lastrowid) | |
a2395d14 | 221 | self.patterns[uid] = Pattern(uid,pattern,regexp,limit,life) |
6f7f2f85 NC |
222 | db.commit() |
223 | c.close() | |
224 | return uid | |
225 | ||
226 | def count(self,db,uid): | |
227 | uid = int(uid) | |
228 | if uid in self.patterns: | |
229 | c = db.cursor() | |
230 | c.execute("""SELECT id, triggered FROM patterns WHERE id=? LIMIT 1""",(uid,)) | |
231 | items = c.fetchall() | |
232 | if len(items): | |
233 | (uid,triggered) = items[0] | |
234 | triggered = int(triggered + 1) | |
235 | c.execute("""UPDATE patterns SET triggered=? WHERE id=?""",(triggered,uid)) | |
236 | db.commit() | |
237 | c.close() | |
238 | ||
239 | def ls (self,db,pattern,deep=False): | |
240 | c = db.cursor() | |
241 | glob = '*%s*' % pattern | |
242 | like = '%'+pattern+'%' | |
243 | i = None | |
244 | try: | |
245 | i = int(pattern) | |
246 | except: | |
247 | i = None | |
248 | if i: | |
249 | c.execute("""SELECT id, pattern, regexp, operator, at, triggered, removed_at, removed_by, comment, mini, life FROM patterns WHERE id=? LIMIT 1""",(i,)) | |
250 | else: | |
251 | if deep: | |
252 | c.execute("""SELECT id, pattern, regexp, operator, at, triggered, removed_at, removed_by, comment, mini, life FROM patterns WHERE id GLOB ? OR id LIKE ? OR pattern GLOB ? OR pattern LIKE ? OR comment GLOB ? OR comment LIKE ? ORDER BY id DESC""",(glob,like,glob,like,glob,like)) | |
253 | else: | |
254 | c.execute("""SELECT id, pattern, regexp, operator, at, triggered, removed_at, removed_by, comment, mini, life FROM patterns WHERE (id GLOB ? OR id LIKE ? OR pattern GLOB ? OR pattern LIKE ? OR comment GLOB ? OR comment LIKE ?) and removed_at is NULL ORDER BY id DESC""",(glob,like,glob,like,glob,like)) | |
255 | items = c.fetchall() | |
256 | c.close() | |
257 | if len(items): | |
258 | results = [] | |
259 | for item in items: | |
260 | (uid,pattern,regexp,operator,at,triggered,removed_at,removed_by,comment,limit,life) = item | |
261 | end = '' | |
262 | if i: | |
263 | if removed_by: | |
264 | end = ' - disabled on %s by %s - ' % (floatToGMT(removed_at),removed_by.split('!')[0]) | |
f12749a8 NC |
265 | regexp = int(regexp) |
266 | reg = 'not case sensitive' | |
267 | if regexp == 1: | |
268 | reg = 'regexp pattern' | |
269 | results.append('#%s "%s" by %s on %s (%s calls) %s/%ss%s %s - %s' % (uid,pattern,operator.split('!')[0],floatToGMT(at),triggered,limit,life,end,comment,reg)) | |
6f7f2f85 NC |
270 | else: |
271 | if removed_by: | |
272 | end = ' (disabled)' | |
273 | results.append('[#%s "%s" (%s calls) %s/%ss%s]' % (uid,pattern,triggered,limit,life,end)) | |
6f7f2f85 NC |
274 | return results |
275 | return [] | |
b78d8847 | 276 | |
6f7f2f85 NC |
277 | def edit (self,db,uid,limit,life,comment): |
278 | c = db.cursor() | |
279 | uid = int(uid) | |
280 | c.execute("""SELECT id, life FROM patterns WHERE id=? LIMIT 1""",(uid,)) | |
281 | items = c.fetchall() | |
282 | if len(items): | |
283 | if comment: | |
284 | c.execute("""UPDATE patterns SET life=?, mini=?, comment=? WHERE id=? LIMIT 1""",(life,limit,comment,uid)) | |
285 | else: | |
286 | c.execute("""UPDATE patterns SET life=?, mini=? WHERE id=? LIMIT 1""",(life,limit,uid)) | |
287 | db.commit() | |
288 | if uid in self.patterns: | |
289 | self.patterns[uid].life = life | |
290 | self.patterns[uid].limit = limit | |
291 | found = True | |
292 | c.close() | |
293 | return (len(items)) | |
b78d8847 | 294 | |
6f7f2f85 NC |
295 | def toggle (self,db,uid,prefix,active): |
296 | c = db.cursor() | |
297 | uid = int(uid) | |
298 | c.execute("""SELECT id, pattern, regexp, mini, life, removed_at, removed_by FROM patterns WHERE id=? LIMIT 1""",(uid,)) | |
299 | items = c.fetchall() | |
300 | updated = False | |
301 | if len(items): | |
302 | (id,pattern,regexp,limit,life,removed_at,removed_by) = items[0] | |
f12749a8 | 303 | regexp = int(regexp) |
6f7f2f85 NC |
304 | if active and removed_at: |
305 | c.execute("""UPDATE patterns SET removed_at=NULL, removed_by=NULL WHERE id=? LIMIT 1""",(uid,)) | |
306 | self.patterns[uid] = Pattern(uid,pattern,regexp == 1,limit,life) | |
307 | updated = True | |
f12749a8 | 308 | elif not removed_at and not active: |
6f7f2f85 NC |
309 | c.execute("""UPDATE patterns SET removed_at=?, removed_by=? WHERE id=? LIMIT 1""",(float(time.time()),prefix,uid)) |
310 | if uid in self.patterns: | |
311 | del self.patterns[uid] | |
312 | updated = True | |
313 | db.commit() | |
314 | c.close() | |
315 | return updated | |
b78d8847 | 316 | |
7ede9ecc NC |
317 | def remove (self, db, uid): |
318 | c = db.cursor() | |
319 | uid = int(uid) | |
320 | c.execute("""SELECT id, pattern, regexp, mini, life, removed_at, removed_by FROM patterns WHERE id=? LIMIT 1""",(uid,)) | |
321 | items = c.fetchall() | |
322 | updated = False | |
323 | if len(items): | |
324 | (id,pattern,regexp,limit,life,removed_at,removed_by) = items[0] | |
325 | c.execute("""DELETE FROM patterns WHERE id=? LIMIT 1""",(uid,)) | |
326 | if not removed_at: | |
327 | if uid in self.patterns: | |
328 | del self.patterns[uid] | |
329 | updated = True | |
330 | db.commit() | |
331 | c.close() | |
332 | return updated | |
333 | ||
6f7f2f85 | 334 | class Chan (object): |
2bcb4fa2 | 335 | __slots__ = ('channel', 'patterns', 'buffers', 'logs', 'nicks', 'called', 'klines', 'requestedBySpam') |
6f7f2f85 NC |
336 | def __init__(self,channel): |
337 | self.channel = channel | |
338 | self.patterns = None | |
339 | self.buffers = {} | |
340 | self.logs = {} | |
341 | self.nicks = {} | |
342 | self.called = False | |
56b48e4e | 343 | self.klines = utils.structures.TimeoutQueue(1800) |
2bcb4fa2 | 344 | self.requestedBySpam = False |
c83d9b65 | 345 | |
6f7f2f85 NC |
346 | def __repr__(self): |
347 | return '%s(channel=%r, patterns=%r, buffers=%r, logs=%r, nicks=%r)' % (self.__class__.__name__, | |
348 | self.channel, self.patterns, self.buffers, self.logs, self.nicks) | |
349 | ||
350 | class Pattern (object): | |
3e6d4c6d | 351 | __slots__ = ('uid', 'pattern', 'limit', 'life', '_match') |
6f7f2f85 NC |
352 | def __init__(self,uid,pattern,regexp,limit,life): |
353 | self.uid = uid | |
354 | self.pattern = pattern | |
355 | self.limit = limit | |
356 | self.life = life | |
357 | self._match = False | |
358 | if regexp: | |
359 | self._match = utils.str.perlReToPythonRe(pattern) | |
54deeac6 NC |
360 | else: |
361 | self.pattern = pattern.lower() | |
876382df | 362 | |
bd83123d NC |
363 | def match (self,text): |
364 | s = False | |
066ea069 NC |
365 | if isinstance(text,bytes): |
366 | text = str(text, "utf-8") | |
56b48e4e NC |
367 | if self._match: |
368 | s = self._match.search (text) != None | |
369 | else: | |
370 | text = text.lower() | |
066ea069 | 371 | s = self.pattern in text |
bd83123d | 372 | return s |
54deeac6 | 373 | |
6f7f2f85 NC |
374 | def __repr__(self): |
375 | return '%s(uid=%r, pattern=%r, limit=%r, life=%r, _match=%r)' % (self.__class__.__name__, | |
376 | self.uid, self.pattern, self.limit, self.life, self._match) | |
377 | ||
6f7f2f85 NC |
378 | class Sigyn(callbacks.Plugin,plugins.ChannelDBHandler): |
379 | """Network and Channels Spam protections""" | |
380 | threaded = True | |
381 | noIgnore = True | |
b78d8847 | 382 | |
6f7f2f85 | 383 | def __init__(self, irc): |
6f7f2f85 NC |
384 | callbacks.Plugin.__init__(self, irc) |
385 | plugins.ChannelDBHandler.__init__(self) | |
386 | self._ircs = ircutils.IrcDict() | |
7f2e65ae | 387 | self.cache = {} |
6f7f2f85 | 388 | self.getIrc(irc) |
0c38e343 | 389 | self.starting = world.starting |
645a036d | 390 | self.recaps = re.compile("[A-Z]") |
d8d19c3a | 391 | self.ipfiltered = {} |
4ea839a3 | 392 | self.rmrequestors = {} |
99ebdad5 | 393 | self.spamchars = {'Ḕ', 'Î', 'Ù', 'Ṋ', 'ℰ', 'Ừ', 'ś', 'ï', 'ℯ', 'ļ', 'ẋ', 'ᾒ', 'ἶ', 'ệ', 'ℓ', 'Ŋ', 'Ḝ', 'ξ', 'ṵ', 'û', 'ẻ', 'Ũ', 'ṡ', '§', 'Ƚ', 'Š', 'ᶙ', 'ṩ', '¹', 'ư', 'Ῐ', 'Ü', 'ŝ', 'ὴ', 'Ș', 'ũ', 'ῑ', 'ⱷ', 'Ǘ', 'Ɇ', 'ĭ', 'ἤ', 'Ɲ', 'Ǝ', 'ủ', 'µ', 'Ỵ', 'Ű', 'ū', 'į', 'ἳ', 'ΐ', 'ḝ', 'Ɛ', 'ṇ', 'È', 'ῆ', 'ử', 'Ň', 'υ', 'Ǜ', 'Ἔ', 'Ὑ', 'μ', 'Ļ', 'ů', 'Ɫ', 'ŷ', 'Ǚ', 'ἠ', 'Ĺ', 'Ę', 'Ὲ', 'Ẍ', 'Ɣ', 'Ϊ', 'ℇ', 'ẍ', 'ῧ', 'ϵ', 'ἦ', 'ừ', 'ṳ', 'ᾕ', 'ṋ', 'ù', 'ῦ', 'Ι', 'ῠ', 'ṥ', 'ὲ', 'ê', 'š', 'ě', 'ề', 'ẽ', 'ī', 'Ė', 'ỷ', 'Ủ', 'ḯ', 'Ἓ', 'Ὓ', 'Ş', 'ύ', 'Ṧ', 'Ŷ', 'ἒ', 'ἵ', 'ė', 'ἰ', 'ẹ', 'Ȇ', 'Ɏ', 'Ί', 'ὶ', 'Ε', 'ḛ', 'Ὤ', 'ǐ', 'ȇ', 'ἢ', 'í', 'ȕ', 'Ữ', '$', 'ή', 'Ṡ', 'ἷ', 'Ḙ', 'Ὢ', 'Ṉ', 'Ľ', 'ῃ', 'Ụ', 'Ṇ', 'ᾐ', 'Ů', 'Ἕ', 'ý', 'Ȅ', 'ᴌ', 'ύ', 'ņ', 'ὒ', 'Ý', 'ế', 'ĩ', 'ǘ', 'Ē', 'ṹ', 'Ư', 'é', 'Ÿ', 'ΰ', 'Ὦ', 'Ë', 'ỳ', 'ἓ', 'ĕ', 'ἑ', 'ṅ', 'ȗ', 'Ν', 'ί', 'ể', 'ᴟ', 'è', 'ᴇ', 'ḭ', 'ȝ', 'ϊ', 'ƪ', 'Ὗ', 'Ų', 'Ề', 'Ṷ', 'ü', 'Ɨ', 'Ώ', 'ň', 'ṷ', 'ƞ', 'Ȗ', 'ș', 'ῒ', 'Ś', 'Ự', 'Ń', 'Ἳ', 'Ứ', 'Ἷ', 'ἱ', 'ᾔ', 'ÿ', 'Ẽ', 'ὖ', 'ὑ', 'ἧ', 'Ὥ', 'ṉ', 'Ὠ', 'ℒ', 'Ệ', 'Ὼ', 'Ẻ', 'ḙ', 'Ŭ', '₴', 'Ὡ', 'ȉ', 'Ṅ', 'ᵪ', 'ữ', 'Ὧ', 'ń', 'Ἐ', 'Ú', 'ɏ', 'î', 'Ⱡ', 'Ƨ', 'Ě', 'ȿ', 'ᴉ', 'Ṩ', 'Ê', 'ȅ', 'ᶊ', 'Ṻ', 'Ḗ', 'ǹ', 'ᴣ', 'ş', 'Ï', 'ᾗ', 'ự', 'ὗ', 'ǔ', 'ᶓ', 'Ǹ', 'Ἶ', 'Ṳ', 'Ʊ', 'ṻ', 'Ǐ', 'ᵴ', 'ῇ', 'Ẹ', 'Ế', 'Ϋ', 'Ū', 'Ῑ', 'ί', 'ỹ', 'Ḯ', 'ǀ', 'Ὣ', 'Ȳ', 'ǃ', 'ų', 'ϴ', 'Ώ', 'Í', 'ì', 'ι', 'ῄ', 'ΰ', 'ἣ', 'ῡ', 'Ἒ', 'Ḽ', 'Ȉ', 'Έ', 'ἴ', 'ᶇ', 'ἕ', 'ǚ', 'Ī', 'Έ', '¥', 'Ṵ', 'ὔ', 'Ŝ', 'ῢ', 'Ἱ', 'ű', 'Ḷ', 'Ὶ', 'ḗ', 'ᴜ', 'ę', 'ὐ', 'Û', 'ᾑ', 'Ʋ', 'Ἑ', 'Ì', 'ŋ', 'Ḛ', 'ỵ', 'Ễ', '℮', '×', 'Ῠ', 'Ἵ', 'Ύ', 'Ử', 'ᴈ', 'ē', 'Ἰ', 'ᶖ', 'ȳ', 'Ǯ', 'ὓ', 'ὕ', 'ῂ', 'Ĕ', 'É', 'ᾓ', 'Ḻ', 'Ņ', 'ἥ', 'ḕ', 'ὺ', 'Ȋ', 'ı', 'Ȕ', 'ṧ', 'ᾖ', 'Ί', 'ΐ', '€', 'Ḭ', 'Ƴ', 'ȵ', 'Ṹ', 'Ñ', 'Ƞ', 'Ȩ', 'ῐ', 'ứ', 'έ', 'ł', 'ŭ', '϶', 'ƴ', '₤', 'ƨ', '£', 'Ł', 'ñ', 'ë', 'ễ', 'ǯ', 'ᶕ', 'ή', 'ᶔ', 'Π', 'ȩ', 'ἐ', 'Ể', 'ε', 'Ĩ', 'ǜ', 'Į', 'Ξ', 'Ḹ', 'Ῡ', '∩', 'ú', 'Χ', 'ụ'} |
1c77429f | 394 | |
09e2c886 | 395 | def removeDnsbl (self,irc,ip,droneblHost,droneblKey): |
54deeac6 NC |
396 | headers = { |
397 | 'Content-Type' : 'text/xml' | |
398 | } | |
f7007307 | 399 | def check(answer): |
12a76c8b | 400 | found = False |
992db209 NC |
401 | for line in answer.split('\n'): |
402 | if line.find('listed="1"') != -1: | |
992db209 NC |
403 | id = line.split('id="')[1] |
404 | id = id.split('"')[0] | |
c6c34792 | 405 | if line.find('type="18"') != -1: |
406 | self.logChannel(irc,'RMDNSBL: %s (%s) not removed: is type 18' % (ip,id)) | |
4ea839a3 NC |
407 | if ip in self.rmrequestors: |
408 | irc.queueMsg(ircmsgs.privmsg(self.rmrequestors[ip],'%s (%s) not removed: is type 18' % (ip,id))) | |
409 | del self.rmrequestors[ip] | |
c6c34792 | 410 | continue |
54deeac6 | 411 | data = "<?xml version=\"1.0\"?><request key='"+droneblKey+"'><remove id='"+id+"' /></request>" |
12a76c8b NC |
412 | found = True |
413 | try: | |
54deeac6 NC |
414 | r = requests.post(droneblHost,data=data,headers=headers) |
415 | response = r.text.replace('\n','') | |
12a76c8b | 416 | if "You are not authorized to remove this incident" in response: |
c6c34792 | 417 | self.logChannel(irc,'RMDNSBL: %s (%s) failed: You are not authorized to remove this incident' % (ip,id)) |
4ea839a3 NC |
418 | if ip in self.rmrequestors: |
419 | irc.queueMsg(ircmsgs.privmsg(self.rmrequestors[ip],'%s (%s) not removed: You are not authorized to remove this incident' % (ip,id))) | |
420 | del self.rmrequestors[ip] | |
12a76c8b | 421 | else: |
c6c34792 | 422 | self.logChannel(irc,'RMDNSBL: %s (%s) removed' % (ip,id)) |
4ea839a3 NC |
423 | if ip in self.rmrequestors: |
424 | irc.queueMsg(ircmsgs.privmsg(self.rmrequestors[ip],'%s (%s) removed' % (ip,id))) | |
425 | del self.rmrequestors[ip] | |
12a76c8b | 426 | except: |
c6c34792 | 427 | self.logChannel(irc,'RMDNSBL: %s (%s) failed: unknown error' % (ip,id)) |
4ea839a3 NC |
428 | if ip in self.rmrequestors: |
429 | irc.queueMsg(ircmsgs.privmsg(self.rmrequestors[ip],'%s (%s) not removed: unknown error' % (ip,id))) | |
430 | del self.rmrequestors[ip] | |
12a76c8b | 431 | if not found: |
c6c34792 | 432 | self.logChannel(irc,'RMDNSBL: %s (none) not removed: no listing found' % ip) |
4ea839a3 NC |
433 | if ip in self.rmrequestors: |
434 | irc.queueMsg(ircmsgs.privmsg(self.rmrequestors[ip],'%s (%s) not removed: no listing found' % (ip,id))) | |
435 | del self.rmrequestors[ip] | |
54deeac6 NC |
436 | data = "<?xml version=\"1.0\"?><request key='"+droneblKey+"'><lookup ip='"+ip+"' /></request>" |
437 | r = requests.post(droneblHost,data=data,headers=headers) | |
438 | if r.status_code == 200: | |
439 | check(r.text) | |
7ede9ecc | 440 | else: |
c6c34792 | 441 | self.logChannel(irc,'RMDNSBL: %s (unknown) failed: status code %s' % (ip,r.status_code)) |
4ea839a3 NC |
442 | if ip in self.rmrequestors: |
443 | irc.queueMsg(ircmsgs.privmsg(self.rmrequestors[ip],'%s (unknown) not removed: status code %s' % (ip,r.status_code))) | |
7ede9ecc | 444 | |
8c2c160e | 445 | def fillDnsbl (self,irc,ip,droneblHost,droneblKey,comment=None): |
54deeac6 NC |
446 | headers = { |
447 | 'Content-Type' : 'text/xml' | |
448 | } | |
f7007307 | 449 | def check(answer): |
910cbf0c | 450 | self.log.info ('fillDnsbl, answered %s' % ip) |
f7007307 | 451 | if 'listed="1"' in answer: |
363cc1d4 | 452 | self.logChannel(irc,'DNSBL: %s (already listed)' % ip) |
f7007307 | 453 | return |
ff95c471 NC |
454 | type = 3 |
455 | if comment == 'Bottler': | |
43c56ec1 | 456 | type = 5 |
ff95c471 NC |
457 | elif comment == 'Unknown spambot or drone': |
458 | type = 6 | |
459 | elif comment == 'DDOS Drone': | |
460 | type = 7 | |
461 | elif comment == 'SOCKS Proxy': | |
462 | type = 8 | |
463 | elif comment == 'HTTP Proxy': | |
464 | type = 9 | |
465 | elif comment == 'ProxyChain': | |
466 | type = 10 | |
467 | elif comment == 'Web Page Proxy': | |
468 | type = 11 | |
469 | elif comment == 'Open DNS Resolver': | |
470 | type = 12 | |
471 | elif comment == 'Brute force attackers': | |
472 | type = 13 | |
473 | elif comment == 'Open Wingate Proxy': | |
474 | type = 14 | |
475 | elif comment == 'Compromised router / gateway': | |
476 | type = 15 | |
477 | elif comment == 'Autorooting worms': | |
478 | type = 16 | |
479 | elif comment == 'Automatically determined botnet IPs (experimental)': | |
480 | type = 17 | |
481 | elif comment == 'DNS/MX type hostname detected on IRC': | |
482 | type = 18 | |
5d3ff2a4 | 483 | elif comment == "Abused VPN Service": |
484 | type = 19 | |
b414e9a1 NC |
485 | data = "<?xml version=\"1.0\"?><request key='"+droneblKey+"'><add ip='"+ip+"' type='"+str(type)+"' comment='used by irc spam bot' /></request>" |
486 | r = requests.post(droneblHost,data=data,headers=headers) | |
487 | if r.status_code != 200: | |
488 | self.logChannel(irc,'DNSBL: %s (add returned %s %s)' % (ip,r.status_code,r.reason)) | |
8c2c160e | 489 | if comment: |
ff95c471 | 490 | self.logChannel(irc,'DNSBL: %s (%s,type:%s)' % (ip,comment,type)) |
8c2c160e NC |
491 | else: |
492 | self.logChannel(irc,'DNSBL: %s' % ip) | |
910cbf0c | 493 | self.log.info('fillDnsbl, checking %s' % ip) |
54deeac6 | 494 | data = "<?xml version=\"1.0\"?><request key='"+droneblKey+"'><lookup ip='"+ip+"' /></request>" |
910cbf0c NC |
495 | try: |
496 | r = requests.post(droneblHost,data=data,headers=headers,timeout=9) | |
497 | if r.status_code == 200: | |
498 | check(r.text) | |
499 | else: | |
500 | self.logChannel(irc,'DNSBL: %s (%s)' % (ip,r.status_code)) | |
501 | except requests.exceptions.RequestException as e: | |
502 | self.logChannel(irc,'DNSBL: %s (%s)' % (ip,e)) | |
f7007307 | 503 | |
7a9d24ed NC |
504 | def state (self,irc,msg,args,channel): |
505 | """[<channel>] | |
30a6bd55 | 506 | |
7a9d24ed | 507 | returns state of the plugin, for optional <channel>""" |
87804149 | 508 | self.cleanup(irc) |
6f7f2f85 | 509 | i = self.getIrc(irc) |
7a9d24ed | 510 | if not channel: |
45ac582a | 511 | irc.queueMsg(ircmsgs.privmsg(msg.nick,'Opered %s, enable %s, defcon %s, netsplit %s' % (i.opered,self.registryValue('enable'),(i.defcon),i.netsplit))) |
5fd1c53b | 512 | irc.queueMsg(ircmsgs.privmsg(msg.nick,'There are %s permanent patterns and %s channels directly monitored' % (len(i.patterns),len(i.channels)))) |
7a9d24ed NC |
513 | channels = 0 |
514 | prefixs = 0 | |
515 | for k in i.queues: | |
516 | if irc.isChannel(k): | |
517 | channels += 1 | |
518 | elif ircutils.isUserHostmask(k): | |
519 | prefixs += 1 | |
520 | irc.queueMsg(ircmsgs.privmsg(msg.nick,"Via server's notices: %s channels and %s users monitored" % (channels,prefixs))) | |
521 | for chan in i.channels: | |
7537b129 | 522 | if channel == chan: |
7a9d24ed NC |
523 | ch = self.getChan(irc,chan) |
524 | if not self.registryValue('ignoreChannel',channel=chan): | |
0bf86d58 NC |
525 | called = "" |
526 | if ch.called: | |
527 | called = 'currently in defcon' | |
528 | irc.queueMsg(ircmsgs.privmsg(msg.nick,'On %s (%s users) %s:' % (chan,len(ch.nicks),called))) | |
7a9d24ed NC |
529 | protections = ['flood','lowFlood','repeat','lowRepeat','massRepeat','lowMassRepeat','hilight','nick','ctcp'] |
530 | for protection in protections: | |
531 | if self.registryValue('%sPermit' % protection,channel=chan) > -1: | |
a2395d14 AE |
532 | permit = self.registryValue('%sPermit' % protection,channel=chan) |
533 | life = self.registryValue('%sLife' % protection,channel=chan) | |
7a9d24ed NC |
534 | abuse = self.hasAbuseOnChannel(irc,chan,protection) |
535 | if abuse: | |
536 | abuse = ' (ongoing abuses) ' | |
537 | else: | |
538 | abuse = '' | |
539 | count = 0 | |
540 | if protection == 'repeat': | |
541 | for b in ch.buffers: | |
542 | if ircutils.isUserHostmask('n!%s' % b): | |
543 | count += 1 | |
544 | else: | |
545 | for b in ch.buffers: | |
546 | if protection in b: | |
547 | count += len(ch.buffers[b]) | |
548 | if count: | |
549 | count = " - %s user's buffers" % count | |
550 | else: | |
551 | count = "" | |
a2395d14 | 552 | irc.queueMsg(ircmsgs.privmsg(msg.nick," - %s : %s/%ss %s%s" % (protection,permit,life,abuse,count))) |
7a9d24ed NC |
553 | irc.replySuccess() |
554 | state = wrap(state,['owner',optional('channel')]) | |
b78d8847 | 555 | |
0bf86d58 | 556 | def defcon (self,irc,msg,args,channel): |
a2395d14 | 557 | """[<channel>] |
30a6bd55 | 558 | |
f33d2f5d | 559 | limits are lowered, globally or for a specific <channel>""" |
6f7f2f85 | 560 | i = self.getIrc(irc) |
2ecf26e6 | 561 | if channel and channel != self.registryValue('logChannel'): |
0bf86d58 NC |
562 | if channel in i.channels and self.registryValue('abuseDuration',channel=channel) > 0: |
563 | chan = self.getChan(irc,channel) | |
564 | if chan.called: | |
45ac582a | 565 | self.logChannel(irc,'INFO: [%s] rescheduled ignores lifted, limits lowered (by %s) for %ss' % (channel,msg.nick,self.registryValue('abuseDuration',channel=channel))) |
0bf86d58 | 566 | chan.called = time.time() |
a2395d14 | 567 | else: |
45ac582a | 568 | self.logChannel(irc,'INFO: [%s] ignores lifted, limits lowered (by %s) for %ss' % (channel,msg.nick,self.registryValue('abuseDuration',channel=channel))) |
0bf86d58 | 569 | chan.called = time.time() |
6f7f2f85 | 570 | else: |
0bf86d58 NC |
571 | if i.defcon: |
572 | i.defcon = time.time() | |
573 | irc.reply('Already in defcon mode, reset, %ss more' % self.registryValue('defcon')) | |
574 | else: | |
575 | i.defcon = time.time() | |
576 | self.logChannel(irc,"INFO: ignores lifted and abuses end to klines for %ss by %s" % (self.registryValue('defcon'),msg.nick)) | |
47e9a74a NC |
577 | if not i.god: |
578 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick)) | |
579 | else: | |
d4b544e7 | 580 | self.applyDefcon (irc) |
0bf86d58 NC |
581 | irc.replySuccess() |
582 | defcon = wrap(defcon,['owner',optional('channel')]) | |
6f7f2f85 NC |
583 | |
584 | def vacuum (self,irc,msg,args): | |
30a6bd55 VL |
585 | """takes no arguments |
586 | ||
6f7f2f85 NC |
587 | VACUUM the permanent patterns's database""" |
588 | db = self.getDb(irc.network) | |
589 | c = db.cursor() | |
590 | c.execute('VACUUM') | |
591 | c.close() | |
592 | irc.replySuccess() | |
593 | vacuum = wrap(vacuum,['owner']) | |
594 | ||
53ff6dfb NC |
595 | def leave (self,irc,msg,args,channel): |
596 | """<channel> | |
597 | ||
598 | force the bot to part <channel> and won't rejoin even if invited | |
599 | """ | |
c89ce14a | 600 | if channel in irc.state.channels: |
53ff6dfb NC |
601 | reason = conf.supybot.plugins.channel.partMsg.getValue() |
602 | irc.queueMsg(ircmsgs.part(channel,reason)) | |
c89ce14a NC |
603 | try: |
604 | network = conf.supybot.networks.get(irc.network) | |
605 | network.channels().remove(channel) | |
606 | except: | |
607 | pass | |
645a036d | 608 | self.setRegistryValue('lastActionTaken',-1.0,channel=channel) |
43c56ec1 | 609 | irc.replySuccess() |
53ff6dfb NC |
610 | leave = wrap(leave,['owner','channel']) |
611 | ||
612 | def stay (self,irc,msg,args,channel): | |
613 | """<channel> | |
614 | ||
615 | force bot to stay in <channel> | |
616 | """ | |
617 | self.setRegistryValue('leaveChannelIfNoActivity',-1,channel=channel) | |
2e246c41 | 618 | if not channel in irc.state.channels: |
53ff6dfb NC |
619 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) |
620 | irc.queueMsg(ircmsgs.join(channel)) | |
a451876e NC |
621 | try: |
622 | network = conf.supybot.networks.get(irc.network) | |
623 | network.channels().add(channel) | |
624 | except KeyError: | |
625 | pass | |
53ff6dfb NC |
626 | irc.replySuccess() |
627 | stay = wrap(stay,['owner','channel']) | |
628 | ||
c56a3fc6 NC |
629 | def isprotected (self,irc,msg,args,hostmask,channel): |
630 | """<hostmask> [<channel>] | |
afd11ab6 | 631 | |
c56a3fc6 NC |
632 | returns true if <hostmask> is protected, in optional <channel>""" |
633 | if ircdb.checkCapability(hostmask, 'protected'): | |
0c38e343 | 634 | irc.reply('%s is globally protected' % hostmask) |
c56a3fc6 NC |
635 | else: |
636 | if channel: | |
637 | protected = ircdb.makeChannelCapability(channel, 'protected') | |
638 | if ircdb.checkCapability(hostmask, protected): | |
639 | irc.reply('%s is protected in %s' % (hostmask,channel)) | |
640 | else: | |
641 | irc.reply('%s is not protected in %s' % (hostmask,channel)) | |
642 | else: | |
643 | irc.reply('%s is not protected' % hostmask); | |
b3fa7e13 | 644 | isprotected = wrap(isprotected,['owner','hostmask',optional('channel')]) |
c56a3fc6 | 645 | |
e35a4b0e | 646 | def checkactions (self,irc,msg,args,duration): |
afd11ab6 AE |
647 | """<duration> in days |
648 | ||
e35a4b0e NC |
649 | return channels where last action taken is older than <duration>""" |
650 | channels = [] | |
651 | duration = duration * 24 * 3600 | |
652 | for channel in irc.state.channels: | |
653 | if irc.isChannel(channel): | |
a15240d5 | 654 | if self.registryValue('mainChannel') in channel or channel == self.registryValue('reportChannel') or self.registryValue('snoopChannel') == channel or self.registryValue('secretChannel') == channel: |
7ede9ecc NC |
655 | continue |
656 | if self.registryValue('ignoreChannel',channel): | |
657 | continue | |
e35a4b0e NC |
658 | action = self.registryValue('lastActionTaken',channel=channel) |
659 | if action > 0: | |
660 | if time.time()-action > duration: | |
661 | channels.append('%s: %s' % (channel,time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(action)))) | |
662 | else: | |
663 | channels.append(channel) | |
664 | irc.replies(channels,None,None,False) | |
665 | checkactions = wrap(checkactions,['owner','positiveInt']) | |
666 | ||
28164a19 NC |
667 | def netsplit (self,irc,msg,args,duration): |
668 | """<duration> | |
669 | ||
670 | entering netsplit mode for <duration> (in seconds)""" | |
671 | i = self.getIrc(irc) | |
672 | if i.netsplit: | |
673 | i.netsplit = time.time()+duration | |
674 | irc.reply('Already in netsplit mode, reset, %ss more' % duration) | |
675 | else: | |
676 | i.netsplit = time.time()+duration | |
677 | self.logChannel(irc,"INFO: netsplit activated for %ss by %s: some abuses are ignored" % (duration,msg.nick)) | |
678 | irc.replySuccess() | |
679 | netsplit = wrap(netsplit,['owner','positiveInt']) | |
680 | ||
f12749a8 NC |
681 | def checkpattern (self,irc,msg,args,text): |
682 | """ <text> | |
afd11ab6 | 683 | |
c56a3fc6 | 684 | returns permanents patterns triggered by <text>""" |
f12749a8 NC |
685 | i = self.getIrc(irc) |
686 | patterns = [] | |
066ea069 | 687 | text = text.encode('utf-8').strip() |
f12749a8 NC |
688 | for k in i.patterns: |
689 | pattern = i.patterns[k] | |
690 | if pattern.match(text): | |
691 | patterns.append('#%s' % pattern.uid) | |
692 | if len(patterns): | |
f33d2f5d | 693 | irc.queueMsg(ircmsgs.privmsg(msg.nick,'%s matches: %s' % (len(patterns),', '.join(patterns)))) |
f12749a8 | 694 | else: |
a2395d14 | 695 | irc.reply('No matches') |
f12749a8 NC |
696 | checkpattern = wrap(checkpattern,['owner','text']) |
697 | ||
6f7f2f85 NC |
698 | def lspattern (self,irc,msg,args,optlist,pattern): |
699 | """[--deep] <id|pattern> | |
30a6bd55 | 700 | |
876382df | 701 | returns patterns which matches pattern or info about pattern #id, use --deep to search on deactivated patterns, * to return all pattern""" |
6f7f2f85 | 702 | i = self.getIrc(irc) |
7ede9ecc | 703 | deep = pattern == '*' |
6f7f2f85 NC |
704 | for (option, arg) in optlist: |
705 | if option == 'deep': | |
706 | deep = True | |
6f7f2f85 NC |
707 | results = i.ls(self.getDb(irc.network),pattern,deep) |
708 | if len(results): | |
7ede9ecc | 709 | if deep or pattern == '*': |
2f79d105 NC |
710 | for r in results: |
711 | irc.queueMsg(ircmsgs.privmsg(msg.nick,r)) | |
712 | else: | |
713 | irc.replies(results,None,None,False) | |
6f7f2f85 NC |
714 | else: |
715 | irc.reply('no pattern found') | |
716 | lspattern = wrap(lspattern,['owner',getopts({'deep': ''}),'text']) | |
afd11ab6 | 717 | |
7ede9ecc NC |
718 | def rmpattern (self,irc,msg,args,ids): |
719 | """<id> [<id>] | |
720 | ||
721 | remove permanent pattern by id""" | |
722 | i = self.getIrc(irc) | |
723 | results = [] | |
724 | for id in ids: | |
725 | result = i.remove(self.getDb(irc.network),id) | |
726 | if result: | |
727 | results.append('#%s' % id) | |
728 | self.logChannel(irc,'PATTERN: %s deleted %s' % (msg.nick,','.join(results))) | |
afd11ab6 | 729 | irc.replySuccess() |
7ede9ecc | 730 | rmpattern = wrap(rmpattern,['owner',many('positiveInt')]) |
b78d8847 | 731 | |
6f7f2f85 NC |
732 | def addpattern (self,irc,msg,args,limit,life,pattern): |
733 | """<limit> <life> <pattern> | |
30a6bd55 | 734 | |
6f7f2f85 NC |
735 | add a permanent <pattern> : kline after <limit> calls raised during <life> seconds, |
736 | for immediate kline use limit 0""" | |
737 | i = self.getIrc(irc) | |
bd83123d | 738 | pattern = pattern.lower() |
6f7f2f85 | 739 | result = i.add(self.getDb(irc.network),msg.prefix,pattern,limit,life,False) |
782aa82f | 740 | self.logChannel(irc,'PATTERN: %s added #%s : "%s" %s/%ss' % (msg.nick,result,pattern,limit,life)) |
6f7f2f85 | 741 | irc.reply('#%s added' % result) |
6f7f2f85 NC |
742 | addpattern = wrap(addpattern,['owner','nonNegativeInt','positiveInt','text']) |
743 | ||
744 | def addregexpattern (self,irc,msg,args,limit,life,pattern): | |
745 | """<limit> <life> /<pattern>/ | |
30a6bd55 | 746 | |
6f7f2f85 NC |
747 | add a permanent /<pattern>/ to kline after <limit> calls raised during <life> seconds, |
748 | for immediate kline use limit 0""" | |
749 | i = self.getIrc(irc) | |
750 | result = i.add(self.getDb(irc.network),msg.prefix,pattern[0],limit,life,True) | |
751 | self.logChannel(irc,'PATTERN: %s added #%s : "%s" %s/%ss' % (msg.nick,result,pattern[0],limit,life)) | |
b78d8847 | 752 | irc.reply('#%s added' % result) |
6f7f2f85 NC |
753 | addregexpattern = wrap(addregexpattern,['owner','nonNegativeInt','positiveInt','getPatternAndMatcher']) |
754 | ||
755 | def editpattern (self,irc,msg,args,uid,limit,life,comment): | |
756 | """<id> <limit> <life> [<comment>] | |
30a6bd55 | 757 | |
6f7f2f85 NC |
758 | edit #<id> with new <limit> <life> and <comment>""" |
759 | i = self.getIrc(irc) | |
760 | result = i.edit(self.getDb(irc.network),uid,limit,life,comment) | |
761 | if result: | |
762 | if comment: | |
763 | self.logChannel(irc,'PATTERN: %s edited #%s with %s/%ss (%s)' % (msg.nick,uid,limit,life,comment)) | |
764 | else: | |
765 | self.logChannel(irc,'PATTERN: %s edited #%s with %s/%ss' % (msg.nick,uid,limit,life)) | |
766 | irc.replySuccess() | |
767 | else: | |
768 | irc.reply("#%s doesn't exist") | |
769 | editpattern = wrap(editpattern,['owner','positiveInt','nonNegativeInt','positiveInt',optional('text')]) | |
770 | ||
771 | def togglepattern (self,irc,msg,args,uid,toggle): | |
772 | """<id> <boolean> | |
30a6bd55 | 773 | |
a2395d14 | 774 | activate or deactivate #<id>""" |
6f7f2f85 NC |
775 | i = self.getIrc(irc) |
776 | result = i.toggle(self.getDb(irc.network),uid,msg.prefix,toggle) | |
777 | if result: | |
778 | if toggle: | |
779 | self.logChannel(irc,'PATTERN: %s enabled #%s' % (msg.nick,uid)) | |
780 | else: | |
781 | self.logChannel(irc,'PATTERN: %s disabled #%s' % (msg.nick,uid)) | |
782 | irc.replySuccess() | |
783 | else: | |
784 | irc.reply("#%s doesn't exist or is already in requested state" % uid) | |
785 | togglepattern = wrap(togglepattern,['owner','positiveInt','boolean']) | |
786 | ||
787 | def lstmp (self,irc,msg,args,channel): | |
788 | """[<channel>] | |
30a6bd55 | 789 | |
6f7f2f85 NC |
790 | returns temporary patterns for given channel""" |
791 | i = self.getIrc(irc) | |
792 | if channel in i.channels: | |
793 | chan = self.getChan(irc,channel) | |
794 | if chan.patterns: | |
795 | patterns = list(chan.patterns) | |
796 | if len(patterns): | |
797 | irc.reply('[%s] %s patterns : %s' % (channel,len(patterns),', '.join(patterns))) | |
798 | else: | |
43c56ec1 | 799 | irc.reply('[%s] no active pattern' % channel) |
6f7f2f85 NC |
800 | else: |
801 | irc.reply('[%s] no active pattern' % channel) | |
802 | else: | |
803 | irc.reply('[%s] is unknown' % channel) | |
804 | lstmp = wrap(lstmp,['op']) | |
a2395d14 | 805 | |
99ebdad5 NC |
806 | def dnsblresolve (self,irc,msg,args,ips): |
807 | """<ip> [,<ip>] | |
808 | ||
809 | add <ips> on dronebl, hostmasks can be provided""" | |
810 | for ip in ips: | |
811 | if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip): | |
812 | t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'),"Unknown spambot or drone")) | |
813 | t.setDaemon(True) | |
814 | t.start() | |
815 | else: | |
816 | prefix = "*!*@%s" % ip | |
817 | if ircutils.isUserHostmask(prefix): | |
818 | t = world.SupyThread(target=self.resolve,name=format('resolve %s', prefix),args=(irc,prefix,'',True,"Unknown spambot or drone")) | |
819 | t.setDaemon(True) | |
820 | t.start() | |
821 | irc.replySuccess() | |
822 | dnsblresolve = wrap(dnsblresolve,['owner',commalist('something')]) | |
f7007307 | 823 | |
8c2c160e NC |
824 | def dnsbl (self,irc,msg,args,ips,comment): |
825 | """<ip> [,<ip>] [<comment>] | |
a2395d14 | 826 | |
ff95c471 | 827 | add <ips> on dronebl, <comment> can be used to change type (Bottler|Unknown spambot or drone|DDOS Drone|SOCKS Proxy|HTTP Proxy|ProxyChain|Web Page Proxy|Open DNS Resolver|Brute force attackers|Open Wingate Proxy|Compromised router / gateway|Autorooting worms)""" |
f7007307 | 828 | for ip in ips: |
d8d19c3a | 829 | if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip): |
8c2c160e | 830 | t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'),comment)) |
f7007307 NC |
831 | t.setDaemon(True) |
832 | t.start() | |
833 | irc.replySuccess() | |
8c2c160e | 834 | dnsbl = wrap(dnsbl,['owner',commalist('ip'),rest('text')]) |
f7007307 NC |
835 | |
836 | def rmdnsbl (self,irc,msg,args,ips): | |
837 | """<ip> [<ip>] | |
a2395d14 | 838 | |
f7007307 NC |
839 | remove <ips> from dronebl""" |
840 | for ip in ips: | |
d8d19c3a | 841 | if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip): |
4ea839a3 | 842 | self.rmrequestors[ip] = msg.nick |
95ef286d | 843 | t = world.SupyThread(target=self.removeDnsbl,name=format('rmDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'))) |
f7007307 NC |
844 | t.setDaemon(True) |
845 | t.start() | |
a2395d14 | 846 | irc.replySuccess() |
f7007307 NC |
847 | rmdnsbl = wrap(rmdnsbl,['owner',many('ip')]) |
848 | ||
6f7f2f85 NC |
849 | def addtmp (self,irc,msg,args,channel,text): |
850 | """[<channel>] <message> | |
851 | ||
852 | add a string in channel's temporary patterns""" | |
853 | text = text.lower() | |
854 | i = self.getIrc(irc) | |
855 | if channel in i.channels: | |
856 | chan = self.getChan(irc,channel) | |
2ecf26e6 | 857 | shareID = self.registryValue('shareComputedPatternID',channel=channel) |
ff95c471 | 858 | if shareID == -1 or not i.defcon: |
2ecf26e6 NC |
859 | life = self.registryValue('computedPatternLife',channel=channel) |
860 | if not chan.patterns: | |
861 | chan.patterns = utils.structures.TimeoutQueue(life) | |
862 | elif chan.patterns.timeout != life: | |
863 | chan.patterns.setTimeout(life) | |
864 | chan.patterns.enqueue(text) | |
865 | self.logChannel(irc,'PATTERN: [%s] added tmp "%s" for %ss by %s' % (channel,text,life,msg.nick)) | |
866 | irc.replySuccess() | |
867 | else: | |
868 | n = 0 | |
a2395d14 | 869 | l = self.registryValue('computedPatternLife',channel=channel) |
2ecf26e6 NC |
870 | for channel in i.channels: |
871 | chan = self.getChan(irc,channel) | |
e58750d3 | 872 | id = self.registryValue('shareComputedPatternID',channel=channel) |
2ecf26e6 NC |
873 | if id == shareID: |
874 | life = self.registryValue('computedPatternLife',channel=channel) | |
875 | if not chan.patterns: | |
876 | chan.patterns = utils.structures.TimeoutQueue(life) | |
877 | elif chan.patterns.timeout != life: | |
878 | chan.patterns.setTimeout(life) | |
879 | chan.patterns.enqueue(text) | |
880 | n = n + 1 | |
70bd3e0c | 881 | self.logChannel(irc,'PATTERN: added tmp "%s" for %ss by %s in %s channels' % (text,l,msg.nick,n)) |
2ecf26e6 | 882 | irc.replySuccess() |
6f7f2f85 NC |
883 | else: |
884 | irc.reply('unknown channel') | |
885 | addtmp = wrap(addtmp,['op','text']) | |
b78d8847 | 886 | |
d3ef70da NC |
887 | def addglobaltmp (self,irc,msg,args,text): |
888 | """<text> | |
a2395d14 | 889 | |
45ac582a | 890 | add <text> to temporary patterns in all channels""" |
d3ef70da NC |
891 | text = text.lower() |
892 | i = self.getIrc(irc) | |
893 | n = 0 | |
894 | for channel in i.channels: | |
895 | chan = self.getChan(irc,channel) | |
896 | life = self.registryValue('computedPatternLife',channel=channel) | |
897 | if not chan.patterns: | |
898 | chan.patterns = utils.structures.TimeoutQueue(life) | |
899 | elif chan.patterns.timeout != life: | |
900 | chan.patterns.setTimeout(life) | |
901 | chan.patterns.enqueue(text) | |
902 | n = n + 1 | |
903 | self.logChannel(irc,'PATTERN: added tmp "%s" for %ss by %s in %s channels' % (text,life,msg.nick,n)) | |
904 | irc.replySuccess() | |
905 | addglobaltmp = wrap(addglobaltmp,['owner','text']) | |
906 | ||
6f7f2f85 NC |
907 | def rmtmp (self,irc,msg,args,channel): |
908 | """[<channel>] | |
b78d8847 | 909 | |
6f7f2f85 NC |
910 | remove temporary patterns for given channel""" |
911 | i = self.getIrc(irc) | |
912 | if channel in i.channels: | |
913 | chan = self.getChan(irc,channel) | |
f12749a8 NC |
914 | shareID = self.registryValue('shareComputedPatternID',channel=channel) |
915 | if shareID != -1: | |
916 | n = 0 | |
917 | for channel in i.channels: | |
918 | id = self.registryValue('shareComputedPatternID',channel=channel) | |
919 | if id == shareID: | |
920 | if i.channels[channel].patterns: | |
921 | i.channels[channel].patterns.reset() | |
a2395d14 | 922 | n = n + 1 |
f12749a8 NC |
923 | self.logChannel(irc,'PATTERN: removed tmp patterns in %s channels by %s' % (n,msg.nick)) |
924 | elif chan.patterns: | |
6f7f2f85 NC |
925 | l = len(chan.patterns) |
926 | chan.patterns.reset() | |
927 | if l: | |
928 | self.logChannel(irc,'PATTERN: [%s] removed %s tmp pattern by %s' % (channel,l,msg.nick)) | |
929 | irc.replySuccess() | |
930 | else: | |
931 | irc.reply('[%s] no active pattern' % channel) | |
932 | else: | |
933 | irc.reply('[%s] no active pattern' % channel) | |
934 | else: | |
935 | irc.reply('unknown channel') | |
936 | rmtmp = wrap(rmtmp,['op']) | |
937 | ||
c83d9b65 | 938 | def unkline (self,irc,msg,args,nick): |
a5fc07c7 | 939 | """<nick> |
c83d9b65 NC |
940 | request unkline of <nick>, klined recently from your channel |
941 | """ | |
942 | channels = [] | |
943 | ops = [] | |
12a3933a | 944 | nick = nick.lower() |
c83d9b65 NC |
945 | for channel in irc.state.channels: |
946 | if msg.nick in irc.state.channels[channel].ops: | |
947 | chan = self.getChan(irc,channel) | |
948 | if len(chan.klines): | |
949 | for q in chan.klines: | |
99ebdad5 NC |
950 | self.log.info('klines found %s' % q) |
951 | if q.startswith(nick): | |
c83d9b65 | 952 | ip = q.split(' ')[1] |
c83d9b65 | 953 | channels.append(channel) |
4165a619 | 954 | if not isCloaked('%s!%s' % (nick,ip),self): |
f35897a2 | 955 | if self.registryValue('useOperServ'): |
7769a3cb | 956 | irc.sendMsg(ircmsgs.IrcMsg('PRIVMSG OperServ :AKILL DEL %s' % ip)) |
f35897a2 NC |
957 | else: |
958 | irc.queueMsg(ircmsgs.IrcMsg('UNKLINE %s' % ip)) | |
c27f7c2a NC |
959 | if self.registryValue('clearTmpPatternOnUnkline',channel=channel): |
960 | if chan.patterns and len(chan.patterns): | |
961 | self.logChannel(irc,'PATTERN: [%s] removed %s tmp pattern by %s' % (channel,len(chan.patterns),msg.nick)) | |
962 | chan.patterns.reset() | |
066ea069 NC |
963 | self.logChannel(irc,'OP: [%s] %s unklined %s (%s)' % (channel,msg.nick,ip,nick)) |
964 | irc.reply('The ban on %s from %s has been lifted' % (nick,channel)) | |
a2395d14 | 965 | else: |
066ea069 | 966 | self.logChannel(irc,'OP: [%s] %s asked for removal of %s (%s)' % (channel,msg.nick,ip,nick)) |
7f2e65ae | 967 | irc.reply(self.registryValue('msgInviteConfirm')) |
c83d9b65 | 968 | ops.append(channel) |
066ea069 NC |
969 | if len(ops): |
970 | if not len(channels): | |
971 | irc.replyError("'%s' does not match any recent bans from %s" % (nick,', '.join(ops))) | |
21efdd22 | 972 | else: |
066ea069 | 973 | irc.replyError("Only **Opped** channel operators of the channel the ban originated in can remove k-lines. If you have any questions, contact freenode staff (#freenode-sigyn)") |
c83d9b65 NC |
974 | unkline = wrap(unkline,['private','text']) |
975 | ||
976 | ||
6f7f2f85 | 977 | def oper (self,irc,msg,args): |
30a6bd55 VL |
978 | """takes no arguments |
979 | ||
6f7f2f85 NC |
980 | ask bot to oper""" |
981 | if len(self.registryValue('operatorNick')) and len(self.registryValue('operatorPassword')): | |
982 | irc.sendMsg(ircmsgs.IrcMsg('OPER %s %s' % (self.registryValue('operatorNick'),self.registryValue('operatorPassword')))) | |
983 | irc.replySuccess() | |
984 | else: | |
985 | irc.replyError('operatorNick or operatorPassword is empty') | |
986 | oper = wrap(oper,['owner']) | |
b78d8847 | 987 | |
5ecb3350 NC |
988 | def undline (self,irc,msg,args,txt): |
989 | """<ip> | |
990 | undline an ip | |
991 | """ | |
992 | irc.queueMsg(ircmsgs.IrcMsg('UNDLINE %s on *' % txt)) | |
993 | irc.replySuccess() | |
994 | undline = wrap(undline,['owner','ip']) | |
995 | ||
996 | ||
997 | def checkresolve (self,irc,msg,args,txt): | |
a2395d14 AE |
998 | """<nick!ident@hostmask> |
999 | ||
5ecb3350 NC |
1000 | returns computed hostmask""" |
1001 | irc.reply(self.prefixToMask(irc,txt)) | |
13f59897 | 1002 | checkresolve = wrap(checkresolve,['owner','hostmask']) |
5ecb3350 | 1003 | |
6f7f2f85 | 1004 | # internal stuff |
a2395d14 | 1005 | |
d4b544e7 NC |
1006 | def applyDefcon (self, irc): |
1007 | i = self.getIrc(irc) | |
1008 | for channel in irc.state.channels: | |
1009 | if irc.isChannel(channel) and self.registryValue('defconMode',channel=channel): | |
1010 | chan = self.getChan(irc,channel) | |
1011 | if i.defcon or chan.called: | |
1012 | if not 'z' in irc.state.channels[channel].modes: | |
1013 | if irc.nick in list(irc.state.channels[channel].ops): | |
1014 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s +qz $~a' % channel)) | |
1015 | else: | |
1016 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s +oqz %s $~a' % (channel,irc.nick))) | |
1017 | ||
1018 | ||
1019 | ||
a228e729 NC |
1020 | def _ip_ranges (self, h): |
1021 | if '/' in h: | |
e706f1be | 1022 | # we've got a cloak |
1023 | parts = h.split('/') | |
1024 | if parts[0] == 'gateway' and parts[-1].startswith('ip.'): | |
1025 | # we've got a dehexed gateway IP cloak | |
1026 | h = parts[-1].split('.', 1)[1] | |
1027 | else: | |
1028 | return [h] | |
1029 | ||
a228e729 | 1030 | if utils.net.isIPV4(h): |
e706f1be | 1031 | prefixes = [27, 26, 25, 24] |
1032 | elif utils.net.bruteIsIPV6(h): | |
f1d4928c | 1033 | # noteworthy IPv6 allocation information |
1034 | # - linode assigns a /128 by default. can also offer /56, /64 & /116 | |
ebd109b5 | 1035 | # - xfinity (comcast) has been reported as offering /60 |
f1d4928c | 1036 | # - hurricane electric tunnel brokers get a /48 |
1037 | ||
ebd109b5 | 1038 | prefixes = [120, 118, 116, 114, 112, 110, 64, 60, 56, 48] |
e706f1be | 1039 | else: |
1040 | return [h] | |
1041 | ||
1042 | ranges = [] | |
1043 | for prefix in prefixes: | |
1044 | range = ipaddress.ip_network('%s/%d' % (h, prefix), strict=False).with_prefixlen | |
1045 | ranges.append(range) | |
1046 | return ranges | |
6f7f2f85 | 1047 | |
5886af90 | 1048 | def resolve (self,irc,prefix,channel='',dnsbl=False,comment=False): |
b78d8847 NC |
1049 | (nick,ident,host) = ircutils.splitHostmask(prefix) |
1050 | if ident.startswith('~'): | |
1051 | ident = '*' | |
d3ef70da NC |
1052 | if prefix in self.cache: |
1053 | return self.cache[prefix] | |
b78d8847 NC |
1054 | try: |
1055 | resolver = dns.resolver.Resolver() | |
1056 | resolver.timeout = self.registryValue('resolverTimeout') | |
1057 | resolver.lifetime = self.registryValue('resolverTimeout') | |
b78d8847 | 1058 | L = [] |
5ecb3350 NC |
1059 | ips = None |
1060 | try: | |
1061 | ips = resolver.query(host,'AAAA') | |
1062 | except: | |
1063 | ips = None | |
f90ccc3a NC |
1064 | if ips: |
1065 | for ip in ips: | |
5ecb3350 NC |
1066 | if not str(ip) in L: |
1067 | L.append(str(ip)) | |
1068 | try: | |
1069 | ips = resolver.query(host,'A') | |
1070 | except: | |
1071 | ips = None | |
1072 | if ips: | |
1073 | for ip in ips: | |
1074 | if not str(ip) in L: | |
1075 | L.append(str(ip)) | |
aaf3c542 | 1076 | #self.log.debug('%s resolved as %s' % (prefix,L)) |
b78d8847 NC |
1077 | if len(L) == 1: |
1078 | h = L[0] | |
aaf3c542 | 1079 | #self.log.debug('%s is resolved as %s@%s' % (prefix,ident,h)) |
d8d19c3a NC |
1080 | if dnsbl: |
1081 | if utils.net.isIPV4(h) or utils.net.bruteIsIPV6(h): | |
1082 | if len(self.registryValue('droneblKey')) and len(self.registryValue('droneblHost')) and self.registryValue('enable'): | |
1083 | t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', h),args=(irc,h,self.registryValue('droneblHost'),self.registryValue('droneblKey'),comment)) | |
1084 | t.setDaemon(True) | |
1085 | t.start() | |
1086 | if prefix in i.resolving: | |
1087 | del i.resolving[prefix] | |
1088 | return | |
f90ccc3a | 1089 | self.cache[prefix] = '%s@%s' % (ident,h) |
b78d8847 | 1090 | else: |
f90ccc3a | 1091 | self.cache[prefix] = '%s@%s' % (ident,host) |
b78d8847 | 1092 | except: |
f90ccc3a | 1093 | self.cache[prefix] = '%s@%s' % (ident,host) |
66b6dfb5 | 1094 | i = self.getIrc(irc) |
d3ef70da NC |
1095 | if channel and channel in irc.state.channels: |
1096 | chan = self.getChan(irc,channel) | |
1097 | if nick in irc.state.channels[channel].users: | |
1098 | if nick in chan.nicks: | |
1099 | chan.nicks[nick][2] = self.cache[prefix] | |
66b6dfb5 NC |
1100 | if prefix in i.resolving: |
1101 | del i.resolving[prefix] | |
b78d8847 | 1102 | |
5886af90 | 1103 | def prefixToMask (self,irc,prefix,channel='',dnsbl=False,comment=None): |
f90ccc3a NC |
1104 | if prefix in self.cache: |
1105 | return self.cache[prefix] | |
756385c4 | 1106 | prefix = prefix |
b78d8847 NC |
1107 | (nick,ident,host) = ircutils.splitHostmask(prefix) |
1108 | if '/' in host: | |
1109 | if host.startswith('gateway/web/freenode'): | |
1110 | if 'ip.' in host: | |
f90ccc3a | 1111 | self.cache[prefix] = '*@%s' % host.split('ip.')[1] |
b78d8847 NC |
1112 | else: |
1113 | # syn offline / busy | |
f90ccc3a | 1114 | self.cache[prefix] = '%s@gateway/web/freenode/*' % ident |
b78d8847 | 1115 | elif host.startswith('gateway/tor-sasl'): |
f90ccc3a | 1116 | self.cache[prefix] = '*@%s' % host |
53ff6dfb | 1117 | elif host.startswith('gateway/vpn') or host.startswith('nat/'): |
62250834 NC |
1118 | if ident.startswith('~'): |
1119 | ident = '*' | |
1120 | if '/x-' in host: | |
53ff6dfb | 1121 | host = host.split('/x-')[0] + '/*' |
62250834 | 1122 | self.cache[prefix] = '%s@%s' % (ident,host) |
b78d8847 NC |
1123 | elif host.startswith('gateway'): |
1124 | h = host.split('/') | |
1125 | if 'ip.' in host: | |
1126 | ident = '*' | |
1127 | h = host.split('ip.')[1] | |
1128 | elif '/vpn/' in host: | |
62250834 | 1129 | if '/x-' in host: |
b78d8847 NC |
1130 | h = h[:3] |
1131 | h = '%s/*' % '/'.join(h) | |
1132 | else: | |
1133 | h = host | |
1134 | if ident.startswith('~'): | |
1135 | ident = '*' | |
1136 | elif len(h) > 3: | |
1137 | h = h[:3] | |
1138 | h = '%s/*' % '/'.join(h) | |
1139 | else: | |
1140 | h = host | |
f90ccc3a | 1141 | self.cache[prefix] = '%s@%s' % (ident,h) |
b78d8847 NC |
1142 | else: |
1143 | if ident.startswith('~'): | |
1144 | ident = '*' | |
f90ccc3a | 1145 | self.cache[prefix] = '%s@%s' % (ident,host) |
b78d8847 NC |
1146 | else: |
1147 | if ident.startswith('~'): | |
1148 | ident = '*' | |
1149 | if utils.net.isIPV4(host): | |
f90ccc3a | 1150 | self.cache[prefix] = '%s@%s' % (ident,host) |
b78d8847 | 1151 | elif utils.net.bruteIsIPV6(host): |
bcee8da5 | 1152 | self.cache[prefix] = '%s@%s' % (ident,host) |
b78d8847 | 1153 | else: |
66b6dfb5 | 1154 | i = self.getIrc(irc) |
bd83123d NC |
1155 | if self.registryValue('useWhoWas'): |
1156 | self.cache[prefix] = '%s@%s' % (ident,host) | |
1157 | elif not prefix in i.resolving: | |
66b6dfb5 | 1158 | i.resolving[prefix] = True |
5886af90 | 1159 | t = world.SupyThread(target=self.resolve,name=format('resolve %s', prefix),args=(irc,prefix,channel,dnsbl,comment)) |
66b6dfb5 NC |
1160 | t.setDaemon(True) |
1161 | t.start() | |
bd83123d | 1162 | return '%s@%s' % (ident,host) |
f90ccc3a NC |
1163 | if prefix in self.cache: |
1164 | return self.cache[prefix] | |
1165 | else: | |
1166 | if ident.startswith('~'): | |
1167 | ident = '*' | |
1168 | return '%s@%s' % (ident,host) | |
b78d8847 | 1169 | |
45ac582a | 1170 | def do352 (self,irc,msg): |
afd11ab6 | 1171 | # RPL_WHOREPLY |
bd83123d | 1172 | channel = msg.args[1] |
d3ef70da | 1173 | (nick, ident, host) = (msg.args[5], msg.args[2], msg.args[3]) |
bd83123d NC |
1174 | if irc.isChannel(channel): |
1175 | chan = self.getChan(irc,channel) | |
1176 | t = time.time() | |
1177 | prefix = '%s!%s@%s' % (nick,ident,host) | |
1178 | mask = self.prefixToMask(irc,prefix,channel) | |
1179 | if isCloaked(prefix,self): | |
1180 | t = t - self.registryValue('ignoreDuration',channel=channel) - 1 | |
1181 | chan.nicks[nick] = [t,prefix,mask,'',''] | |
d3ef70da | 1182 | |
2bcb4fa2 NC |
1183 | def spam (self,irc,msg,args,channel): |
1184 | """<channel> | |
afd11ab6 | 1185 | |
2bcb4fa2 NC |
1186 | trusted users can ask the bot to join <channel> for a limited period of time |
1187 | """ | |
1188 | if not channel in irc.state.channels: | |
d8b0ea2d | 1189 | t = time.time() - (self.registryValue('leaveChannelIfNoActivity',channel=channel) * 24 * 3600) + 3600 |
2bcb4fa2 NC |
1190 | self.setRegistryValue('lastActionTaken',t,channel=channel) |
1191 | irc.sendMsg(ircmsgs.join(channel)) | |
1192 | chan = self.getChan(irc,channel) | |
1193 | chan.requestedBySpam = True | |
1194 | self.logChannel(irc,"JOIN: [%s] due to %s (trusted)" % (channel,msg.prefix)) | |
1195 | try: | |
1196 | network = conf.supybot.networks.get(irc.network) | |
1197 | network.channels().add(channel) | |
1198 | except KeyError: | |
1199 | pass | |
1200 | irc.replySuccess() | |
1201 | spam = wrap(spam,[('checkCapability','trusted'),'channel']) | |
1202 | ||
95ef286d NC |
1203 | def unstaffed (self,irc,msg,args): |
1204 | """ | |
1205 | ||
1206 | returns monitored channels without staffers | |
1207 | """ | |
1208 | channels = [] | |
1209 | for channel in irc.state.channels: | |
1210 | found = False | |
1211 | for nick in list(irc.state.channels[channel].users): | |
1212 | try: | |
1213 | hostmask = irc.state.nickToHostmask(nick) | |
2842e065 | 1214 | if ircutils.isUserHostmask(hostmask) and self.registryValue('staffCloak') in hostmask: |
95ef286d NC |
1215 | found = True |
1216 | break | |
1217 | except: | |
1218 | continue | |
1219 | if not found: | |
1220 | channels.append(channel) | |
79971e5d | 1221 | irc.reply('%s channels: %s' %(len(channels),', '.join(channels))) |
95ef286d NC |
1222 | unstaffed = wrap(unstaffed,['owner']) |
1223 | ||
79971e5d NC |
1224 | def list (self,irc,msg,args): |
1225 | """ | |
afd11ab6 | 1226 | |
79971e5d NC |
1227 | returns list of monitored channels with their users count and * if leaveChannelIfNoActivity is -1 |
1228 | """ | |
1229 | channels = [] | |
1230 | for channel in list(irc.state.channels): | |
1231 | flag = '' | |
1232 | if self.registryValue('leaveChannelIfNoActivity',channel=channel) == -1: | |
1233 | flag = '*' | |
1234 | l = len(irc.state.channels[channel].users) | |
6a9e943e NC |
1235 | if not channel == self.registryValue('secretChannel') and not channel == self.registryValue('snoopChannel') and not channel == self.registryValue('reportChannel') and not channel == self.registryValue('logChannel'): |
1236 | channels.append((l,flag,channel)) | |
21efdd22 NC |
1237 | def getKey(item): |
1238 | return item[0] | |
1239 | chs = sorted(channels,key=getKey,reverse=True) | |
1240 | channels = [] | |
1241 | for c in chs: | |
1242 | (l,flag,channel) = c | |
79971e5d NC |
1243 | channels.append('%s %s(%s)' % (channel,flag,l)) |
1244 | irc.reply('%s channels: %s' %(len(channels),', '.join(channels))) | |
1245 | list = wrap(list,['owner']) | |
1246 | ||
6f7f2f85 | 1247 | def do001 (self,irc,msg): |
b78d8847 | 1248 | i = self.getIrc(irc) |
6f7f2f85 NC |
1249 | if not i.opered: |
1250 | if len(self.registryValue('operatorNick')) and len(self.registryValue('operatorPassword')): | |
b78d8847 | 1251 | irc.queueMsg(ircmsgs.IrcMsg('OPER %s %s' % (self.registryValue('operatorNick'),self.registryValue('operatorPassword')))) |
6f7f2f85 NC |
1252 | |
1253 | def do381 (self,irc,msg): | |
1254 | i = self.getIrc(irc) | |
1255 | if not i.opered: | |
1256 | i.opered = True | |
b888f64b | 1257 | irc.queueMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick)) |
ff95c471 | 1258 | irc.queueMsg(ircmsgs.IrcMsg('MODE %s +s +Fbnfl' % irc.nick)) |
4aae7f45 | 1259 | try: |
f7007307 | 1260 | conf.supybot.protocols.irc.throttleTime.setValue(0.0) |
4aae7f45 NC |
1261 | except: |
1262 | t = True | |
1263 | ||
6f7f2f85 NC |
1264 | def doMode (self,irc,msg): |
1265 | target = msg.args[0] | |
1266 | if target == irc.nick: | |
1267 | i = self.getIrc(irc) | |
1268 | modes = ircutils.separateModes(msg.args[1:]) | |
1269 | for change in modes: | |
1270 | (mode,value) = change | |
4aae7f45 | 1271 | if mode == '-o': |
6f7f2f85 NC |
1272 | i.opered = False |
1273 | if len(self.registryValue('operatorNick')) and len(self.registryValue('operatorPassword')): | |
4aae7f45 | 1274 | irc.queueMsg(ircmsgs.IrcMsg('OPER %s %s' % (self.registryValue('operatorNick'),self.registryValue('operatorPassword')))) |
47e9a74a NC |
1275 | elif mode == '+p': |
1276 | i.god = True | |
1277 | self.log.debug('%s is switching to god' % irc.nick) | |
d4b544e7 | 1278 | self.applyDefcon(irc) |
47e9a74a NC |
1279 | elif mode == '-p': |
1280 | i.god = False | |
a2395d14 | 1281 | self.log.debug('%s is switching to mortal' % irc.nick) |
bb25730c NC |
1282 | elif target in irc.state.channels and 'm' in irc.state.channels[target].modes: |
1283 | modes = ircutils.separateModes(msg.args[1:]) | |
1284 | for change in modes: | |
1285 | (mode,value) = change | |
1286 | if mode == '+v': | |
1287 | chan = self.getChan(irc,target) | |
1288 | if value in chan.nicks: | |
1289 | a = chan.nicks[value] | |
1290 | if len(a) == 5: | |
1291 | chan.nicks[msg.nick] = [time.time(),a[1],a[2],a[3],a[4]] | |
1292 | else: | |
1293 | chan.nicks[msg.nick] = [time.time(),a[1],a[2],'',''] | |
cd27d8a5 NC |
1294 | elif target in irc.state.channels: |
1295 | modes = ircutils.separateModes(msg.args[1:]) | |
1296 | for change in modes: | |
1297 | (mode,value) = change | |
c22f5ade | 1298 | if mode == '+z': |
cd27d8a5 NC |
1299 | if not irc.nick in list(irc.state.channels[target].ops): |
1300 | irc.queueMsg(ircmsgs.IrcMsg('PRIVMSG ChanServ :OP %s' % target)) | |
1301 | if target == self.registryValue('mainChannel'): | |
1302 | self.opStaffers(irc) | |
1303 | elif mode == '+b' or mode == '+q': | |
1304 | if ircutils.isUserHostmask(value): | |
1305 | mask = self.prefixToMask(irc,value) | |
a2395d14 | 1306 | ip = mask.split('@')[1] |
18f102f7 | 1307 | permit = self.registryValue('banPermit') |
a228e729 NC |
1308 | if permit > -1: |
1309 | ipranges = self._ip_ranges(ip) | |
55afd715 | 1310 | announced = False |
a228e729 | 1311 | for range in ipranges: |
54deeac6 | 1312 | range = range |
18f102f7 | 1313 | q = self.getIrcQueueFor(irc,'ban-check',range,self.registryValue('banLife')) |
a228e729 NC |
1314 | q.enqueue(target) |
1315 | if len(q) > permit: | |
1316 | chs = [] | |
1317 | for m in q: | |
1318 | chs.append(m) | |
1319 | q.reset() | |
55afd715 NC |
1320 | if not announced: |
1321 | announced = True | |
18f102f7 | 1322 | self.logChannel(irc,"INFO: *@%s is collecting bans (%s/%ss) %s" % (range, permit, self.registryValue('banLife'), ','.join(chs))) |
a2395d14 AE |
1323 | permit = permit + 1 |
1324 | ||
c22f5ade NC |
1325 | def opStaffers (self,irc): |
1326 | ops = [] | |
cd27d8a5 NC |
1327 | if self.registryValue('mainChannel') in irc.state.channels and irc.nick in list(irc.state.channels[self.registryValue('mainChannel')].ops): |
1328 | for nick in list(irc.state.channels[self.registryValue('mainChannel')].users): | |
1329 | if not nick in list(irc.state.channels[self.registryValue('mainChannel')].ops): | |
c22f5ade NC |
1330 | try: |
1331 | mask = irc.state.nickToHostmask(nick) | |
7f2e65ae | 1332 | if mask and self.registryValue('staffCloak') in mask: |
c22f5ade NC |
1333 | ops.append(nick) |
1334 | except: | |
1335 | continue | |
1336 | if len(ops): | |
1337 | for i in range(0, len(ops), 4): | |
0c38e343 | 1338 | irc.sendMsg(ircmsgs.ops(self.registryValue('mainChannel'),ops[i:i+4],irc.prefix)) |
6f7f2f85 NC |
1339 | |
1340 | def getIrc (self,irc): | |
1341 | if not irc.network in self._ircs: | |
1342 | self._ircs[irc.network] = Ircd(irc) | |
1343 | self._ircs[irc.network].restore(self.getDb(irc.network)) | |
1344 | if len(self.registryValue('operatorNick')) and len(self.registryValue('operatorPassword')): | |
4aae7f45 | 1345 | irc.queueMsg(ircmsgs.IrcMsg('OPER %s %s' % (self.registryValue('operatorNick'),self.registryValue('operatorPassword')))) |
6f7f2f85 | 1346 | return self._ircs[irc.network] |
b78d8847 | 1347 | |
d6127bfb | 1348 | def doAccount (self,irc,msg): |
ff95c471 | 1349 | i = self.getIrc(irc) |
d6127bfb NC |
1350 | if ircutils.isUserHostmask(msg.prefix): |
1351 | nick = ircutils.nickFromHostmask(msg.prefix) | |
1352 | acc = msg.args[0] | |
1353 | if acc == '*': | |
1354 | acc = None | |
ff95c471 NC |
1355 | else: |
1356 | aa = acc.lower() | |
99ebdad5 NC |
1357 | for u in i.klinednicks: |
1358 | if aa == u: | |
18f102f7 | 1359 | self.logChannel(irc,"SERVICE: %s (%s) lethal account (account-notify)" % (msg.prefix,acc)) |
99ebdad5 | 1360 | src = msg.nick |
18f102f7 | 1361 | i.klinednicks.enqueue(aa) |
99ebdad5 NC |
1362 | if not src in i.tokline: |
1363 | i.toklineresults[src] = {} | |
1364 | i.toklineresults[src]['kind'] = 'evade' | |
1365 | i.tokline[src] = src | |
18f102f7 NC |
1366 | def f (): |
1367 | irc.sendMsg(ircmsgs.IrcMsg('WHOIS %s %s' % (src,src))) | |
1368 | schedule.addEvent(f,time.time()+random.randint(0,7)) | |
1369 | #irc.sendMsg(ircmsgs.IrcMsg('WHOIS %s %s' % (src,src))) | |
99ebdad5 | 1370 | break |
d6127bfb NC |
1371 | for channel in irc.state.channels: |
1372 | if irc.isChannel(channel): | |
1373 | chan = self.getChan(irc,channel) | |
1374 | if nick in chan.nicks: | |
1375 | a = chan.nicks[msg.nick] | |
1376 | if len(a) == 5: | |
1377 | chan.nicks[msg.nick] = [a[0],a[1],a[2],a[3],acc] | |
1378 | else: | |
1379 | chan.nicks[msg.nick] = [a[0],a[1],a[2],'',acc] | |
b78d8847 | 1380 | |
6f7f2f85 NC |
1381 | def getChan (self,irc,channel): |
1382 | i = self.getIrc(irc) | |
bd83123d | 1383 | if not channel in i.channels and irc.isChannel(channel): |
6f7f2f85 | 1384 | i.channels[channel] = Chan(channel) |
0c38e343 NC |
1385 | if not self.starting: |
1386 | irc.queueMsg(ircmsgs.who(channel)) | |
6f7f2f85 | 1387 | return i.channels[channel] |
b78d8847 | 1388 | |
06bd28a0 | 1389 | def kill (self,irc,nick,reason=None): |
6f7f2f85 | 1390 | i = self.getIrc(irc) |
3a41dff7 NC |
1391 | if i.defcon: |
1392 | i.defcon = time.time() | |
6f7f2f85 NC |
1393 | if not self.registryValue('enable'): |
1394 | self.logChannel(irc,"INFO: disabled, can't kill %s" % nick) | |
1395 | return | |
1396 | if not i.opered: | |
1397 | self.logChannel(irc,"INFO: not opered, can't kill %s" % nick) | |
1398 | return | |
06bd28a0 NC |
1399 | if not reason: |
1400 | reason = self.registryValue('killMessage') | |
1401 | irc.sendMsg(ircmsgs.IrcMsg('KILL %s :%s' % (nick,reason))) | |
b78d8847 | 1402 | |
6f7f2f85 NC |
1403 | def do338 (self,irc,msg): |
1404 | i = self.getIrc(irc) | |
1405 | if msg.args[0] == irc.nick and msg.args[1] in i.whowas: | |
1406 | pending = i.whowas[msg.args[1]] | |
1407 | del i.whowas[msg.args[1]] | |
42f3350d | 1408 | (nick,ident,host) = ircutils.splitHostmask(pending[0]) |
6f7f2f85 NC |
1409 | # [prefix,mask,duration,reason,klineMessage] |
1410 | ident = pending[1].split('@')[0] | |
8d531ba7 NC |
1411 | h = msg.args[2] |
1412 | if h == '255.255.255.255': | |
1413 | h = host | |
1414 | mask = self.prefixToMask(irc,'%s!%s@%s' % (nick,ident,h)) | |
6f7f2f85 NC |
1415 | if not self.registryValue('enable'): |
1416 | self.logChannel(irc,"INFO: disabled, can't kline %s (%s)" % (mask,pending[3])) | |
1417 | if pending[1] in i.klines: | |
1418 | del i.klines[pending[1]] | |
1419 | return | |
1420 | if not i.opered: | |
1421 | self.logChannel(irc,"INFO: not opered, can't kline %s (%s)" % (mask,pending[3])) | |
1422 | if pending[1] in i.klines: | |
1423 | del i.klines[pending[1]] | |
1424 | return | |
13f59897 | 1425 | self.log.info('KLINE %s|%s' % (mask,pending[3])) |
f35897a2 | 1426 | if self.registryValue('useOperServ'): |
afd11ab6 | 1427 | irc.sendMsg(ircmsgs.IrcMsg('PRIVMSG OperServ :AKILL ADD %s !T %s %s | %s' % (mask,pending[2],pending[4],pending[3]))) |
f35897a2 NC |
1428 | else: |
1429 | irc.sendMsg(ircmsgs.IrcMsg('KLINE %s %s :%s|%s' % (pending[2],mask,pending[4],pending[3]))) | |
99ebdad5 | 1430 | nickLowered = nick.lower() |
96c084aa NC |
1431 | for channel in irc.state.channels: |
1432 | chan = self.getChan(irc,channel) | |
1433 | if len(chan.klines): | |
1434 | index = 0 | |
1435 | for k in chan.klines: | |
99ebdad5 | 1436 | if k.startswith(nickLowered): |
96c084aa | 1437 | (at, m) = chan.klines.queue[index] |
99ebdad5 NC |
1438 | chan.klines.queue[index] = (at,'%s %s' % (nickLowered,mask)) |
1439 | self.log.info('kline %s replaced at %s: %s / %s' % (m,index,nickLowered,mask)) | |
96c084aa NC |
1440 | break |
1441 | index = index + 1 | |
6f7f2f85 NC |
1442 | if pending[1] in i.klines: |
1443 | del i.klines[pending[1]] | |
1444 | ||
1445 | def kline (self,irc,prefix,mask,duration,reason,klineMessage=None): | |
1446 | i = self.getIrc(irc) | |
1447 | if mask in i.klines: | |
1448 | return | |
1449 | if duration < 0: | |
c27f7c2a | 1450 | self.log.info('Ignored kline %s due to no duration', mask) |
6f7f2f85 NC |
1451 | return |
1452 | if not klineMessage: | |
1453 | klineMessage = self.registryValue('klineMessage') | |
1454 | if '"' in klineMessage: | |
1455 | klineMessage = self.registryValue('klineMessage') | |
066ea069 | 1456 | canKline = True |
afd11ab6 | 1457 | i.klines[mask] = mask |
276258c4 NC |
1458 | if "bc.googleusercontent.com" in prefix: |
1459 | reason = reason + ' !dnsbl Unknown spambot or drone' | |
066ea069 NC |
1460 | if ircutils.isUserHostmask(prefix): |
1461 | canKline = not self.registryValue('useWhoWas') | |
1462 | if i.defcon or 'gateway/' in prefix: | |
1463 | canKline = True | |
1aac7048 NC |
1464 | elif '/' in prefix: |
1465 | canKline = False | |
066ea069 NC |
1466 | else: |
1467 | self.log.info('INVALID PREFIX %s : %s : %s' % (prefix,mask,reason)) | |
8d531ba7 | 1468 | self.log.info('CANKLINE %s %s %s' % (prefix,mask,canKline)) |
6f7f2f85 NC |
1469 | if canKline: |
1470 | if not self.registryValue('enable'): | |
1471 | self.logChannel(irc,"INFO: disabled, can't kline %s (%s)" % (mask,reason)) | |
1472 | else: | |
13f59897 | 1473 | self.log.info('KLINE %s|%s' % (mask,reason)) |
f35897a2 | 1474 | if self.registryValue('useOperServ'): |
afd11ab6 | 1475 | irc.sendMsg(ircmsgs.IrcMsg('PRIVMSG OperServ :AKILL ADD %s !T %s %s | %s' % (mask,duration,klineMessage,reason))) |
f35897a2 NC |
1476 | else: |
1477 | irc.sendMsg(ircmsgs.IrcMsg('KLINE %s %s :%s|%s' % (duration,mask,klineMessage,reason))) | |
1c77429f NC |
1478 | if i.defcon: |
1479 | i.defcon = time.time() | |
4165a619 NC |
1480 | elif ircutils.isUserHostmask(prefix): |
1481 | (nick,ident,host) = ircutils.splitHostmask(prefix) | |
99ebdad5 | 1482 | self.log.info('whowas for %s | %s | %s' % (prefix,mask,reason)) |
4165a619 NC |
1483 | if not nick in i.whowas: |
1484 | i.whowas[nick] = [prefix,mask,duration,reason,klineMessage] | |
1485 | irc.sendMsg(ircmsgs.IrcMsg('WHOWAS %s' % nick)) | |
6f7f2f85 | 1486 | def forgetKline (): |
fca1dc9d | 1487 | i = self.getIrc(irc) |
6f7f2f85 NC |
1488 | if mask in i.klines: |
1489 | del i.klines[mask] | |
1490 | schedule.addEvent(forgetKline,time.time()+7) | |
b78d8847 | 1491 | |
06bd28a0 NC |
1492 | def ban (self,irc,nick,prefix,mask,duration,reason,message,log,killReason=None): |
1493 | self.kill(irc,nick,killReason) | |
6f7f2f85 NC |
1494 | self.kline(irc,prefix,mask,duration,reason,message) |
1495 | self.logChannel(irc,log) | |
b78d8847 | 1496 | |
6f7f2f85 NC |
1497 | def getIrcQueueFor (self,irc,key,kind,life): |
1498 | i = self.getIrc(irc) | |
1499 | if not key in i.queues: | |
1500 | i.queues[key] = {} | |
1501 | if not kind in i.queues[key]: | |
1502 | i.queues[key][kind] = utils.structures.TimeoutQueue(life) | |
1503 | elif i.queues[key][kind].timeout != life: | |
cf659704 | 1504 | i.queues[key][kind].setTimeout(life) |
6f7f2f85 NC |
1505 | return i.queues[key][kind] |
1506 | ||
1507 | def rmIrcQueueFor (self,irc,key): | |
1508 | i = self.getIrc(irc) | |
1509 | if key in i.queues: | |
1510 | for k in i.queues[key]: | |
1511 | if type(i.queues[key][k]) == utils.structures.TimeoutQueue: | |
1512 | i.queues[key][k].reset() | |
1513 | i.queues[key][k].queue = None | |
1514 | i.queues[key].clear() | |
1515 | del i.queues[key] | |
1516 | ||
28164a19 | 1517 | def do015 (self,irc,msg): |
66b6dfb5 NC |
1518 | try: |
1519 | (targets,text) = msg.args | |
1520 | i = self.getIrc(irc) | |
70bd3e0c | 1521 | reg = r".*-\s+([a-z]+\.freenode\.net)\[.*Users:\s+(\d{2,6})\s+" |
66b6dfb5 | 1522 | result = re.match(reg,text) |
7f2e65ae | 1523 | # here we store server name and users count, and we will ping the server with the most users |
66b6dfb5 NC |
1524 | if result: |
1525 | i.servers[result.group(1)] = int(result.group(2)) | |
43c56ec1 | 1526 | except: |
7f2e65ae | 1527 | pass |
28164a19 NC |
1528 | |
1529 | def do017 (self,irc,msg): | |
1530 | found = None | |
1531 | users = None | |
1532 | i = self.getIrc(irc) | |
1533 | for server in i.servers: | |
1534 | if not users or users < i.servers[server]: | |
1535 | found = server | |
1536 | users = i.servers[server] | |
87804149 NC |
1537 | server = None |
1538 | if found: | |
1539 | i.servers = {} | |
70bd3e0c | 1540 | server = '%s' % found |
87804149 NC |
1541 | i.servers[server] = time.time() |
1542 | def bye(): | |
fca1dc9d | 1543 | i = self.getIrc(irc) |
87804149 NC |
1544 | if server in i.servers: |
1545 | del i.servers[server] | |
1546 | if not i.netsplit: | |
daaa2be5 | 1547 | self.logChannel(irc,'INFO: netsplit activated for %ss due to %s/%ss of lags with %s : some abuses are ignored' % (self.registryValue('netsplitDuration'),self.registryValue('lagPermit'),self.registryValue('lagPermit'),server)) |
87804149 | 1548 | i.netsplit = time.time() + self.registryValue('netsplitDuration') |
2ecf26e6 | 1549 | schedule.addEvent(bye,time.time()+self.registryValue('lagPermit')) |
87804149 | 1550 | irc.queueMsg(ircmsgs.IrcMsg('TIME %s' % server)) |
28164a19 | 1551 | |
d3ef70da | 1552 | def resync (self,irc,msg,args): |
45ac582a NC |
1553 | """in case of plugin being reloaded |
1554 | call this to recompute user to ignore (ignoreDuration)""" | |
d3ef70da | 1555 | for channel in irc.state.channels: |
4165a619 | 1556 | irc.queueMsg(ircmsgs.who(channel)) |
d3ef70da NC |
1557 | irc.replySuccess() |
1558 | resync = wrap(resync,['owner']) | |
1559 | ||
276258c4 | 1560 | def lethalaccount (self,irc,msg,args,text): |
99ebdad5 NC |
1561 | """<accountname> monitor account and kline it on sight |
1562 | during 24h, via extended-join, account-notify, account's name change""" | |
276258c4 | 1563 | i = self.getIrc(irc) |
b414e9a1 NC |
1564 | account = text.lower().strip() |
1565 | i.klinednicks.enqueue(account) | |
1566 | self.logChannel(irc,'SERVICE: %s lethaled for 24h by %s' % (account, msg.nick)) | |
1567 | for channel in irc.state.channels: | |
1568 | if irc.isChannel(channel): | |
1569 | c = self.getChan(irc,channel) | |
1570 | for u in list(irc.state.channels[channel].users): | |
1571 | if u in c.nicks: | |
43c56ec1 | 1572 | if len(c.nicks[u]) > 4: |
066ea069 | 1573 | if c.nicks[u][4] and c.nicks[u][4].lower() == account: |
b414e9a1 | 1574 | self.ban(irc,u,c.nicks[u][1],c.nicks[u][2],self.registryValue('klineDuration'),'Lethaled account %s' % account,self.registryValue('klineMessage'),'BAD: %s (lethaled account %s)' % (account,c.nicks[u][1]),self.registryValue('killMessage')) |
276258c4 NC |
1575 | irc.replySuccess() |
1576 | lethalaccount = wrap(lethalaccount,['owner','text']) | |
1577 | ||
87804149 NC |
1578 | def cleanup (self,irc): |
1579 | i = self.getIrc(irc) | |
ff95c471 | 1580 | partReason = 'Leaving the channel. /invite %s %s again if needed' |
eedaff77 | 1581 | for channel in irc.state.channels: |
bd83123d | 1582 | if irc.isChannel(channel) and not channel in self.registryValue('mainChannel') and not channel == self.registryValue('snoopChannel') and not channel == self.registryValue('logChannel') and not channel == self.registryValue('reportChannel') and not channel == self.registryValue('secretChannel'): |
2e246c41 | 1583 | if self.registryValue('lastActionTaken',channel=channel) > 1.0 and self.registryValue('leaveChannelIfNoActivity',channel=channel) > -1 and not i.defcon: |
eedaff77 | 1584 | if time.time() - self.registryValue('lastActionTaken',channel=channel) > (self.registryValue('leaveChannelIfNoActivity',channel=channel) * 24 * 3600): |
ff95c471 | 1585 | irc.queueMsg(ircmsgs.part(channel, partReason % (irc.nick,channel))) |
2bcb4fa2 NC |
1586 | chan = self.getChan(irc,channel) |
1587 | if chan.requestedBySpam: | |
92be5900 | 1588 | self.setRegistryValue('lastActionTaken',self.registryValue('lastActionTaken'),channel=channel) |
2bcb4fa2 NC |
1589 | else: |
1590 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) | |
eedaff77 | 1591 | self.logChannel(irc,'PART: [%s] due to inactivity for %s days' % (channel,self.registryValue('leaveChannelIfNoActivity',channel=channel))) |
27cc952d NC |
1592 | try: |
1593 | network = conf.supybot.networks.get(irc.network) | |
1594 | network.channels().remove(channel) | |
1595 | except KeyError: | |
1596 | pass | |
87804149 NC |
1597 | kinds = [] |
1598 | for kind in i.queues: | |
1599 | count = 0 | |
1600 | ks = [] | |
2ecf26e6 NC |
1601 | try: |
1602 | for k in i.queues[kind]: | |
1603 | if isinstance(i.queues[kind][k],utils.structures.TimeoutQueue): | |
1604 | if not len(i.queues[kind][k]): | |
1605 | ks.append(k) | |
1606 | else: | |
1607 | count += 1 | |
87804149 | 1608 | else: |
2ecf26e6 NC |
1609 | count += 1 |
1610 | except: | |
1611 | self.log.error('Exception with %s' % kind) | |
87804149 NC |
1612 | if len(ks): |
1613 | for k in ks: | |
1614 | del i.queues[kind][k] | |
1615 | if count == 0: | |
1616 | kinds.append(kind) | |
1617 | for kind in kinds: | |
1618 | del i.queues[kind] | |
fca1dc9d | 1619 | chs = [] |
87804149 NC |
1620 | for channel in i.channels: |
1621 | chan = i.channels[channel] | |
1622 | ns = [] | |
1623 | for n in chan.nicks: | |
1624 | if channel in irc.state.channels: | |
54deeac6 | 1625 | if not n in irc.state.channels[channel].users: |
87804149 NC |
1626 | ns.append(n) |
1627 | else: | |
1628 | ns.append(n) | |
1629 | for n in ns: | |
1630 | del chan.nicks[n] | |
1631 | bs = [] | |
1632 | for b in chan.buffers: | |
1633 | qs = [] | |
1634 | count = 0 | |
1635 | for q in chan.buffers[b]: | |
1636 | if isinstance(chan.buffers[b][q],utils.structures.TimeoutQueue): | |
1637 | if not len(chan.buffers[b][q]): | |
1638 | qs.append(q) | |
1639 | else: | |
1640 | count += 1 | |
1641 | else: | |
a2395d14 | 1642 | count +=1 |
87804149 NC |
1643 | for q in qs: |
1644 | del chan.buffers[b][q] | |
1645 | if count == 0: | |
1646 | bs.append(b) | |
1647 | for b in bs: | |
1648 | del chan.buffers[b] | |
1649 | logs = [] | |
1650 | if chan.logs: | |
1651 | for log in chan.logs: | |
1652 | if not len(chan.logs[log]): | |
1653 | logs.append(log) | |
1654 | for log in logs: | |
1655 | del chan.logs[log] | |
fca1dc9d NC |
1656 | if len(ns) or len(bs) or len(logs): |
1657 | chs.append('[%s : %s nicks, %s buffers, %s logs]' % (channel,len(ns),len(bs),len(logs))) | |
87804149 | 1658 | |
28164a19 NC |
1659 | def do391 (self,irc,msg): |
1660 | i = self.getIrc(irc) | |
394ca47c NC |
1661 | if msg.prefix in i.servers: |
1662 | delay = time.time()-i.servers[msg.prefix] | |
1663 | del i.servers[msg.prefix] | |
1664 | if delay > self.registryValue('lagPermit'): | |
1665 | if not i.netsplit: | |
a2395d14 | 1666 | self.logChannel(irc,'INFO: netsplit activated for %ss due to %s/%ss of lags with %s : some abuses are ignored' % (self.registryValue('netsplitDuration'),delay,self.registryValue('lagPermit'),msg.prefix)) |
394ca47c | 1667 | i.netsplit = time.time() + self.registryValue('netsplitDuration') |
a2395d14 | 1668 | |
92740f96 NC |
1669 | def do219 (self,irc,msg): |
1670 | i = self.getIrc(irc) | |
1671 | r = [] | |
1672 | for k in i.stats: | |
8732a8a6 | 1673 | if i.stats[k] > self.registryValue('ghostPermit'): |
92740f96 NC |
1674 | r.append(k.replace('[unknown@','').replace(']','')) |
1675 | for ip in r: | |
7f2e65ae | 1676 | irc.sendMsg(ircmsgs.IrcMsg('DLINE %s %s on * :%s' % (1440,ip,self.registryValue('msgTooManyGhost')))) |
92740f96 NC |
1677 | i.stats = {} |
1678 | if len(r): | |
1679 | self.logChannel(irc,'DOS: %s ip(s) %s' % (len(r),', '.join(r))) | |
ff95c471 NC |
1680 | if len(i.dlines): |
1681 | for l in i.dlines: | |
1682 | found = False | |
1683 | for ip in i.ilines: | |
1684 | if l in ip: | |
1685 | found = True | |
1686 | break | |
1687 | if not found: | |
1688 | self.log.info('DLINE %s|%s' % (l,self.registryValue('saslDuration'))) | |
1689 | irc.sendMsg(ircmsgs.IrcMsg('DLINE %s %s on * :%s' % (self.registryValue('saslDuration'),l,self.registryValue('saslMessage')))) | |
1690 | i.dlines = [] | |
43c56ec1 | 1691 | i.ilines = {} |
55afd715 | 1692 | |
70bd3e0c NC |
1693 | def do311 (self,irc,msg): |
1694 | i = self.getIrc(irc) | |
1695 | nick = msg.args[1] | |
1696 | if nick in i.mx: | |
1697 | ident = msg.args[2] | |
1698 | hostmask = '%s!%s@%s' % (nick,ident,msg.args[3]) | |
4fc5835d NC |
1699 | email = i.mx[nick][0] |
1700 | badmail = i.mx[nick][1] | |
1701 | mx = i.mx[nick][2] | |
6a9e943e | 1702 | freeze = i.mx[nick][3] |
70bd3e0c NC |
1703 | del i.mx[nick] |
1704 | mask = self.prefixToMask(irc,hostmask) | |
ff95c471 | 1705 | self.logChannel(irc,'SERVICE: %s registered %s with *@%s is in mxbl (%s)' % (hostmask,nick,email,mx)) |
0c38e343 | 1706 | if badmail and len(email) and len(nick): |
6a9e943e | 1707 | if not freeze: |
6a9e943e | 1708 | irc.queueMsg(ircmsgs.notice(nick,'Your account has been dropped, please register it again with a valid email address (no disposable temporary email)')) |
1c77429f NC |
1709 | elif nick in i.tokline: |
1710 | if not nick in i.toklineresults: | |
1711 | i.toklineresults[nick] = {} | |
1712 | ident = msg.args[2] | |
1713 | hostmask = '%s!%s@%s' % (nick,ident,msg.args[3]) | |
1714 | mask = self.prefixToMask(irc,hostmask) | |
a16411f4 | 1715 | gecos = msg.args[5] |
1c77429f NC |
1716 | i.toklineresults[nick]['hostmask'] = hostmask |
1717 | i.toklineresults[nick]['mask'] = mask | |
a16411f4 | 1718 | i.toklineresults[nick]['gecos'] = gecos |
a15240d5 | 1719 | |
1c77429f NC |
1720 | def do317 (self,irc,msg): |
1721 | i = self.getIrc(irc) | |
1722 | nick = msg.args[1] | |
03ed488b | 1723 | if nick in i.tokline: |
1c77429f NC |
1724 | if not nick in i.toklineresults: |
1725 | i.toklineresults[nick] = {} | |
1726 | i.toklineresults[nick]['signon'] = float(msg.args[3]) | |
1727 | ||
1728 | def do330 (self,irc,msg): | |
1729 | i = self.getIrc(irc) | |
1730 | nick = msg.args[1] | |
1731 | if nick in i.tokline: | |
1732 | if not nick in i.toklineresults: | |
1733 | i.toklineresults[nick] = {} | |
1734 | i.toklineresults[nick]['account'] = True | |
1735 | ||
1736 | def do318 (self,irc,msg): | |
1737 | i = self.getIrc(irc) | |
1738 | nick = msg.args[1] | |
99ebdad5 NC |
1739 | if nick in i.toklineresults: |
1740 | if i.toklineresults[nick]['kind'] == 'evade': | |
ff95c471 | 1741 | uid = random.randint(0,1000000) |
99ebdad5 | 1742 | irc.sendMsg(ircmsgs.IrcMsg('KLINE %s %s :%s|%s' % (self.registryValue('klineDuration'),i.toklineresults[nick]['mask'],self.registryValue('klineMessage'),'%s - kline evasion' % (uid)))) |
ff95c471 | 1743 | self.logChannel(irc,'BAD: [%s] %s (kline evasion)' % (i.toklineresults[nick]['hostmask'],uid)) |
03ed488b | 1744 | del i.tokline[nick] |
1c77429f | 1745 | del i.toklineresults[nick] |
70bd3e0c | 1746 | |
eedaff77 NC |
1747 | def doInvite(self, irc, msg): |
1748 | channel = msg.args[1] | |
79971e5d | 1749 | i = self.getIrc(irc) |
ff95c471 | 1750 | self.log.info('%s inviting %s in %s (%s | %s | %s)' % (msg.prefix,irc.nick,channel,self.registryValue('leaveChannelIfNoActivity',channel=channel),self.registryValue('lastActionTaken',channel=channel),self.registryValue('minimumUsersInChannel'))) |
be30cbe6 | 1751 | if channel and not channel in irc.state.channels and not ircdb.checkIgnored(msg.prefix): |
5fd1c53b NC |
1752 | if self.registryValue('leaveChannelIfNoActivity',channel=channel) == -1: |
1753 | irc.queueMsg(ircmsgs.join(channel)) | |
1754 | self.logChannel(irc,"JOIN: [%s] due to %s's invite" % (channel,msg.prefix)) | |
1755 | try: | |
1756 | network = conf.supybot.networks.get(irc.network) | |
1757 | network.channels().add(channel) | |
1758 | except KeyError: | |
1759 | pass | |
1760 | elif self.registryValue('lastActionTaken',channel=channel) > 0.0: | |
21efdd22 NC |
1761 | if self.registryValue('minimumUsersInChannel') > -1: |
1762 | i.invites[channel] = msg.prefix | |
1763 | irc.queueMsg(ircmsgs.IrcMsg('LIST %s' % channel)) | |
1764 | else: | |
1765 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) | |
1766 | irc.queueMsg(ircmsgs.join(channel)) | |
1767 | self.logChannel(irc,"JOIN: [%s] due to %s's invite" % (channel,msg.prefix)) | |
1768 | try: | |
1769 | network = conf.supybot.networks.get(irc.network) | |
1770 | network.channels().add(channel) | |
1771 | except KeyError: | |
1772 | pass | |
79d1a754 | 1773 | irc.queueMsg(ircmsgs.privmsg(channel,'** Warning: if there is any bot in %s which should be exempted from %s, contact staffers before it gets caught **' % (channel,irc.nick))) |
86a26f0a | 1774 | else: |
1b01fbf5 | 1775 | self.logChannel(irc,'INVITE: [%s] %s is asking for %s' % (channel,msg.prefix,irc.nick)) |
2bcb4fa2 | 1776 | irc.queueMsg(ircmsgs.privmsg(msg.nick,'The invitation to %s will be reviewed by staff' % channel)) |
21efdd22 NC |
1777 | |
1778 | def do322 (self,irc,msg): | |
1779 | i = self.getIrc(irc) | |
1780 | if msg.args[1] in i.invites: | |
87fd201a | 1781 | if int(msg.args[2]) >= self.registryValue('minimumUsersInChannel'): |
21efdd22 NC |
1782 | self.setRegistryValue('lastActionTaken',time.time(),channel=msg.args[1]) |
1783 | irc.queueMsg(ircmsgs.join(msg.args[1])) | |
1784 | try: | |
1785 | network = conf.supybot.networks.get(irc.network) | |
1786 | network.channels().add(msg.args[1]) | |
1787 | except KeyError: | |
1788 | pass | |
1789 | self.logChannel(irc,"JOIN: [%s] due to %s's invite (%s users)" % (msg.args[1],i.invites[msg.args[1]],msg.args[2])) | |
ae6fa9cf | 1790 | irc.queueMsg(ircmsgs.privmsg(msg.args[1],'** Warning: if there is any bot in %s which should be exempted from %s, contact staffers before it gets caught **' % (msg.args[1],irc.nick))) |
21efdd22 NC |
1791 | else: |
1792 | self.logChannel(irc,"INVITE: [%s] by %s denied (%s users)" % (msg.args[1],i.invites[msg.args[1]],msg.args[2])) | |
1793 | (nick,ident,host) = ircutils.splitHostmask(i.invites[msg.args[1]]) | |
d915dd74 | 1794 | irc.queueMsg(ircmsgs.privmsg(nick,'Invitation denied, there are only %s users in %s (%s minimum for %s): contact staffers if needed.' % (msg.args[2],msg.args[1],self.registryValue('minimumUsersInChannel'),irc.nick))) |
21efdd22 | 1795 | del i.invites[msg.args[1]] |
86a26f0a | 1796 | |
6a9e943e | 1797 | def resolveSnoopy (self,irc,account,email,badmail,freeze): |
aaf3c542 NC |
1798 | resolver = dns.resolver.Resolver() |
1799 | resolver.timeout = 10 | |
1800 | resolver.lifetime = 10 | |
82e7681b | 1801 | found = None |
1802 | ||
1803 | mxbl = set(self.registryValue('mxbl')) | |
aaf3c542 | 1804 | i = self.getIrc(irc) |
2638f7e4 | 1805 | if email.lower() in mxbl: |
d8d19c3a | 1806 | found = email |
82e7681b | 1807 | else: |
41939c90 | 1808 | to_resolve = [(email,'MX'), (email,'A'), (email,'AAAA')] |
82e7681b | 1809 | while to_resolve: |
1810 | domain, type = to_resolve.pop(0) | |
ff95c471 | 1811 | try: |
82e7681b | 1812 | res = resolver.query(domain, type) |
ff95c471 | 1813 | except: |
82e7681b | 1814 | pass |
1815 | else: | |
1816 | for record in res: | |
1817 | record = record.to_text() | |
1818 | ||
1819 | if type == 'MX': | |
2638f7e4 | 1820 | record = record.split(" ", 1)[1].lower() |
41939c90 | 1821 | # MX records (and their A records) are what we match on most, |
1822 | # so doing .insert(0, ...) means they're checked first | |
1823 | to_resolve.insert(0, (record, 'A')) | |
1824 | to_resolve.insert(0, (record, 'AAAA')) | |
82e7681b | 1825 | |
1826 | if record in mxbl: | |
1827 | found = record | |
aaf3c542 | 1828 | break |
6ef31980 | 1829 | if found is not None: |
1830 | break | |
82e7681b | 1831 | |
1832 | if found is not None: | |
aaf3c542 NC |
1833 | i.mx[account] = [email,badmail,found,freeze] |
1834 | if badmail and len(email): | |
1835 | irc.queueMsg(ircmsgs.IrcMsg('PRIVMSG NickServ :BADMAIL ADD *@%s %s' % (email,found))) | |
1836 | if not freeze: | |
1837 | irc.queueMsg(ircmsgs.IrcMsg('PRIVMSG NickServ :FDROP %s' % account)) | |
1838 | else: | |
43c56ec1 | 1839 | irc.queueMsg(ircmsgs.IrcMsg('PRIVMSG NickServ :FREEZE %s ON changed email to (%s which is in mxbl %s)' % (account,email,found))) |
aaf3c542 | 1840 | irc.queueMsg(ircmsgs.IrcMsg('WHOIS %s' % account)) |
d8d19c3a NC |
1841 | else: |
1842 | i.cleandomains[email] = True | |
70bd3e0c NC |
1843 | |
1844 | def handleSnoopMessage (self,irc,msg): | |
1845 | (targets, text) = msg.args | |
1846 | text = text.replace('\x02','') | |
d8d19c3a NC |
1847 | if msg.nick == 'NickServ' and 'REGISTER:' in text: |
1848 | email = text.split('@')[1] | |
1849 | account = text.split(' ')[0] | |
1850 | i = self.getIrc(irc) | |
1851 | if not email in i.cleandomains: | |
1852 | t = world.SupyThread(target=self.resolveSnoopy,name=format('Snoopy %s', email),args=(irc,account,email,True,False)) | |
1853 | t.setDaemon(True) | |
1854 | t.start() | |
43c56ec1 | 1855 | account = account.lower().strip() |
ff95c471 NC |
1856 | q = self.getIrcQueueFor(irc,account,'nsregister',600) |
1857 | q.enqueue(email) | |
645a036d | 1858 | if msg.nick == 'NickServ': |
43c56ec1 | 1859 | src = text.split(' ')[0].lower().strip() |
645a036d | 1860 | target = '' |
b414e9a1 NC |
1861 | registering = True |
1862 | grouping = False | |
645a036d | 1863 | if ' GROUP:' in text: |
b414e9a1 | 1864 | grouping = True |
645a036d | 1865 | target = text.split('(')[1].split(')')[0] |
6a9e943e | 1866 | elif 'SET:ACCOUNTNAME:' in text: |
b414e9a1 | 1867 | grouping = True |
645a036d NC |
1868 | t = text.split('(') |
1869 | if len(t) > 1: | |
1870 | target = text.split('(')[1].split(')')[0] | |
1871 | else: | |
1872 | return | |
6a9e943e | 1873 | elif 'UNGROUP: ' in text: |
b414e9a1 | 1874 | grouping = True |
645a036d | 1875 | target = text.split('UNGROUP: ')[1] |
b414e9a1 | 1876 | if len(target) and grouping: |
b21941a8 | 1877 | q = self.getIrcQueueFor(irc,src,'nsAccountGroup',120) |
645a036d NC |
1878 | q.enqueue(text) |
1879 | if len(q) == 3: | |
1880 | index = 0 | |
1881 | a = b = c = False | |
ff95c471 | 1882 | oldAccount = None |
645a036d | 1883 | for m in q: |
b21941a8 | 1884 | if ' GROUP:' in m and index == 0: |
645a036d | 1885 | a = True |
b21941a8 | 1886 | elif ' SET:ACCOUNTNAME:' in m and index == 1: |
ff95c471 | 1887 | oldAccount = m.split(' ')[1].replace('(','').replace('(','') |
645a036d | 1888 | b = True |
b21941a8 | 1889 | elif ' UNGROUP:' in m and index == 2: |
645a036d | 1890 | c = True |
b21941a8 | 1891 | index = index + 1 |
645a036d | 1892 | q.reset() |
43c56ec1 | 1893 | if a and b and c: |
b414e9a1 | 1894 | self.logChannel(irc,"SERVICE: %s suspicious evades/abuses with GROUP/ACCOUNTNAME/UNGROUP (was %s)" % (src,oldAccount)) |
ff95c471 | 1895 | i = self.getIrc(irc) |
43c56ec1 | 1896 | oldAccount = oldAccount.lower().strip() |
ff95c471 NC |
1897 | for u in i.klinednicks: |
1898 | if u == oldAccount: | |
43c56ec1 | 1899 | self.logChannel(irc,"SERVICE: %s lethaled (%s), enforcing" % (src,oldAccount)) |
99ebdad5 | 1900 | i.klinednicks.enqueue(src) |
ff95c471 NC |
1901 | if not src in i.tokline: |
1902 | i.toklineresults[src] = {} | |
1903 | i.toklineresults[src]['kind'] = 'evade' | |
1904 | i.tokline[src] = src | |
18f102f7 NC |
1905 | def f (): |
1906 | irc.sendMsg(ircmsgs.IrcMsg('WHOIS %s %s' % (src,src))) | |
1907 | schedule.addEvent(f,time.time()+random.randint(0,7)) | |
99ebdad5 | 1908 | break |
92740f96 NC |
1909 | def do211 (self,irc,msg): |
1910 | i = self.getIrc(irc) | |
1911 | if msg.args[1].startswith('[unknown@'): | |
1912 | if msg.args[1] in i.stats: | |
1913 | i.stats[msg.args[1]] = i.stats[msg.args[1]] + 1 | |
1914 | else: | |
1915 | i.stats[msg.args[1]] = 0 | |
a2395d14 | 1916 | |
2f48e1dd NC |
1917 | def do728 (self,irc,msg): |
1918 | i = self.getIrc(irc) | |
1919 | channel = msg.args[1] | |
1920 | value = msg.args[3] | |
1921 | op = msg.args[4] | |
1922 | if self.registryValue('defconMode',channel=channel) and not i.defcon: | |
1923 | if value == '$~a' and op == irc.prefix: | |
c22f5ade NC |
1924 | if channel == self.registryValue('mainChannel'): |
1925 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s -qz $~a' % channel)) | |
1926 | else: | |
1927 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s -qzo $~a %s' % (channel,irc.nick))) | |
1928 | ||
28164a19 | 1929 | def handleMsg (self,irc,msg,isNotice): |
6f7f2f85 NC |
1930 | if not ircutils.isUserHostmask(msg.prefix): |
1931 | return | |
1932 | if msg.prefix == irc.prefix: | |
1933 | return | |
56b48e4e | 1934 | (targets, t) = msg.args |
6f7f2f85 NC |
1935 | if ircmsgs.isAction(msg): |
1936 | text = ircmsgs.unAction(msg) | |
54deeac6 NC |
1937 | else: |
1938 | text = t | |
56b48e4e | 1939 | try: |
54deeac6 | 1940 | raw = ircutils.stripFormatting(text) |
56b48e4e | 1941 | except: |
54deeac6 | 1942 | raw = text |
bd83123d | 1943 | text = raw.lower() |
b78d8847 | 1944 | mask = self.prefixToMask(irc,msg.prefix) |
6f7f2f85 | 1945 | i = self.getIrc(irc) |
28164a19 | 1946 | if not i.ping or time.time() - i.ping > self.registryValue('lagInterval'): |
87804149 NC |
1947 | i.ping = time.time() |
1948 | self.cleanup(irc) | |
28164a19 | 1949 | if self.registryValue('lagPermit') > -1: |
92740f96 | 1950 | i.stats = {} |
8732a8a6 NC |
1951 | if self.registryValue('ghostPermit') > -1: |
1952 | irc.queueMsg(ircmsgs.IrcMsg('STATS L')) | |
28164a19 | 1953 | irc.queueMsg(ircmsgs.IrcMsg('MAP')) |
6f7f2f85 | 1954 | if i.defcon: |
42f3350d | 1955 | if time.time() > i.defcon + self.registryValue('defcon'): |
f7007307 | 1956 | i.lastDefcon = time.time() |
6f7f2f85 NC |
1957 | i.defcon = False |
1958 | self.logChannel(irc,"INFO: triggers restored to normal behaviour") | |
47e9a74a NC |
1959 | for channel in irc.state.channels: |
1960 | if irc.isChannel(channel) and self.registryValue('defconMode',channel=channel): | |
1961 | if 'z' in irc.state.channels[channel].modes and irc.nick in list(irc.state.channels[channel].ops) and not 'm' in irc.state.channels[channel].modes: | |
2f48e1dd | 1962 | irc.queueMsg(ircmsgs.IrcMsg('MODE %s q' % channel)) |
6f7f2f85 NC |
1963 | if i.netsplit: |
1964 | if time.time() > i.netsplit: | |
6f7f2f85 | 1965 | i.netsplit = False |
c57440a8 | 1966 | self.logChannel(irc,"INFO: netsplit mode deactivated") |
6f7f2f85 | 1967 | if mask in i.klines: |
9dae3680 | 1968 | self.log.debug('Ignoring %s (%s) - kline in progress', msg.prefix,mask) |
6f7f2f85 NC |
1969 | return |
1970 | isBanned = False | |
1971 | for channel in targets.split(','): | |
49d5868d | 1972 | if channel.startswith('@'): |
e35a4b0e | 1973 | channel = channel.replace('@','',1) |
49d5868d | 1974 | if channel.startswith('+'): |
e35a4b0e | 1975 | channel = channel.replace('+','',1) |
6f7f2f85 NC |
1976 | if irc.isChannel(channel) and channel in irc.state.channels: |
1977 | if self.registryValue('reportChannel') == channel: | |
1978 | self.handleReportMessage(irc,msg) | |
70bd3e0c NC |
1979 | if self.registryValue('snoopChannel') == channel: |
1980 | self.handleSnoopMessage(irc,msg) | |
e4bec0e3 NC |
1981 | if self.registryValue('secretChannel') == channel: |
1982 | self.handleSecretMessage(irc,msg) | |
6f7f2f85 NC |
1983 | if self.registryValue('ignoreChannel',channel): |
1984 | continue | |
4aae7f45 | 1985 | if ircdb.checkCapability(msg.prefix, 'protected'): |
54deeac6 | 1986 | if msg.nick in list(irc.state.channels[channel].ops) and irc.nick in text: |
e4bec0e3 | 1987 | self.logChannel(irc,'OP: [%s] <%s> %s' % (channel,msg.nick,text)) |
4aae7f45 | 1988 | continue |
6f7f2f85 NC |
1989 | chan = self.getChan(irc,channel) |
1990 | if chan.called: | |
1991 | if time.time() - chan.called > self.registryValue('abuseDuration',channel=channel): | |
1992 | chan.called = False | |
d8d19c3a NC |
1993 | if not i.defcon: |
1994 | self.logChannel(irc,'INFO: [%s] returns to regular state' % channel) | |
4e74ed4c | 1995 | if irc.isChannel(channel) and self.registryValue('defconMode',channel=channel) and not i.defcon: |
70bd3e0c | 1996 | if 'z' in irc.state.channels[channel].modes and irc.nick in list(irc.state.channels[channel].ops) and not 'm' in irc.state.channels[channel].modes: |
e4bec0e3 | 1997 | irc.queueMsg(ircmsgs.IrcMsg('MODE %s q' % channel)) |
6f7f2f85 NC |
1998 | if isBanned: |
1999 | continue | |
2000 | if msg.nick in list(irc.state.channels[channel].ops): | |
e4bec0e3 NC |
2001 | if irc.nick in raw: |
2002 | self.logChannel(irc,'OP: [%s] <%s> %s' % (channel,msg.nick,text)) | |
6f7f2f85 | 2003 | continue |
8a5d024b NC |
2004 | if self.registryValue('ignoreVoicedUser',channel=channel): |
2005 | if msg.nick in list(irc.state.channels[channel].voices): | |
2006 | continue | |
6f7f2f85 NC |
2007 | protected = ircdb.makeChannelCapability(channel, 'protected') |
2008 | if ircdb.checkCapability(msg.prefix, protected): | |
2009 | continue | |
54deeac6 NC |
2010 | if self.registryValue('ignoreRegisteredUser',channel=channel): |
2011 | if msg.nick in chan.nicks and len(chan.nicks[msg.nick]) > 4: | |
2012 | if chan.nicks[msg.nick][4]: | |
2013 | continue | |
06bd28a0 | 2014 | killReason = self.registryValue('killMessage',channel=channel) |
ff95c471 NC |
2015 | if msg.nick in chan.nicks and len(chan.nicks[msg.nick]) > 4: |
2016 | if chan.nicks[msg.nick][3] == "https://webchat.freenode.net": | |
2017 | hh = mask.split('@')[1] | |
2018 | mask = '*@%s' % hh | |
18f102f7 NC |
2019 | flag = ircdb.makeChannelCapability(channel, 'pattern') |
2020 | if ircdb.checkCapability(msg.prefix, flag): | |
2021 | for k in i.patterns: | |
2022 | pattern = i.patterns[k] | |
2023 | if pattern.match(raw): | |
2024 | if pattern.limit == 0: | |
6f7f2f85 | 2025 | isBanned = True |
992db209 | 2026 | uid = random.randint(0,1000000) |
18f102f7 NC |
2027 | reason = '%s - matches #%s in %s' % (uid,pattern.uid,channel) |
2028 | log = 'BAD: [%s] %s (matches #%s - %s)' % (channel,msg.prefix,pattern.uid,uid) | |
06bd28a0 | 2029 | self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),reason,self.registryValue('klineMessage'),log,killReason) |
6f7f2f85 | 2030 | i.count(self.getDb(irc.network),pattern.uid) |
12a3933a | 2031 | chan.klines.enqueue('%s %s' % (msg.nick.lower(),mask)) |
c83d9b65 | 2032 | self.isAbuseOnChannel(irc,channel,'pattern',mask) |
e35a4b0e | 2033 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) |
6f7f2f85 | 2034 | break |
18f102f7 NC |
2035 | else: |
2036 | queue = self.getIrcQueueFor(irc,mask,pattern.uid,pattern.life) | |
2037 | queue.enqueue(text) | |
2038 | if len(queue) > pattern.limit: | |
2039 | isBanned = True | |
2040 | uid = random.randint(0,1000000) | |
2041 | reason = '%s - matches #%s (%s/%ss) in %s' % (uid,pattern.uid,pattern.limit,pattern.life,channel) | |
2042 | log = 'BAD: [%s] %s (matches #%s %s/%ss - %s)' % (channel,msg.prefix,pattern.uid,pattern.limit,pattern.life,uid) | |
2043 | self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),reason,self.registryValue('klineMessage'),log,killReason) | |
2044 | self.rmIrcQueueFor(irc,mask) | |
2045 | i.count(self.getDb(irc.network),pattern.uid) | |
2046 | chan.klines.enqueue('%s %s' % (msg.nick.lower(),mask)) | |
2047 | self.isAbuseOnChannel(irc,channel,'pattern',mask) | |
2048 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) | |
2049 | break | |
2050 | i.count(self.getDb(irc.network),pattern.uid) | |
6f7f2f85 NC |
2051 | if isBanned: |
2052 | continue | |
99ebdad5 NC |
2053 | if i.defcon and self.isChannelUniSpam(irc,msg,channel,mask,text): |
2054 | isBanned = True | |
2055 | uid = random.randint(0,1000000) | |
2056 | reason = '!dnsbl UniSpam' | |
2057 | log = 'BAD: [%s] %s (%s - %s)' % (channel,msg.prefix,reason,uid) | |
2058 | chan.klines.enqueue('%s %s' % (msg.nick.lower(),mask)) | |
2059 | reason = '%s - %s' % (uid,reason) | |
2060 | self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),reason,self.registryValue('klineMessage'),log,killReason) | |
2061 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) | |
2062 | i.defcon = time.time() | |
99ebdad5 NC |
2063 | if isBanned: |
2064 | continue | |
6f7f2f85 NC |
2065 | ignoreDuration = self.registryValue('ignoreDuration',channel=channel) |
2066 | if not msg.nick in chan.nicks: | |
2067 | t = time.time() | |
4165a619 | 2068 | if isCloaked(msg.prefix,self): |
6f7f2f85 NC |
2069 | t = t - ignoreDuration - 1 |
2070 | chan.nicks[msg.nick] = [t,msg.prefix,mask] | |
2071 | isIgnored = False | |
2072 | if ignoreDuration > 0: | |
2073 | ts = chan.nicks[msg.nick][0] | |
2074 | if time.time()-ts > ignoreDuration: | |
b78d8847 | 2075 | isIgnored = True |
6f7f2f85 | 2076 | reason = '' |
e58750d3 | 2077 | publicreason = '' |
730f1fc2 NC |
2078 | if self.registryValue('joinSpamPartPermit',channel=channel) > -1: |
2079 | kind = 'joinSpamPart' | |
2080 | life = self.registryValue('joinSpamPartLife',channel=channel) | |
2081 | key = mask | |
99ebdad5 | 2082 | isNew = False |
730f1fc2 NC |
2083 | if not kind in chan.buffers: |
2084 | chan.buffers[kind] = {} | |
2085 | if not key in chan.buffers[kind]: | |
99ebdad5 | 2086 | isNew = True |
730f1fc2 NC |
2087 | chan.buffers[kind][key] = utils.structures.TimeoutQueue(life) |
2088 | elif chan.buffers[kind][key].timeout != life: | |
2089 | chan.buffers[kind][key].setTimeout(life) | |
2090 | chan.buffers[kind][key].enqueue(key) | |
43c56ec1 NC |
2091 | if not isIgnored and isNew and len(chan.buffers[kind][key]) == 1 and text.startswith('http') and time.time()-chan.nicks[msg.nick][0] < 15 and 'z' in irc.state.channels[channel].modes and channel == '#freenode': |
2092 | publicreason = 'link spam once joined' | |
2093 | reason = 'linkspam' | |
54deeac6 NC |
2094 | badunicode = False |
2095 | flag = ircdb.makeChannelCapability(channel,'badunicode') | |
2096 | if ircdb.checkCapability(msg.prefix,flag): | |
2097 | badunicode = self.isChannelUnicode(irc,msg,channel,mask,text) | |
2098 | if badunicode and self.hasAbuseOnChannel(irc,channel,'badunicode'): | |
2099 | isIgnored = False | |
2100 | if badunicode: | |
2101 | publicreason = 'unreadable unicode glyphes' | |
2102 | reason = badunicode | |
a16411f4 NC |
2103 | hilight = False |
2104 | flag = ircdb.makeChannelCapability(channel, 'hilight') | |
2105 | if ircdb.checkCapability(msg.prefix, flag): | |
2106 | hilight = self.isChannelHilight(irc,msg,channel,mask,text) | |
2107 | if hilight and self.hasAbuseOnChannel(irc,channel,'hilight'): | |
2108 | isIgnored = False | |
2109 | if hilight: | |
e58750d3 | 2110 | publicreason = 'nicks/hilight spam' |
a16411f4 NC |
2111 | reason = hilight |
2112 | if chan.patterns and not len(reason): | |
6f7f2f85 NC |
2113 | for pattern in chan.patterns: |
2114 | if pattern in text: | |
ff95c471 | 2115 | isIgnored = False |
f33d2f5d | 2116 | reason = 'matches tmp pattern in %s' % channel |
e58750d3 | 2117 | publicreason = 'your sentence matches temporary blacklisted words' |
3a41dff7 | 2118 | chan.patterns.enqueue(pattern) |
730f1fc2 | 2119 | self.isAbuseOnChannel(irc,channel,'pattern',mask) |
6f7f2f85 | 2120 | break |
0603d7b7 NC |
2121 | massrepeat = False |
2122 | flag = ircdb.makeChannelCapability(channel, 'massRepeat') | |
96fdc6a9 | 2123 | if ircdb.checkCapability(msg.prefix, flag): |
0603d7b7 NC |
2124 | massrepeat = self.isChannelMassRepeat(irc,msg,channel,mask,text) |
2125 | if massrepeat and self.hasAbuseOnChannel(irc,channel,'massRepeat'): | |
2126 | isIgnored = False | |
2127 | lowmassrepeat = False | |
2128 | flag = ircdb.makeChannelCapability(channel, 'lowMassRepeat') | |
96fdc6a9 | 2129 | if ircdb.checkCapability(msg.prefix, flag): |
0603d7b7 NC |
2130 | lowmassrepeat = self.isChannelLowMassRepeat(irc,msg,channel,mask,text) |
2131 | if lowmassrepeat and self.hasAbuseOnChannel(irc,channel,'lowMassRepeat'): | |
2132 | isIgnored = False | |
2133 | repeat = False | |
2134 | flag = ircdb.makeChannelCapability(channel, 'repeat') | |
96fdc6a9 | 2135 | if ircdb.checkCapability(msg.prefix, flag): |
0603d7b7 NC |
2136 | repeat = self.isChannelRepeat(irc,msg,channel,mask,text) |
2137 | if repeat and self.hasAbuseOnChannel(irc,channel,'repeat'): | |
2138 | isIgnored = False | |
2139 | lowrepeat = False | |
2140 | flag = ircdb.makeChannelCapability(channel, 'lowRepeat') | |
96fdc6a9 | 2141 | if ircdb.checkCapability(msg.prefix, flag): |
0603d7b7 NC |
2142 | lowrepeat = self.isChannelLowRepeat(irc,msg,channel,mask,text) |
2143 | if lowrepeat and self.hasAbuseOnChannel(irc,channel,'lowRepeat'): | |
2144 | isIgnored = False | |
0603d7b7 NC |
2145 | lowhilight = False |
2146 | flag = ircdb.makeChannelCapability(channel, 'lowHilight') | |
96fdc6a9 | 2147 | if ircdb.checkCapability(msg.prefix, flag): |
0603d7b7 NC |
2148 | lowhilight = self.isChannelLowHilight(irc,msg,channel,mask,text) |
2149 | if lowhilight and self.hasAbuseOnChannel(irc,channel,'lowHilight'): | |
2150 | isIgnored = False | |
2151 | flood = False | |
2152 | flag = ircdb.makeChannelCapability(channel, 'flood') | |
96fdc6a9 | 2153 | if ircdb.checkCapability(msg.prefix, flag): |
0603d7b7 NC |
2154 | flood = self.isChannelFlood(irc,msg,channel,mask,text) |
2155 | if flood and self.hasAbuseOnChannel(irc,channel,'flood'): | |
2156 | isIgnored = False | |
2157 | lowflood = False | |
2158 | flag = ircdb.makeChannelCapability(channel, 'lowFlood') | |
96fdc6a9 | 2159 | if ircdb.checkCapability(msg.prefix, flag): |
0603d7b7 NC |
2160 | lowflood = self.isChannelLowFlood(irc,msg,channel,mask,text) |
2161 | if lowflood and self.hasAbuseOnChannel(irc,channel,'lowFlood'): | |
2162 | isIgnored = False | |
6f7f2f85 | 2163 | ctcp = False |
0603d7b7 | 2164 | flag = ircdb.makeChannelCapability(channel, 'ctcp') |
96fdc6a9 | 2165 | if ircdb.checkCapability(msg.prefix, flag): |
c27f7c2a | 2166 | if not ircmsgs.isAction(msg) and ircmsgs.isCtcp(msg): |
0603d7b7 NC |
2167 | ctcp = self.isChannelCtcp(irc,msg,channel,mask,text) |
2168 | if ctcp and self.hasAbuseOnChannel(irc,channel,'ctcp'): | |
2169 | isIgnored = False | |
c27f7c2a NC |
2170 | notice = False |
2171 | flag = ircdb.makeChannelCapability(channel, 'notice') | |
2172 | if ircdb.checkCapability(msg.prefix, flag): | |
2173 | if not ircmsgs.isAction(msg) and isNotice: | |
2174 | notice = self.isChannelNotice(irc,msg,channel,mask,text) | |
2175 | if notice and self.hasAbuseOnChannel(irc,channel,'notice'): | |
2176 | isIgnored = False | |
645a036d NC |
2177 | cap = False |
2178 | flag = ircdb.makeChannelCapability(channel, 'cap') | |
2179 | if ircdb.checkCapability(msg.prefix, flag): | |
99ebdad5 | 2180 | cap = self.isChannelCap(irc,msg,channel,mask,raw) |
645a036d NC |
2181 | if cap and self.hasAbuseOnChannel(irc,channel,'cap'): |
2182 | isIgnored = False | |
6f7f2f85 NC |
2183 | if not reason: |
2184 | if massrepeat: | |
2185 | reason = massrepeat | |
e58750d3 | 2186 | publicreason = 'repetition detected' |
6f7f2f85 NC |
2187 | elif lowmassrepeat: |
2188 | reason = lowmassrepeat | |
e58750d3 | 2189 | publicreason = 'repetition detected' |
6f7f2f85 NC |
2190 | elif repeat: |
2191 | reason = repeat | |
e58750d3 | 2192 | publicreason = 'repetition detected' |
6f7f2f85 NC |
2193 | elif lowrepeat: |
2194 | reason = lowrepeat | |
e58750d3 | 2195 | publicreason = 'repetition detected' |
6f7f2f85 NC |
2196 | elif hilight: |
2197 | reason = hilight | |
e58750d3 | 2198 | publicreason = 'nicks/hilight spam' |
6f7f2f85 NC |
2199 | elif lowhilight: |
2200 | reason = lowhilight | |
e58750d3 | 2201 | publicreason = 'nicks/hilight spam' |
645a036d NC |
2202 | elif cap: |
2203 | reason = cap | |
2204 | publicreason = 'uppercase detected' | |
6f7f2f85 NC |
2205 | elif flood: |
2206 | reason = flood | |
e58750d3 | 2207 | publicreason = 'flood detected' |
6f7f2f85 NC |
2208 | elif lowflood: |
2209 | reason = lowflood | |
e58750d3 | 2210 | publicreason = 'flood detected' |
6f7f2f85 NC |
2211 | elif ctcp: |
2212 | reason = ctcp | |
e58750d3 | 2213 | publicreason = 'channel CTCP' |
c27f7c2a NC |
2214 | elif notice: |
2215 | reason = notice | |
e58750d3 | 2216 | publicreason = 'channel notice' |
6f7f2f85 | 2217 | if reason: |
99ebdad5 | 2218 | if isIgnored: |
18f102f7 NC |
2219 | if self.warnedOnOtherChannel(irc,channel,mask): |
2220 | isIgnored = False | |
2221 | elif self.isBadOnChannel(irc,channel,'bypassIgnore',mask): | |
99ebdad5 | 2222 | isIgnored = False |
3a41dff7 | 2223 | if chan.called: |
0bf86d58 | 2224 | isIgnored = False |
6f7f2f85 NC |
2225 | if isIgnored: |
2226 | bypassIgnore = self.isBadOnChannel(irc,channel,'bypassIgnore',mask) | |
2227 | if bypassIgnore: | |
b78d8847 | 2228 | isBanned = True |
992db209 | 2229 | uid = random.randint(0,1000000) |
b78d8847 | 2230 | reason = '%s %s' % (reason,bypassIgnore) |
f9a54a50 | 2231 | log = 'BAD: [%s] %s (%s - %s)' % (channel,msg.prefix,reason,uid) |
12a3933a | 2232 | chan.klines.enqueue('%s %s' % (msg.nick.lower(),mask)) |
992db209 | 2233 | reason = '%s - %s' % (uid,reason) |
06bd28a0 | 2234 | self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),reason,self.registryValue('klineMessage'),log,killReason) |
e35a4b0e | 2235 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) |
03ed488b NC |
2236 | if i.defcon: |
2237 | i.defcon = time.time() | |
6f7f2f85 | 2238 | else: |
c22f5ade | 2239 | q = self.getIrcQueueFor(irc,mask,'warned-%s' % channel,self.registryValue('alertPeriod')) |
03ed488b NC |
2240 | if len(q) == 0: |
2241 | q.enqueue(text) | |
2242 | self.logChannel(irc,'IGNORED: [%s] %s (%s)' % (channel,msg.prefix,reason)) | |
e58750d3 | 2243 | matter = None |
cb069545 | 2244 | if msg.nick: |
86210a76 | 2245 | irc.queueMsg(ircmsgs.notice(msg.nick,"Your actions in %s tripped automated anti-spam measures (%s), but were ignored based on your time in channel. Stop now, or automated action will still be taken. If you have any questions, please don't hesitate to contact a member of staff" % (channel,publicreason))) |
6f7f2f85 NC |
2246 | else: |
2247 | isBanned = True | |
992db209 NC |
2248 | uid = random.randint(0,1000000) |
2249 | log = 'BAD: [%s] %s (%s - %s)' % (channel,msg.prefix,reason,uid) | |
12a3933a | 2250 | chan.klines.enqueue('%s %s' % (msg.nick.lower(),mask)) |
992db209 | 2251 | reason = '%s - %s' % (uid,reason) |
06bd28a0 | 2252 | self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),reason,self.registryValue('klineMessage'),log,killReason) |
03ed488b NC |
2253 | if i.defcon: |
2254 | i.defcon = time.time() | |
55afd715 NC |
2255 | if chan.called: |
2256 | chan.called = time.time() | |
2257 | if i.lastDefcon and time.time()-i.lastDefcon < self.registryValue('alertPeriod') and not i.defcon: | |
2258 | self.logChannel(irc,"INFO: ignores lifted and abuses end to klines for %ss due to abuses in %s after lastest defcon %s" % (self.registryValue('defcon')*2,channel,i.lastDefcon)) | |
645a036d | 2259 | i.defcon = time.time() + (self.registryValue('defcon')*2) |
13f59897 | 2260 | if not i.god: |
d4b544e7 | 2261 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick)) |
13f59897 | 2262 | else: |
d4b544e7 | 2263 | self.applyDefcon(irc) |
12259aae | 2264 | ip = mask.split('@')[1] |
d8d19c3a NC |
2265 | if hilight and i.defcon: |
2266 | if utils.net.bruteIsIPV6(ip) or utils.net.isIPV4(ip): | |
2267 | if len(self.registryValue('droneblKey')) and len(self.registryValue('droneblHost')) and self.registryValue('enable'): | |
2268 | t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'),reason)) | |
2269 | t.setDaemon(True) | |
2270 | t.start() | |
e35a4b0e | 2271 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) |
12259aae | 2272 | |
6f7f2f85 | 2273 | if not isBanned: |
e6957b3f NC |
2274 | mini = self.registryValue('amsgMinimum') |
2275 | if len(text) > mini or text.find('http') != -1: | |
6f7f2f85 NC |
2276 | limit = self.registryValue('amsgPermit') |
2277 | if limit > -1: | |
2278 | life = self.registryValue('amsgLife') | |
2279 | percent = self.registryValue('amsgPercent') | |
2280 | queue = self.getIrcQueueFor(irc,mask,channel,life) | |
2281 | queue.enqueue(text) | |
2282 | found = None | |
2283 | for ch in i.channels: | |
2284 | chc = self.getChan(irc,ch) | |
2285 | if msg.nick in chc.nicks and ch != channel: | |
2286 | queue = self.getIrcQueueFor(irc,mask,ch,life) | |
2287 | for m in queue: | |
2288 | if compareString(m,text) > percent: | |
2289 | found = ch | |
2290 | break | |
2291 | if found: | |
2292 | break | |
2293 | if found: | |
2294 | queue = self.getIrcQueueFor(irc,mask,'amsg',life) | |
2295 | flag = False | |
2296 | for q in queue: | |
2297 | if found in q: | |
2298 | flag = True | |
2299 | break | |
2300 | if not flag: | |
2301 | queue.enqueue(found) | |
2302 | if len(queue) > limit: | |
2303 | chs = list(queue) | |
2304 | queue.reset() | |
2305 | key = 'amsg %s' % mask | |
13f59897 NC |
2306 | q = self.getIrcQueueFor(irc,key,'amsg',self.registryValue('alertPeriod')) |
2307 | if len(q) == 0: | |
2308 | q.enqueue(mask) | |
6f7f2f85 NC |
2309 | chs.append(channel) |
2310 | self.logChannel(irc,'AMSG: %s (%s) in %s' % (msg.nick,text,', '.join(chs))) | |
2ecf26e6 NC |
2311 | for channel in i.channels: |
2312 | chan = self.getChan(irc,channel) | |
2313 | life = self.registryValue('computedPatternLife',channel=channel) | |
2314 | if not chan.patterns: | |
2315 | chan.patterns = utils.structures.TimeoutQueue(life) | |
2316 | elif chan.patterns.timeout != life: | |
2317 | chan.patterns.setTimeout(life) | |
2318 | chan.patterns.enqueue(text.lower()) | |
e4bec0e3 NC |
2319 | |
2320 | def handleSecretMessage (self,irc,msg): | |
2321 | (targets, text) = msg.args | |
6a9e943e | 2322 | nicks = ['OperServ','NickServ'] |
5886af90 | 2323 | i = self.getIrc(irc) |
e4bec0e3 NC |
2324 | if msg.nick in nicks: |
2325 | if text.startswith('klinechan_check_join(): klining '): | |
a2395d14 | 2326 | patterns = self.registryValue('droneblPatterns') |
e4bec0e3 | 2327 | found = False |
a2395d14 AE |
2328 | if len(patterns): |
2329 | for pattern in patterns: | |
2330 | if len(pattern) and pattern in text: | |
7f2e65ae | 2331 | found = pattern |
e4bec0e3 | 2332 | break |
a2395d14 AE |
2333 | if found: |
2334 | a = text.split('klinechan_check_join(): klining ')[1].split(' ') | |
2335 | a = a[0] | |
e4bec0e3 | 2336 | ip = a.split('@')[1] |
d8d19c3a | 2337 | if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip): |
a2395d14 | 2338 | if len(self.registryValue('droneblKey')) and len(self.registryValue('droneblHost')) and self.registryValue('enable'): |
7f2e65ae | 2339 | t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'),found)) |
e4bec0e3 | 2340 | t.setDaemon(True) |
a2395d14 | 2341 | t.start() |
e4bec0e3 NC |
2342 | else: |
2343 | self.prefixToMask(irc,'*!*@%s' % ip,'',True) | |
03ed488b NC |
2344 | if text.startswith('sendemail():') and self.registryValue('registerPermit') > 0: |
2345 | text = text.replace('sendemail():','') | |
2346 | pattern = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' | |
2347 | result = re.search(pattern,text) | |
7f2e65ae | 2348 | email = text.split('<')[1].split('>')[0] |
3774e684 | 2349 | h = text.split('email for ')[1].split(']')[0].strip().replace('[','!') |
03ed488b NC |
2350 | if result: |
2351 | ip = result.group(0) | |
2352 | if ip and 'type register to' in text: | |
2353 | q = self.getIrcQueueFor(irc,ip,'register',self.registryValue('registerLife')) | |
7f2e65ae | 2354 | q.enqueue(email) |
03ed488b NC |
2355 | if len(q) > self.registryValue('registerPermit'): |
2356 | ms = [] | |
2357 | for m in q: | |
756385c4 | 2358 | ms.append(m) |
3774e684 NC |
2359 | if i.defcon: |
2360 | uid = random.randint(0,1000000) | |
afd11ab6 | 2361 | m = self.prefixToMask(irc,h) |
3774e684 NC |
2362 | self.ban(irc,nick,h,m,self.registryValue('klineDuration'),'%s - services load with %s' % (uid,','.join(ms)),self.registryValue('klineMessage'),'BAD: %s (registered load of accounts - %s)' % (h,uid)) |
2363 | else: | |
2364 | self.logChannel(irc,'SERVICE: %s load of accounts %s' % (h,', '.join(ms))) | |
7f2e65ae NC |
2365 | if 'type register to' in text: |
2366 | q = self.getIrcQueueFor(irc,email,'register',self.registryValue('registerLife')) | |
2367 | text = text.replace('email for ','') | |
2368 | text = text.split(' type register')[0] | |
2369 | q.enqueue(text.strip()) | |
2370 | if len(q) > self.registryValue('registerPermit'): | |
2371 | ms = [] | |
2372 | for m in q: | |
2373 | ms.append(q) | |
2374 | self.logChannel(irc,'SERVICE: loads of registration to %s (%s)' % (email,', '.join(ms))) | |
730f1fc2 NC |
2375 | if 'AKICK:ADD:' in text or 'AKICK:DEL:' in text: |
2376 | life = self.registryValue('decloakLife') | |
2377 | limit = self.registryValue('decloakPermit') | |
2378 | if limit > -1: | |
2379 | origin = text.split(' ')[0] | |
2380 | target = text.split(' ').pop() | |
2381 | q = self.getIrcQueueFor(irc,origin,target,life) | |
2382 | q.enqueue(text) | |
2383 | if len(q) > limit: | |
2384 | q.reset() | |
afd11ab6 | 2385 | self.logChannel(irc,'SERVICE: [%s] %s suspicious AKICK behaviour' % (target,origin)) |
6a9e943e NC |
2386 | if 'VERIFY:EMAILCHG:' in text: |
2387 | account = text.split(' VERIFY:EMAILCHG')[0] | |
2388 | email = text.split('(email: ')[1].split(')')[0].split('@')[1] | |
2389 | t = world.SupyThread(target=self.resolveSnoopy,name=format('Snoopy %s', email),args=(irc,account,email,True,True)) | |
2390 | t.setDaemon(True) | |
2391 | t.start() | |
e4bec0e3 | 2392 | |
45ac582a NC |
2393 | def handleReportMessage (self,irc,msg): |
2394 | (targets, text) = msg.args | |
2395 | nicks = self.registryValue('reportNicks') | |
2396 | if msg.nick in nicks: | |
2397 | i = self.getIrc(irc) | |
2398 | if text.startswith('BAD:') and not '(tor' in text and '(' in text: | |
2399 | permit = self.registryValue('reportPermit') | |
2400 | if permit > -1: | |
2401 | life = self.registryValue('reportLife') | |
2402 | queue = self.getIrcQueueFor(irc,'report','bad',life) | |
2403 | target = text.split('(')[0] | |
2404 | if len(text.split(' ')) > 1: | |
2405 | target = text.split(' ')[1] | |
2406 | found = False | |
2407 | for q in queue: | |
2408 | if q == target: | |
2409 | found = True | |
2410 | break | |
2411 | if not found: | |
f7007307 | 2412 | queue.enqueue(target) |
45ac582a NC |
2413 | if len(queue) > permit: |
2414 | queue.reset() | |
2415 | if not i.defcon: | |
a2395d14 | 2416 | self.logChannel(irc,"BOT: Wave in progress (%s/%ss), ignores lifted, triggers thresholds lowered for %ss at least" % (self.registryValue('reportPermit'),self.registryValue('reportLife'),self.registryValue('defcon'))) |
f7007307 | 2417 | i.defcon = time.time() |
47e9a74a NC |
2418 | if not i.god: |
2419 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick)) | |
2420 | else: | |
d4b544e7 | 2421 | self.applyDefcon (irc) |
45ac582a NC |
2422 | i.defcon = time.time() |
2423 | else: | |
2424 | if i.netsplit and text.startswith('Join rate in '): | |
a2395d14 | 2425 | i.netsplit = time.time() + self.registryValue('netsplitDuration') |
1b01fbf5 NC |
2426 | if text.startswith('Client ') and 'suspicious' in text and i.defcon: |
2427 | text = text.replace('Client ','') | |
2428 | hostmask = text.split(' ')[0].replace('(','!').replace(')','') | |
2429 | if ircutils.isUserHostmask(hostmask): | |
2430 | mask = self.prefixToMask(irc,hostmask) | |
2431 | (nick,ident,host) = ircutils.splitHostmask(hostmask) | |
2432 | patterns = self.registryValue('droneblPatterns') | |
2433 | found = False | |
2434 | if len(patterns): | |
2435 | for pattern in patterns: | |
2436 | if len(pattern) and pattern in text: | |
2437 | found = pattern | |
2438 | break | |
2439 | if found: | |
ff95c471 NC |
2440 | def k(): |
2441 | self.kline(irc,hostmask,mask,self.registryValue('klineDuration'),'!dnsbl (%s in suspicious mask)' % found) | |
2442 | schedule.addEvent(k,time.time()+random.uniform(1, 6)) | |
f7007307 NC |
2443 | if text.startswith('Killing client ') and 'due to lethal mask ' in text: |
2444 | patterns = self.registryValue('droneblPatterns') | |
2445 | found = False | |
2446 | if len(patterns): | |
2447 | for pattern in patterns: | |
2448 | if len(pattern) and pattern in text: | |
7f2e65ae | 2449 | found = pattern |
f7007307 NC |
2450 | break |
2451 | if found: | |
2452 | a = text.split('Killing client ')[1] | |
2453 | a = a.split(')')[0] | |
a2395d14 | 2454 | ip = a.split('@')[1] |
d8d19c3a | 2455 | if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip): |
5886af90 NC |
2456 | if len(self.registryValue('droneblKey')) and len(self.registryValue('droneblHost')) and self.registryValue('enable'): |
2457 | t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'),found)) | |
2458 | t.setDaemon(True) | |
2459 | t.start() | |
2460 | else: | |
2461 | self.prefixToMask(irc,'*!*@%s' % ip,'',True,found) | |
45ac582a | 2462 | |
6f7f2f85 NC |
2463 | def doPrivmsg (self,irc,msg): |
2464 | self.handleMsg(irc,msg,False) | |
f7007307 | 2465 | try: |
4e74ed4c NC |
2466 | i = self.getIrc(irc) |
2467 | mask = self.prefixToMask(irc,msg.prefix) | |
2468 | (targets, text) = msg.args | |
2469 | text = text | |
2470 | if ircdb.checkCapability(msg.prefix, 'protected'): | |
2471 | return | |
2472 | for channel in targets.split(','): | |
ff95c471 NC |
2473 | if channel.startswith('@'): |
2474 | channel = channel.replace('@','',1) | |
2475 | if channel.startswith('+'): | |
2476 | channel = channel.replace('+','',1) | |
4e74ed4c NC |
2477 | if not irc.isChannel(channel) and channel == irc.nick: |
2478 | killReason = self.registryValue('killMessage',channel=channel) | |
2479 | for k in i.patterns: | |
2480 | pattern = i.patterns[k] | |
2481 | if pattern.match(text): | |
2482 | if pattern.limit == 0: | |
992db209 NC |
2483 | uid = random.randint(0,1000000) |
2484 | reason = '%s - matches #%s in pm' % (pattern.uid,uid) | |
2485 | log = 'BAD: [%s] %s (matches #%s - %s)' % (channel,msg.prefix,pattern.uid,uid) | |
4e74ed4c NC |
2486 | self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),reason,self.registryValue('klineMessage'),log,killReason) |
2487 | i.count(self.getDb(irc.network),pattern.uid) | |
2488 | break | |
2489 | else: | |
2490 | queue = self.getIrcQueueFor(irc,mask,pattern.uid,pattern.life) | |
2491 | queue.enqueue(text) | |
2492 | if len(queue) > pattern.limit: | |
992db209 NC |
2493 | uid = random.randint(0,1000000) |
2494 | reason = '%s - matches #%s (%s/%ss) in pm' % (pattern.uid,pattern.limit,pattern.life,uid) | |
2495 | log = 'BAD: [%s] %s (matches #%s %s/%ss - %s)' % (channel,msg.prefix,pattern.uid,pattern.limit,pattern.life,uid) | |
4e74ed4c NC |
2496 | self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),reason,self.registryValue('klineMessage'),log,killReason) |
2497 | self.rmIrcQueueFor(irc,mask) | |
2498 | i.count(self.getDb(irc.network),pattern.uid) | |
2499 | break | |
a2395d14 | 2500 | i.count(self.getDb(irc.network),pattern.uid) |
f7007307 NC |
2501 | except: |
2502 | return | |
6f7f2f85 | 2503 | |
70bd3e0c NC |
2504 | def doTopic(self, irc, msg): |
2505 | self.handleMsg(irc,msg,False) | |
2506 | ||
c40936fd | 2507 | def do903 (self,irc,msg): |
b78d8847 | 2508 | irc.queueMsg(ircmsgs.IrcMsg('CAP REQ :extended-join account-notify')) |
c40936fd | 2509 | |
6f7f2f85 NC |
2510 | def handleFloodSnote (self,irc,text): |
2511 | user = text.split('Possible Flooder ')[1] | |
2512 | a = user[::-1] | |
2513 | ar = a.split(']',1) | |
2514 | ar.reverse() | |
b78d8847 | 2515 | ar.pop() |
6f7f2f85 NC |
2516 | user = "%s" % ar[0] |
2517 | user = user.replace('[','!',1) | |
2518 | user = '%s' % user[::-1] | |
2519 | if not ircutils.isUserHostmask(user): | |
2520 | return | |
2521 | target = text.split('target: ')[1] | |
2522 | i = self.getIrc(irc) | |
2523 | if irc.isChannel(target): | |
6f7f2f85 NC |
2524 | limit = self.registryValue('channelFloodPermit') |
2525 | life = self.registryValue('channelFloodLife') | |
ddcdfefc | 2526 | key = 'snoteFloodAlerted' |
6f7f2f85 NC |
2527 | if limit > -1: |
2528 | if not self.registryValue('ignoreChannel',target): | |
2529 | protected = ircdb.makeChannelCapability(target, 'protected') | |
2530 | if not ircdb.checkCapability(user, protected): | |
2531 | queue = self.getIrcQueueFor(irc,target,'snoteFlood',life) | |
2e246c41 NC |
2532 | if i.defcon: |
2533 | if limit > 0: | |
2534 | limit = limit - 1 | |
6f7f2f85 NC |
2535 | stored = False |
2536 | for u in queue: | |
2537 | if u == user: | |
2538 | stored = True | |
2539 | break | |
2540 | if not stored: | |
2541 | queue.enqueue(user) | |
a4f8e4d0 | 2542 | users = list(queue) |
6f7f2f85 | 2543 | if len(queue) > limit: |
876382df | 2544 | self.logChannel(irc,'NOTE: [%s] is flooded by %s' % (target,', '.join(users))) |
6f7f2f85 | 2545 | queue.reset() |
13bac464 NC |
2546 | queue = self.getIrcQueueFor(irc,target,'snoteFloodJoin',life) |
2547 | queue.enqueue(text) | |
d8d19c3a NC |
2548 | if len(queue) > 1 or i.defcon: |
2549 | if self.registryValue('lastActionTaken',channel=target) > 0.0 and not target in irc.state.channels: | |
ff95c471 NC |
2550 | for user in users: |
2551 | if not 'gateway/web/' in user: | |
2552 | mask = self.prefixToMask(irc,user) | |
2553 | uid = random.randint(0,1000000) | |
2554 | self.kline(irc,user,mask,self.registryValue('klineDuration'),'%s - snote flood on %s' % (uid,target)) | |
2555 | self.logChannel(irc,"BAD: %s (snote flood on %s - %s)" % (user,target,uid)) | |
13bac464 NC |
2556 | t = time.time() - (self.registryValue('leaveChannelIfNoActivity',channel=target) * 24 * 3600) + 1800 |
2557 | self.setRegistryValue('lastActionTaken',t,channel=target) | |
2558 | irc.sendMsg(ircmsgs.join(target)) | |
2559 | self.logChannel(irc,"JOIN: [%s] due to flood snote" % target) | |
2560 | try: | |
2561 | network = conf.supybot.networks.get(irc.network) | |
2562 | network.channels().add(target) | |
2563 | except KeyError: | |
2564 | pass | |
2565 | queue.reset() | |
6f7f2f85 | 2566 | else: |
6f7f2f85 NC |
2567 | limit = self.registryValue('userFloodPermit') |
2568 | life = self.registryValue('userFloodLife') | |
2569 | if limit > -1: | |
86210a76 NC |
2570 | if target.startswith('freenode-connect'): |
2571 | return | |
6f7f2f85 NC |
2572 | queue = self.getIrcQueueFor(irc,target,'snoteFlood',life) |
2573 | stored = False | |
2574 | for u in queue: | |
2575 | if u == user: | |
2576 | stored = True | |
2577 | break | |
2578 | if not stored: | |
2579 | queue.enqueue(user) | |
a4f8e4d0 | 2580 | users = list(queue) |
6f7f2f85 | 2581 | if len(queue) > limit: |
6f7f2f85 | 2582 | queue.reset() |
992db209 NC |
2583 | queue = self.getIrcQueueFor(irc,target,'snoteFloodLethal',life) |
2584 | queue.enqueue(','.join(users)) | |
2585 | if i.defcon or len(queue) > 1: | |
876382df NC |
2586 | for m in queue: |
2587 | for q in m.split(','): | |
546267d4 | 2588 | if not (ircdb.checkCapability(q, 'protected') or target == 'freenode-connect'): |
21efdd22 | 2589 | mask = self.prefixToMask(irc,q) |
992db209 NC |
2590 | uid = random.randint(0,1000000) |
2591 | self.kline(irc,q,mask,self.registryValue('klineDuration'),'%s - snote flood on %s' % (uid,target)) | |
2592 | self.logChannel(irc,"BAD: %s (snote flood on %s - %s)" % (q,target,uid)) | |
a4f8e4d0 | 2593 | else: |
876382df | 2594 | self.logChannel(irc,'NOTE: %s is flooded by %s' % (target,', '.join(users))) |
21efdd22 NC |
2595 | if ircdb.checkCapability(user, 'protected'): |
2596 | return | |
09e2c886 NC |
2597 | queue = self.getIrcQueueFor(irc,user,'snoteFlood',life) |
2598 | stored = False | |
2599 | for u in queue: | |
2600 | if u == target: | |
2601 | stored = True | |
2602 | break | |
2603 | if not stored: | |
2604 | queue.enqueue(target) | |
43c56ec1 | 2605 | if len(queue)> limit: |
09e2c886 NC |
2606 | targets = list(queue) |
2607 | queue.reset() | |
992db209 NC |
2608 | queue = self.getIrcQueueFor(irc,user,'snoteFloodLethal',life) |
2609 | queue.enqueue(target) | |
2610 | if i.defcon or len(queue) > 1: | |
1b01fbf5 | 2611 | mask = self.prefixToMask(irc,user) |
992db209 NC |
2612 | uid = random.randint(0,1000000) |
2613 | self.kline(irc,user,mask,self.registryValue('klineDuration'),'%s - snote flood %s' % (uid,','.join(targets))) | |
2614 | self.logChannel(irc,"BAD: %s (snote flood %s - %s)" % (user,','.join(targets),uid)) | |
09e2c886 | 2615 | else: |
09e2c886 | 2616 | self.logChannel(irc,'NOTE: %s is flooding %s' % (user,', '.join(targets))) |
b78d8847 | 2617 | |
6f7f2f85 NC |
2618 | def handleJoinSnote (self,irc,text): |
2619 | limit = self.registryValue('joinRatePermit') | |
2620 | life = self.registryValue('joinRateLife') | |
6f7f2f85 NC |
2621 | target = text.split('trying to join ')[1].split(' is')[0] |
2622 | if self.registryValue('ignoreChannel',target): | |
2623 | return | |
2624 | user = text.split('User ')[1].split(')')[0] | |
2625 | user = user.replace('(','!').replace(')','').replace(' ','') | |
2626 | if not ircutils.isUserHostmask(user): | |
6f7f2f85 | 2627 | return |
d915dd74 | 2628 | mask = self.prefixToMask(irc,user) |
70bd3e0c NC |
2629 | if ircdb.checkCapability(user, 'protected'): |
2630 | return | |
6f7f2f85 NC |
2631 | protected = ircdb.makeChannelCapability(target, 'protected') |
2632 | if ircdb.checkCapability(user, protected): | |
2633 | return | |
68303e96 | 2634 | queue = self.getIrcQueueFor(irc,user,'snoteJoin',life) |
6f7f2f85 NC |
2635 | stored = False |
2636 | for u in queue: | |
2637 | if u == user: | |
2638 | stored = True | |
2639 | break | |
2640 | if not stored: | |
2641 | queue.enqueue(user) | |
2642 | i = self.getIrc(irc) | |
2643 | key = 'snoteJoinAlerted' | |
68303e96 | 2644 | if len(queue) > limit and limit > 0: |
6f7f2f85 NC |
2645 | users = list(queue) |
2646 | queue.reset() | |
876382df NC |
2647 | queue = self.getIrcQueueFor(irc,user,'snoteJoinAlert',self.registryValue('alertPeriod')) |
2648 | if len(queue): | |
2649 | self.logChannel(irc,'NOTE: [%s] join/part by %s' % (target,', '.join(users))) | |
2650 | queue.enqueue(','.join(users)) | |
68303e96 NC |
2651 | life = self.registryValue('crawlLife') |
2652 | limit = self.registryValue('crawlPermit') | |
f7007307 NC |
2653 | if limit < 0: |
2654 | return | |
68303e96 | 2655 | queue = self.getIrcQueueFor(irc,mask,'snoteJoin',life) |
6f7f2f85 NC |
2656 | stored = False |
2657 | for u in queue: | |
2658 | if u == target: | |
2659 | stored = True | |
2660 | break | |
2661 | if not stored: | |
2662 | queue.enqueue(target) | |
70bd3e0c NC |
2663 | if '1wm' in user: |
2664 | limit = 1 | |
f7007307 | 2665 | if len(queue) > limit: |
6f7f2f85 | 2666 | channels = list(queue) |
187984d6 | 2667 | queue.reset() |
876382df NC |
2668 | queue = self.getIrcQueueFor(irc,mask,'snoteJoinLethal',self.registryValue('alertPeriod')) |
2669 | if len(queue) == 0: | |
7f2e65ae | 2670 | self.logChannel(irc,'NOTE: %s is indexing the network (%s)' % (user,', '.join(channels))) |
876382df NC |
2671 | queue.enqueue(mask) |
2672 | else: | |
2673 | self.kline(irc,user,mask,self.registryValue('klineDuration'),'crawling') | |
b78d8847 | 2674 | |
6f7f2f85 NC |
2675 | def handleIdSnote (self,irc,text): |
2676 | target = text.split('failed login attempts to ')[1].split('.')[0].strip() | |
2677 | user = text.split('Last attempt received from ')[1].split(' on')[0].strip() | |
2678 | if not ircutils.isUserHostmask(user): | |
2679 | return | |
870623ef | 2680 | if user.split('!')[0].lower() == target.lower(): |
6f7f2f85 NC |
2681 | return |
2682 | limit = self.registryValue('idPermit') | |
2683 | life = self.registryValue('idLife') | |
2684 | if limit < 0: | |
2685 | return | |
6f7f2f85 NC |
2686 | queue = self.getIrcQueueFor(irc,user,'snoteId',life) |
2687 | queue.enqueue(target) | |
2688 | i = self.getIrc(irc) | |
49d5868d NC |
2689 | targets = [] |
2690 | key = 'snoteIdAlerted' | |
6f7f2f85 NC |
2691 | if len(queue) > limit: |
2692 | targets = list(queue) | |
2693 | queue.reset() | |
49d5868d NC |
2694 | if not key in i.queues[user]: |
2695 | def rcu(): | |
2696 | i = self.getIrc(irc) | |
2697 | if user in i.queues: | |
2698 | if key in i.queues[user]: | |
2699 | del i.queues[user][key] | |
2700 | i.queues[user][key] = time.time() | |
870623ef | 2701 | schedule.addEvent(rcu,time.time()+self.registryValue('abuseLife')) |
49d5868d NC |
2702 | if key in i.queues[user]: |
2703 | if len(queue): | |
2704 | targets = list(queue) | |
2705 | queue.reset() | |
92740f96 NC |
2706 | a = [] |
2707 | for t in targets: | |
2708 | if not t in a: | |
2709 | a.append(t) | |
49d5868d NC |
2710 | mask = self.prefixToMask(irc,user) |
2711 | (nick,ident,host) = ircutils.splitHostmask(user) | |
3e6d4c6d | 2712 | if not mask in i.klines: |
992db209 | 2713 | uid = random.randint(0,1000000) |
18f102f7 | 2714 | privateReason = '%s - ns id flood (%s)' % (uid,', '.join(a)) |
d8d19c3a | 2715 | if i.defcon: |
18f102f7 NC |
2716 | privateReason = '!dnsbl ' + privateReason |
2717 | self.kline(irc,user,mask,self.registryValue('klineDuration'), privateReason) | |
2718 | self.logChannel(irc,"BAD: %s (%s)" % (user,privateReason)) | |
6f7f2f85 NC |
2719 | queue = self.getIrcQueueFor(irc,target,'snoteId',life) |
2720 | queue.enqueue(user) | |
49d5868d | 2721 | targets = [] |
6f7f2f85 | 2722 | if len(queue) > limit: |
49d5868d | 2723 | targets = list(queue) |
6f7f2f85 | 2724 | queue.reset() |
49d5868d NC |
2725 | def rct(): |
2726 | i = self.getIrc(irc) | |
2727 | if target in i.queues: | |
2728 | if key in i.queues[target]: | |
2729 | del i.queues[target][key] | |
2730 | i.queues[target][key] = time.time() | |
870623ef | 2731 | schedule.addEvent(rct,time.time()+self.registryValue('abuseLife')) |
49d5868d NC |
2732 | if key in i.queues[target]: |
2733 | if len(queue): | |
2734 | targets = list(queue) | |
a2395d14 | 2735 | queue.reset() |
870623ef | 2736 | a = {} |
49d5868d | 2737 | for t in targets: |
92740f96 | 2738 | if not t in a: |
870623ef | 2739 | a[t] = t |
92740f96 | 2740 | for u in a: |
49d5868d NC |
2741 | mask = self.prefixToMask(irc,u) |
2742 | (nick,ident,host) = ircutils.splitHostmask(u) | |
3e6d4c6d | 2743 | if not mask in i.klines: |
54deeac6 | 2744 | self.kill(irc,nick,self.registryValue('killMessage')) |
992db209 | 2745 | uid = random.randint(0,1000000) |
18f102f7 | 2746 | privateReason = '%s - ns id flood on %s' % (uid,target) |
d8d19c3a | 2747 | if i.defcon: |
1aac7048 | 2748 | privateReason = '!dnsbl ' + privateReason |
18f102f7 NC |
2749 | self.kline(irc,u,mask,self.registryValue('klineDuration'), privateReason) |
2750 | self.logChannel(irc,"BAD: %s (%s)" % (u,privateReason)) | |
b78d8847 | 2751 | |
6f7f2f85 | 2752 | def handleKline(self,irc,text): |
a228e729 NC |
2753 | i = self.getIrc(irc) |
2754 | user = text.split('active for')[1] | |
2755 | a = user[::-1] | |
2756 | ar = a.split(']',1) | |
2757 | ar.reverse() | |
2758 | ar.pop() | |
2759 | user = "%s" % ar[0] | |
2760 | user = user.replace('[','!',1) | |
2761 | user = '%s' % user[::-1] | |
2762 | user = user.strip() | |
2763 | if not ircutils.isUserHostmask(user): | |
2764 | return | |
2765 | (nick,ident,host) = ircutils.splitHostmask(user) | |
a2395d14 | 2766 | permit = self.registryValue('alertOnWideKline') |
ff95c471 | 2767 | found = '' |
43c56ec1 | 2768 | if not i.lastKlineOper.find('freenode/staff/') == -1: |
ff95c471 NC |
2769 | for channel in i.channels: |
2770 | chan = i.channels[channel] | |
2771 | ns = [] | |
2772 | if nick in chan.nicks: | |
2773 | if len(chan.nicks[nick]) == 5: | |
43c56ec1 | 2774 | if chan.nicks[nick][4] and chan.nicks[nick][1] == user: |
ff95c471 NC |
2775 | found = chan.nicks[nick][4] |
2776 | break | |
2777 | if found: | |
43c56ec1 | 2778 | self.log.info ('Account klined %s --> %s' % (found,user)) |
a228e729 | 2779 | if permit > -1: |
f7007307 | 2780 | if '/' in host: |
645a036d | 2781 | if host.startswith('gateway/') or host.startswith('nat/'): |
f7007307 NC |
2782 | h = host.split('/') |
2783 | h[-1] = '*' | |
2784 | host = '/'.join(h) | |
a228e729 | 2785 | ranges = self._ip_ranges(host) |
55afd715 | 2786 | announced = False |
a228e729 | 2787 | for range in ranges: |
54deeac6 | 2788 | range = range |
a228e729 NC |
2789 | queue = self.getIrcQueueFor(irc,range,'klineNote',7) |
2790 | queue.enqueue(user) | |
d4b544e7 | 2791 | if len(queue) == permit: |
55afd715 NC |
2792 | if not announced: |
2793 | announced = True | |
2794 | self.logChannel(irc,"NOTE: a kline similar to *@%s seems to hit more than %s users" % (range,self.registryValue('alertOnWideKline'))) | |
b78d8847 | 2795 | |
187984d6 NC |
2796 | def handleNickSnote (self,irc,text): |
2797 | text = text.replace('Nick change: From ','') | |
2798 | text = text.split(' to ')[1] | |
2799 | nick = text.split(' ')[0] | |
2800 | host = text.split(' ')[1] | |
2801 | host = host.replace('[','',1) | |
2802 | host = host[:-1] | |
b888f64b NC |
2803 | limit = self.registryValue('nickChangePermit') |
2804 | life = self.registryValue('nickChangeLife') | |
187984d6 NC |
2805 | if limit < 0: |
2806 | return | |
2807 | mask = self.prefixToMask(irc,'%s!%s' % (nick,host)) | |
187984d6 NC |
2808 | i = self.getIrc(irc) |
2809 | if not i.defcon: | |
2810 | return | |
2811 | queue = self.getIrcQueueFor(irc,mask,'snoteNick',life) | |
2812 | queue.enqueue(nick) | |
2813 | if len(queue) > limit: | |
2814 | nicks = list(queue) | |
2815 | queue.reset() | |
992db209 NC |
2816 | uid = random.randint(0,1000000) |
2817 | self.kline(irc,'%s!%s' % (nick,host),mask,self.registryValue('klineDuration'),'%s - nick changes abuses %s/%ss' % (uid,limit,life)) | |
2818 | self.logChannel(irc,"BAD: %s abuses nick change (%s - %s)" % (mask,','.join(nicks),uid)) | |
a2395d14 | 2819 | |
09e2c886 | 2820 | def handleChannelCreation (self,irc,text): |
03ed488b | 2821 | text = text.replace(' is creating new channel ','') |
cd27d8a5 | 2822 | permit = self.registryValue('channelCreationPermit') |
03ed488b | 2823 | user = text.split('#')[0] |
4165a619 | 2824 | channel = '#' + text.split('#')[1] |
9aca52c8 | 2825 | if '##' in text: |
4165a619 | 2826 | channel = '##' + text.split('##')[1] |
03ed488b | 2827 | i = self.getIrc(irc) |
54deeac6 NC |
2828 | if len(self.registryValue('lethalChannels')) > 0: |
2829 | for pattern in self.registryValue('lethalChannels'): | |
2830 | if len(pattern) and pattern in channel and not user in channel and not user in i.tokline: | |
86a26f0a | 2831 | i.toklineresults[user] = {} |
54deeac6 | 2832 | i.toklineresults[user]['kind'] = 'lethal' |
1c77429f | 2833 | i.tokline[user] = text |
54deeac6 | 2834 | self.log.info('WHOIS %s (%s)' % (user,channel)) |
1c77429f | 2835 | irc.sendMsg(ircmsgs.IrcMsg('WHOIS %s %s' % (user,user))) |
54deeac6 | 2836 | break |
03ed488b | 2837 | |
910cbf0c | 2838 | def handleClient (self,irc,text): |
69ae2c3c NC |
2839 | i = self.getIrc(irc) |
2840 | ||
910cbf0c | 2841 | #if i.defcon: |
43c56ec1 | 2842 | |
910cbf0c | 2843 | |
6f7f2f85 NC |
2844 | def doNotice (self,irc,msg): |
2845 | (targets, text) = msg.args | |
2846 | if len(targets) and targets[0] == '*': | |
2847 | # server notices | |
2848 | text = text.replace('\x02','') | |
2849 | if text.startswith('*** Notice -- '): | |
2850 | text = text.replace('*** Notice -- ','') | |
ff95c471 NC |
2851 | if text.startswith('Client connecting'): |
2852 | if 'gateway/vpn/privateinternetaccess' in text: | |
2853 | account = text.split('(')[1].split(')')[0] | |
2854 | account = account.split('@gateway/vpn/privateinternetaccess/')[1].split('/')[0] | |
2855 | #self.log.info('connecting %s' % account) | |
2856 | q = self.getIrcQueueFor(irc,account,'nsregister',600) | |
2857 | if len(q) == 1: | |
2858 | self.logChannel(irc,"SERVICE: fresh account %s moved to pia" % account) | |
6f7f2f85 NC |
2859 | if text.startswith('Possible Flooder '): |
2860 | self.handleFloodSnote(irc,text) | |
99ebdad5 NC |
2861 | #elif text.find('is creating new channel') != -1: |
2862 | # self.handleChannelCreation(irc,text) | |
187984d6 NC |
2863 | elif text.startswith('Nick change: From'): |
2864 | self.handleNickSnote(irc,text) | |
6f7f2f85 NC |
2865 | elif text.startswith('User') and text.endswith('is a possible spambot'): |
2866 | self.handleJoinSnote(irc,text) | |
5ecb3350 | 2867 | elif 'failed login attempts to' in text and not 'SASL' in text: |
6f7f2f85 NC |
2868 | self.handleIdSnote(irc,text) |
2869 | elif text.startswith('Too many clients, rejecting ') or text.startswith('All connections in use.') or text.startswith('creating SSL/TLS socket pairs: 24 (Too many open files)'): | |
2870 | i = self.getIrc(irc) | |
2871 | if not msg.prefix in i.limits or time.time() - i.limits[msg.prefix] > self.registryValue('alertPeriod'): | |
2872 | i.limits[msg.prefix] = time.time() | |
2873 | self.logChannel(irc,'INFRA: %s is rejecting clients' % msg.prefix.split('.')[0]) | |
4d7410f3 NC |
2874 | if not i.netsplit: |
2875 | self.logChannel(irc,'INFO: netsplit activated for %ss : some abuses are ignored' % self.registryValue('netsplitDuration')) | |
2876 | i.netsplit = time.time() + self.registryValue('netsplitDuration') | |
6f7f2f85 NC |
2877 | elif text.startswith('KLINE active') or text.startswith('K/DLINE active'): |
2878 | self.handleKline(irc,text) | |
f7007307 NC |
2879 | elif text.find('due to too high load') != -1: |
2880 | i = self.getIrc(irc) | |
2881 | if not 'services.' in i.limits: | |
2882 | i.limits['services.'] = time.time() | |
2883 | reason = text.split("type '")[1] | |
2884 | reason = reason.split(' ')[0] | |
2885 | self.logChannel(irc,"INFRA: High load on services ('%s)" % reason) | |
2886 | def rct(): | |
2887 | i = self.getIrc(irc) | |
2888 | if 'services.' in i.limits: | |
2889 | del i.limits['services.'] | |
2890 | schedule.addEvent(rct,time.time()+self.registryValue('alertPeriod')) | |
4e74ed4c | 2891 | elif 'K-Line for [*@' in text: |
ff95c471 NC |
2892 | oper = text.split(' ')[0] |
2893 | i = self.getIrc(irc) | |
2894 | i.lastKlineOper = oper | |
4e74ed4c NC |
2895 | reason = text.split('K-Line for [*@')[1] |
2896 | reason = reason.split(']')[1].replace('[','').replace(']','') | |
5b4139a0 NC |
2897 | hasPattern = False |
2898 | for p in self.registryValue('droneblPatterns'): | |
2899 | if p in reason: | |
7f2e65ae | 2900 | hasPattern = p |
5b4139a0 | 2901 | break |
a228e729 NC |
2902 | ip = text.split('K-Line for [*@')[1].split(']')[0] |
2903 | permit = self.registryValue('ipv4AbusePermit') | |
99ebdad5 | 2904 | if not 'evilmquin' in oper and permit > -1: |
a228e729 | 2905 | ranges = self._ip_ranges(ip) |
a228e729 | 2906 | for range in ranges: |
54deeac6 | 2907 | range = range |
a228e729 NC |
2908 | q = self.getIrcQueueFor(irc,'klineRange',range,self.registryValue('ipv4AbuseLife')) |
2909 | q.enqueue(ip) | |
2910 | if len(q) > permit: | |
2911 | hs = [] | |
2912 | for m in q: | |
2913 | hs.append(m) | |
876382df | 2914 | q.reset() |
f35897a2 NC |
2915 | uid = random.randint(0,1000000) |
2916 | if self.registryValue('useOperServ'): | |
7769a3cb | 2917 | irc.sendMsg(ircmsgs.IrcMsg('PRIVMSG OperServ :AKILL ADD %s !T %s %s' % (range,self.registryValue('klineDuration'),'%s - repeat abuses on this range (%s/%ss)' % (uid,permit,self.registryValue('ipv4AbuseLife'))))) |
f35897a2 NC |
2918 | else: |
2919 | irc.sendMsg(ircmsgs.IrcMsg('KLINE %s *@%s :%s|%s' % (self.registryValue('klineDuration'),range,self.registryValue('klineMessage'),'%s - repeat abuses on this range (%s/%ss)' % (uid,permit,self.registryValue('ipv4AbuseLife'))))) | |
2920 | self.logChannel(irc,"BAD: abuses detected on %s (%s/%ss - %s) %s" % (range,permit,self.registryValue('ipv4AbuseLife'),uid,','.join(hs))) | |
a228e729 | 2921 | permit = permit + 1 |
5b4139a0 | 2922 | if '!dnsbl' in text or hasPattern: |
7f2e65ae NC |
2923 | reason = '' |
2924 | if '!dnsbl' in text: | |
2925 | reason = text.split('!dnsbl')[1].replace(']','').strip() | |
2926 | else: | |
2927 | reason = hasPattern | |
d8d19c3a | 2928 | if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip): |
4e74ed4c | 2929 | if len(self.registryValue('droneblKey')) and len(self.registryValue('droneblHost')) and self.registryValue('enable'): |
7f2e65ae | 2930 | t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'),reason)) |
4e74ed4c NC |
2931 | t.setDaemon(True) |
2932 | t.start() | |
2933 | else: | |
99ebdad5 | 2934 | if len(self.registryValue('droneblKey')) and len(self.registryValue('droneblHost')) and self.registryValue('enable'): |
8d531ba7 | 2935 | t = world.SupyThread(target=self.resolve,name=format('resolve %s', '*!*@%s' % ip),args=(irc,'*!*@%s' % ip,'',True, reason)) |
99ebdad5 NC |
2936 | t.setDaemon(True) |
2937 | t.start() | |
2938 | else: | |
2939 | self.prefixToMask(irc,'*!*@%s' % ip,'',True,reason) | |
5ecb3350 NC |
2940 | elif 'failed login attempts to' in text and 'SASL' in text: |
2941 | self.handleSaslFailure(irc,text) | |
d8d19c3a | 2942 | elif text.startswith('FILTER'): |
b21941a8 | 2943 | ip = text.split(' ')[2].split('[')[1].split(']')[0] |
d8d19c3a NC |
2944 | if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip): |
2945 | if not ip in self.ipfiltered: | |
b21941a8 | 2946 | if self.registryValue('serverFilteringPermit') > -1: |
b21941a8 NC |
2947 | q = self.getIrcQueueFor(irc,'serverSideFiltering',ip,self.registryValue('serverFilteringLife')) |
2948 | q.enqueue(ip) | |
2949 | reason = 'Server Side Filtering' | |
2950 | if len(q) > self.registryValue('serverFilteringPermit'): | |
2951 | self.ipfiltered[ip] = True | |
2952 | if len(self.registryValue('droneblKey')) and len(self.registryValue('droneblHost')) and self.registryValue('enable'): | |
2953 | t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'),reason)) | |
2954 | t.setDaemon(True) | |
43c56ec1 | 2955 | t.start() |
6f7f2f85 NC |
2956 | else: |
2957 | self.handleMsg(irc,msg,True) | |
a2395d14 | 2958 | |
01ad4042 NC |
2959 | def do215 (self,irc,msg): |
2960 | i = self.getIrc(irc) | |
ff95c471 NC |
2961 | if msg.args[0] == irc.nick and msg.args[1] == 'I': |
2962 | i.lines[msg.args[4]] = '%s %s %s' % (msg.args[2],msg.args[3],msg.args[5]) | |
2963 | # if len(i.dlines): | |
2964 | # h = i.dlines.pop(0) | |
2965 | # self.log.info('DLINE %s|%s' % (h,self.registryValue('saslDuration'))) | |
2966 | # irc.sendMsg(ircmsgs.IrcMsg('DLINE %s %s on * :%s' % (self.registryValue('saslDuration'),h,self.registryValue('saslMessage')))) | |
2967 | # if len(i.dlines): | |
2968 | # irc.queueMsg(ircmsgs.IrcMsg('TESTLINE %s' % i.dlines[0])) | |
6f7f2f85 | 2969 | |
5ecb3350 NC |
2970 | def handleSaslFailure (self,irc,text): |
2971 | i = self.getIrc(irc) | |
2972 | limit = self.registryValue('saslPermit') | |
2973 | if limit < 0: | |
2974 | return | |
2975 | life = self.registryValue('saslLife') | |
2976 | account = text.split('failed login attempts to ')[1].split('.')[0] | |
2977 | host = text.split('<Unknown user (via SASL):')[1].split('>')[0] | |
a2395d14 | 2978 | q = self.getIrcQueueFor(irc,'sasl',account,life) |
5ecb3350 NC |
2979 | q.enqueue(host) |
2980 | hosts = {} | |
2981 | if len(q) > limit: | |
2982 | for ip in q: | |
2983 | hosts[ip] = ip | |
2984 | q.reset() | |
2985 | q = self.getIrcQueueFor(irc,'sasl',host,life) | |
2986 | q.enqueue(account) | |
2987 | if len(q) > limit: | |
2988 | q.reset() | |
2989 | hosts[host] = host | |
2990 | if self.registryValue('enable'): | |
5ecb3350 NC |
2991 | if len(hosts) > 0: |
2992 | for h in hosts: | |
c56a3fc6 NC |
2993 | if len(i.dlines): |
2994 | i.dlines.append(h) | |
2995 | else: | |
2996 | i.dlines.append(h) | |
ff95c471 NC |
2997 | found = None |
2998 | users = None | |
2999 | i = self.getIrc(irc) | |
3000 | for server in i.servers: | |
3001 | if not users or users < i.servers[server]: | |
3002 | found = server | |
3003 | users = i.servers[server] | |
3004 | if found: | |
3005 | irc.queueMsg(ircmsgs.IrcMsg('stats I %s' % found)) | |
3006 | self.logChannel(irc,'NOTE: %s (%s) (%s/%ss)' % (h,'SASL failures',limit,life)) | |
5ecb3350 | 3007 | |
18f102f7 | 3008 | def warnedOnOtherChannel (self,irc,channel,mask): |
18f102f7 NC |
3009 | for chan in list(irc.state.channels): |
3010 | if chan != channel: | |
4ea839a3 NC |
3011 | if self.hasAbuseOnChannel(irc,chan,mask): |
3012 | return True | |
3013 | return False | |
18f102f7 | 3014 | |
6f7f2f85 NC |
3015 | def hasAbuseOnChannel (self,irc,channel,key): |
3016 | chan = self.getChan(irc,channel) | |
3017 | kind = 'abuse' | |
c22782ea | 3018 | limit = self.registryValue('%sPermit' % kind,channel=channel) |
6f7f2f85 NC |
3019 | if kind in chan.buffers: |
3020 | if key in chan.buffers[kind]: | |
c22782ea | 3021 | if len(chan.buffers[kind][key]) > limit: |
6f7f2f85 NC |
3022 | return True |
3023 | return False | |
b78d8847 | 3024 | |
6f7f2f85 NC |
3025 | def isAbuseOnChannel (self,irc,channel,key,mask): |
3026 | chan = self.getChan(irc,channel) | |
3027 | kind = 'abuse' | |
3028 | limit = self.registryValue('%sPermit' % kind,channel=channel) | |
3029 | if limit < 0: | |
3030 | return False | |
3031 | life = self.registryValue('%sLife' % kind,channel=channel) | |
3032 | if not kind in chan.buffers: | |
3033 | chan.buffers[kind] = {} | |
3034 | if not key in chan.buffers[kind]: | |
3035 | chan.buffers[kind][key] = utils.structures.TimeoutQueue(life) | |
3036 | elif chan.buffers[kind][key].timeout != life: | |
3037 | chan.buffers[kind][key].setTimeout(life) | |
3038 | found = False | |
3039 | for m in chan.buffers[kind][key]: | |
c22782ea | 3040 | if mask == m: |
6f7f2f85 | 3041 | found = True |
c22782ea | 3042 | break |
6f7f2f85 NC |
3043 | if not found: |
3044 | chan.buffers[kind][key].enqueue(mask) | |
f7007307 NC |
3045 | i = self.getIrc(irc) |
3046 | if i.defcon: | |
3047 | limit = limit - 1 | |
3048 | if limit < 0: | |
3049 | limit = 0 | |
6f7f2f85 | 3050 | if len(chan.buffers[kind][key]) > limit: |
c22782ea | 3051 | self.log.debug('abuse in %s : %s : %s/%s' % (channel,key,len(chan.buffers[kind][key]),limit)) |
6f7f2f85 NC |
3052 | # chan.buffers[kind][key].reset() |
3053 | # queue not reseted, that way during life, it returns True | |
45ac582a | 3054 | if not chan.called: |
d8d19c3a NC |
3055 | if not i.defcon: |
3056 | self.logChannel(irc,"INFO: [%s] ignores lifted, limits lowered due to %s abuses for %ss" % (channel,key,self.registryValue('abuseDuration',channel=channel))) | |
d4b544e7 NC |
3057 | if not i.defcon: |
3058 | i.defcon = time.time() | |
70bd3e0c NC |
3059 | if not i.god: |
3060 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick)) | |
3061 | else: | |
d4b544e7 | 3062 | self.applyDefcon(irc) |
6f7f2f85 | 3063 | chan.called = time.time() |
b78d8847 | 3064 | return True |
6f7f2f85 | 3065 | return False |
b78d8847 | 3066 | |
6f7f2f85 NC |
3067 | def isBadOnChannel (self,irc,channel,kind,key): |
3068 | chan = self.getChan(irc,channel) | |
3069 | limit = self.registryValue('%sPermit' % kind,channel=channel) | |
3070 | if limit < 0: | |
3071 | return False | |
3072 | i = self.getIrc(irc) | |
3073 | if i.netsplit: | |
fca1dc9d | 3074 | kinds = ['flood','lowFlood','nick','lowRepeat','lowMassRepeat','broken'] |
6f7f2f85 NC |
3075 | if kind in kinds: |
3076 | return False | |
3077 | life = self.registryValue('%sLife' % kind,channel=channel) | |
3078 | if limit == 0: | |
a8a3a945 | 3079 | return '%s %s/%ss in %s' % (kind,limit,life,channel) |
6f7f2f85 NC |
3080 | if not kind in chan.buffers: |
3081 | chan.buffers[kind] = {} | |
0bf86d58 | 3082 | newUser = False |
6f7f2f85 | 3083 | if not key in chan.buffers[kind]: |
0bf86d58 | 3084 | newUser = True |
6f7f2f85 | 3085 | chan.buffers[kind][key] = utils.structures.TimeoutQueue(life) |
0bf86d58 | 3086 | chan.buffers[kind]['%s-creation' % key] = time.time() |
6f7f2f85 NC |
3087 | elif chan.buffers[kind][key].timeout != life: |
3088 | chan.buffers[kind][key].setTimeout(life) | |
0bf86d58 NC |
3089 | ignore = self.registryValue('ignoreDuration',channel=channel) |
3090 | if ignore > 0: | |
3091 | if time.time() - chan.buffers[kind]['%s-creation' % key] < ignore: | |
3092 | newUser = True | |
6f7f2f85 | 3093 | chan.buffers[kind][key].enqueue(key) |
0bf86d58 | 3094 | if newUser or i.defcon or self.hasAbuseOnChannel(irc,channel,kind) or chan.called: |
6f7f2f85 NC |
3095 | limit = limit - 1 |
3096 | if limit < 0: | |
3097 | limit = 0 | |
3098 | if len(chan.buffers[kind][key]) > limit: | |
3099 | chan.buffers[kind][key].reset() | |
a8a3a945 NC |
3100 | if not kind == 'broken': |
3101 | self.isAbuseOnChannel(irc,channel,kind,key) | |
3102 | return '%s %s/%ss in %s' % (kind,limit,life,channel) | |
6f7f2f85 | 3103 | return False |
b78d8847 | 3104 | |
ff95c471 NC |
3105 | def hasBadOnChannel (self,irc,channel,kind,key): |
3106 | chan = self.getChan(irc,channel) | |
3107 | if not kind in chan.buffers: | |
3108 | return False | |
3109 | if not key in chan.buffers[kind]: | |
3110 | return False; | |
3111 | return len(chan.buffers[kind][key]) > 0 | |
3112 | ||
99ebdad5 NC |
3113 | def isChannelUniSpam (self,irc,msg,channel,mask,text): |
3114 | count = len([char for char in text if char in self.spamchars]) | |
3115 | return len(text) < 32 and count >=3 | |
3116 | ||
6f7f2f85 NC |
3117 | def isChannelCtcp (self,irc,msg,channel,mask,text): |
3118 | return self.isBadOnChannel(irc,channel,'ctcp',mask) | |
3119 | ||
c27f7c2a NC |
3120 | def isChannelNotice (self,irc,msg,channel,mask,text): |
3121 | return self.isBadOnChannel(irc,channel,'notice',mask) | |
3122 | ||
b78d8847 | 3123 | def isChannelLowFlood (self,irc,msg,channel,mask,text): |
6f7f2f85 NC |
3124 | return self.isBadOnChannel(irc,channel,'lowFlood',mask) |
3125 | ||
645a036d | 3126 | def isChannelCap (self,irc,msg,channel,mask,text): |
99ebdad5 | 3127 | text = text.replace(' ','') |
645a036d NC |
3128 | if len(text) == 0 or len(text) > self.registryValue('capMinimum',channel=channel): |
3129 | limit = self.registryValue('capPermit',channel=channel) | |
3130 | if limit < 0: | |
3131 | return False | |
3132 | trigger = self.registryValue('capPercent',channel=channel) | |
3133 | matchs = self.recaps.findall(text) | |
99ebdad5 | 3134 | #self.log.info ('%s : %s : %s :%s' % (mask,channel,text,len(matchs))) |
645a036d | 3135 | if len(matchs) and len(text): |
99ebdad5 NC |
3136 | percent = (len(matchs)*100) / (len(text) * 1.0) |
3137 | #self.log.info ('%s: %s/%s %s' % (mask,percent,trigger,text)) | |
645a036d NC |
3138 | if percent >= trigger: |
3139 | return self.isBadOnChannel(irc,channel,'cap',mask) | |
3140 | return False | |
3141 | ||
6f7f2f85 | 3142 | def isChannelFlood (self,irc,msg,channel,mask,text): |
aaf3c542 | 3143 | if len(text) == 0 or len(text) >= self.registryValue('floodMinimum',channel=channel) or text.isdigit(): |
6f7f2f85 NC |
3144 | return self.isBadOnChannel(irc,channel,'flood',mask) |
3145 | return False | |
3146 | ||
3147 | def isChannelHilight (self,irc,msg,channel,mask,text): | |
ddcdfefc | 3148 | return self.isHilight(irc,msg,channel,mask,text,False) |
b78d8847 | 3149 | |
6f7f2f85 | 3150 | def isChannelLowHilight (self,irc,msg,channel,mask,text): |
ddcdfefc | 3151 | return self.isHilight(irc,msg,channel,mask,text,True) |
b78d8847 | 3152 | |
54deeac6 NC |
3153 | def isChannelUnicode (self,irc,msg,channel,mask,text): |
3154 | limit = self.registryValue('badunicodeLimit',channel=channel) | |
3155 | if limit > 0: | |
3156 | score = sequence_weirdness(u'%s' % text) | |
e6957b3f NC |
3157 | count = self.registryValue('badunicodeScore',channel=channel) |
3158 | if count < score: | |
54deeac6 | 3159 | return self.isBadOnChannel(irc,channel,'badunicode',mask) |
e6957b3f NC |
3160 | return False |
3161 | ||
ddcdfefc NC |
3162 | def isHilight (self,irc,msg,channel,mask,text,low): |
3163 | kind = 'hilight' | |
3164 | if low: | |
3165 | kind = 'lowHilight' | |
3166 | limit = self.registryValue('%sNick' % kind,channel=channel) | |
6f7f2f85 NC |
3167 | if limit < 0: |
3168 | return False | |
3169 | count = 0 | |
3170 | users = [] | |
3171 | if channel in irc.state.channels and irc.isChannel(channel): | |
3172 | for u in list(irc.state.channels[channel].users): | |
3173 | if u == 'ChanServ' or u == msg.nick: | |
3174 | continue | |
3175 | users.append(u.lower()) | |
3176 | flag = False | |
3177 | us = {} | |
3178 | for user in users: | |
70bd3e0c | 3179 | if len(user) > 3: |
6f7f2f85 NC |
3180 | if not user in us and user in text: |
3181 | us[user] = True | |
3182 | count = count + 1 | |
3183 | if count > limit: | |
3184 | flag = True | |
3185 | break | |
3186 | result = False | |
3187 | if flag: | |
ddcdfefc | 3188 | result = self.isBadOnChannel(irc,channel,kind,mask) |
6f7f2f85 NC |
3189 | return result |
3190 | ||
3191 | def isChannelRepeat (self,irc,msg,channel,mask,text): | |
ddcdfefc | 3192 | return self.isRepeat(irc,msg,channel,mask,text,False) |
b78d8847 | 3193 | |
6f7f2f85 | 3194 | def isChannelLowRepeat (self,irc,msg,channel,mask,text): |
ddcdfefc | 3195 | return self.isRepeat(irc,msg,channel,mask,text,True) |
b78d8847 | 3196 | |
ddcdfefc NC |
3197 | def isRepeat(self,irc,msg,channel,mask,text,low): |
3198 | kind = 'repeat' | |
3199 | key = mask | |
3200 | if low: | |
3201 | kind = 'lowRepeat' | |
3202 | key = 'low_repeat %s' % mask | |
3203 | limit = self.registryValue('%sPermit' % kind,channel=channel) | |
6f7f2f85 NC |
3204 | if limit < 0: |
3205 | return False | |
c56a3fc6 NC |
3206 | if len(text) < self.registryValue('%sMinimum' % kind,channel=channel): |
3207 | return False | |
6f7f2f85 | 3208 | chan = self.getChan(irc,channel) |
ddcdfefc NC |
3209 | life = self.registryValue('%sLife' % kind,channel=channel) |
3210 | trigger = self.registryValue('%sPercent' % kind,channel=channel) | |
6f7f2f85 NC |
3211 | if not key in chan.logs: |
3212 | chan.logs[key] = utils.structures.TimeoutQueue(life) | |
3213 | elif chan.logs[key].timeout != life: | |
3214 | chan.logs[key].setTimeout(life) | |
3215 | logs = chan.logs[key] | |
6f7f2f85 NC |
3216 | flag = False |
3217 | result = False | |
3218 | for m in logs: | |
3219 | if compareString(m,text) > trigger: | |
3220 | flag = True | |
3221 | break | |
3222 | if flag: | |
ddcdfefc | 3223 | result = self.isBadOnChannel(irc,channel,kind,mask) |
c22782ea | 3224 | enough = False |
47e9a74a NC |
3225 | i = self.getIrc(irc) |
3226 | if flag and not i.netsplit: | |
49d5868d | 3227 | if kind in chan.buffers and key in chan.buffers[kind]: |
c56a3fc6 | 3228 | # we start to try to create pattern if user hits around 2/3 of his buffer |
70bd3e0c | 3229 | if len(chan.buffers[kind][key])/(limit * 1.0) > 0.55: |
49d5868d | 3230 | enough = True |
e6957b3f | 3231 | if result or enough: |
6f7f2f85 NC |
3232 | life = self.registryValue('computedPatternLife',channel=channel) |
3233 | if not chan.patterns: | |
3234 | chan.patterns = utils.structures.TimeoutQueue(life) | |
3235 | elif chan.patterns.timeout != life: | |
3236 | chan.patterns.setTimeout(life) | |
8895121b | 3237 | if self.registryValue('computedPattern',channel=channel) > -1 and len(text) > self.registryValue('computedPattern',channel=channel): |
c56a3fc6 NC |
3238 | repeats = [] |
3239 | if low: | |
3240 | pat = '' | |
3241 | for m in logs: | |
3242 | if compareString(m,text) > trigger: | |
3243 | p = largestString(m,text) | |
3244 | if len(p) > self.registryValue('computedPattern',channel=channel): | |
3245 | if len(p) > len(pat): | |
3246 | pat = p | |
3247 | if len(pat): | |
3248 | repeats = [(pat,1)] | |
3249 | else: | |
3250 | repeats = list(repetitions(text)) | |
6f7f2f85 NC |
3251 | candidate = '' |
3252 | patterns = {} | |
3253 | for repeat in repeats: | |
3254 | (p,c) = repeat | |
e6957b3f | 3255 | #self.log.debug('%s :: %s' % (p,c)) |
9fd9b4f6 NC |
3256 | if len(p) < self.registryValue('%sMinimum' % kind, channel=channel): |
3257 | continue | |
12a3933a | 3258 | p = p.strip() |
6f7f2f85 NC |
3259 | if p in patterns: |
3260 | patterns[p] += c | |
3261 | else: | |
3262 | patterns[p] = c | |
6f7f2f85 NC |
3263 | if len(p) > self.registryValue('computedPattern',channel=channel): |
3264 | if len(p) > len(candidate): | |
3265 | candidate = p | |
6f7f2f85 | 3266 | elif len(p) * c > self.registryValue('computedPattern',channel=channel): |
54deeac6 | 3267 | tentative = ''.join(list((p,) * int(c))) |
6f7f2f85 | 3268 | if not tentative in text: |
54deeac6 | 3269 | tentative = ''.join(list(((p + ' '),) * int(c))) |
6f7f2f85 NC |
3270 | if not tentative in text: |
3271 | tentative = '' | |
3272 | if len(tentative): | |
3273 | tentative = tentative[:self.registryValue('computedPattern',channel=channel)] | |
3274 | if len(tentative) > len(candidate): | |
3275 | candidate = tentative | |
c56a3fc6 NC |
3276 | elif patterns[p] > self.registryValue('%sCount' % kind,channel=channel): |
3277 | if len(p) > len(candidate): | |
3278 | candidate = p | |
ff95c471 NC |
3279 | if candidate.strip() == channel: |
3280 | self.log.debug('pattern candidate %s discared in %s' % (candidate,channel)) | |
3281 | candidate = '' | |
9fd9b4f6 | 3282 | if len(candidate) and len(candidate) > self.registryValue('%sMinimum' % kind, channel=channel): |
6f7f2f85 NC |
3283 | found = False |
3284 | for p in chan.patterns: | |
3285 | if p in candidate: | |
3286 | found = True | |
3287 | break | |
3288 | if not found: | |
3289 | candidate = candidate.strip() | |
2f79d105 | 3290 | shareID = self.registryValue('shareComputedPatternID',channel=channel) |
ff95c471 NC |
3291 | i = self.getIrc(irc) |
3292 | if shareID != -1 or i.defcon: | |
2f79d105 | 3293 | nb = 0 |
a2395d14 | 3294 | for chan in i.channels: |
2f79d105 NC |
3295 | ch = i.channels[chan] |
3296 | life = self.registryValue('computedPatternLife',channel=chan) | |
3297 | if shareID != self.registryValue('shareComputedPatternID',channel=chan): | |
a2395d14 AE |
3298 | continue |
3299 | if not ch.patterns: | |
3300 | ch.patterns = utils.structures.TimeoutQueue(life) | |
3301 | elif ch.patterns.timeout != life: | |
3302 | ch.patterns.setTimeout(life) | |
13408b6e | 3303 | ch.patterns.enqueue(candidate) |
a2395d14 | 3304 | nb = nb + 1 |
18f102f7 | 3305 | self.logChannel(irc,'PATTERN: [%s] %s added "%s" in %s channels (%s)' % (channel,mask,candidate,nb,kind)) |
2f79d105 NC |
3306 | else: |
3307 | chan.patterns.enqueue(candidate) | |
18f102f7 | 3308 | self.logChannel(irc,'PATTERN: [%s] %s added "%s" for %ss (%s)' % (channel,mask,candidate,self.registryValue('computedPatternLife',channel=channel),kind)) |
ddcdfefc | 3309 | logs.enqueue(text) |
6f7f2f85 NC |
3310 | return result |
3311 | ||
ddcdfefc NC |
3312 | def isChannelMassRepeat (self,irc,msg,channel,mask,text): |
3313 | return self.isMassRepeat(irc,msg,channel,mask,text,False) | |
3314 | ||
6f7f2f85 | 3315 | def isChannelLowMassRepeat (self,irc,msg,channel,mask,text): |
ddcdfefc | 3316 | return self.isMassRepeat(irc,msg,channel,mask,text,True) |
b78d8847 | 3317 | |
ddcdfefc NC |
3318 | def isMassRepeat (self,irc,msg,channel,mask,text,low): |
3319 | kind = 'massRepeat' | |
3320 | key = 'mass Repeat' | |
3321 | if low: | |
3322 | kind = 'lowMassRepeat' | |
3323 | key = 'low mass Repeat' | |
3324 | limit = self.registryValue('%sPermit' % kind,channel=channel) | |
6f7f2f85 NC |
3325 | if limit < 0: |
3326 | return False | |
ddcdfefc NC |
3327 | if len(text) < self.registryValue('%sMinimum' % kind,channel=channel): |
3328 | return False | |
6f7f2f85 | 3329 | chan = self.getChan(irc,channel) |
ddcdfefc NC |
3330 | life = self.registryValue('%sLife' % kind,channel=channel) |
3331 | trigger = self.registryValue('%sPercent' % kind,channel=channel) | |
6f7f2f85 | 3332 | length = self.registryValue('computedPattern',channel=channel) |
6f7f2f85 NC |
3333 | if not key in chan.logs: |
3334 | chan.logs[key] = utils.structures.TimeoutQueue(life) | |
3335 | elif chan.logs[key].timeout != life: | |
3336 | chan.logs[key].setTimeout(life) | |
6f7f2f85 NC |
3337 | flag = False |
3338 | result = False | |
6f7f2f85 | 3339 | pattern = None |
6f7f2f85 | 3340 | s = '' |
ddcdfefc NC |
3341 | logs = chan.logs[key] |
3342 | for m in logs: | |
6f7f2f85 NC |
3343 | found = compareString(m,text) |
3344 | if found > trigger: | |
3345 | if length > 0: | |
3346 | pattern = largestString(m,text) | |
3347 | if len(pattern) < length: | |
3348 | pattern = None | |
3349 | else: | |
5fd1c53b | 3350 | s = s.strip() |
6f7f2f85 NC |
3351 | if len(s) > len(pattern): |
3352 | pattern = s | |
3353 | s = pattern | |
3354 | flag = True | |
3355 | break | |
3356 | if flag: | |
ddcdfefc | 3357 | result = self.isBadOnChannel(irc,channel,kind,channel) |
8895121b | 3358 | if result and pattern and length > -1: |
6f7f2f85 NC |
3359 | life = self.registryValue('computedPatternLife',channel=channel) |
3360 | if not chan.patterns: | |
3361 | chan.patterns = utils.structures.TimeoutQueue(life) | |
ddcdfefc | 3362 | elif chan.patterns.timeout != life: |
6f7f2f85 | 3363 | chan.patterns.setTimeout(life) |
8895121b | 3364 | if len(pattern) > length: |
6f7f2f85 NC |
3365 | pattern = pattern[:-1] |
3366 | found = False | |
3367 | for p in chan.patterns: | |
3368 | if p in pattern: | |
3369 | found = True | |
3370 | break | |
3371 | if not found: | |
2f79d105 NC |
3372 | shareID = self.registryValue('shareComputedPatternID',channel=channel) |
3373 | if shareID != -1: | |
3374 | nb = 0 | |
3375 | i = self.getIrc(irc) | |
3376 | for chan in i.channels: | |
3377 | ch = i.channels[chan] | |
3378 | if shareID != self.registryValue('shareComputedPatternID',channel=chan): | |
a2395d14 | 3379 | continue |
2f79d105 NC |
3380 | life = self.registryValue('computedPatternLife',channel=chan) |
3381 | if not ch.patterns: | |
3382 | ch.patterns = utils.structures.TimeoutQueue(life) | |
3383 | elif ch.patterns.timeout != life: | |
3384 | ch.patterns.setTimeout(life) | |
13408b6e | 3385 | ch.patterns.enqueue(pattern) |
a2395d14 | 3386 | nb = nb + 1 |
18f102f7 | 3387 | self.logChannel(irc,'PATTERN: [%s] %s added "%s" in %s channels (%s)' % (channel,mask,pattern,nb,kind)) |
2f79d105 NC |
3388 | else: |
3389 | chan.patterns.enqueue(pattern) | |
18f102f7 | 3390 | self.logChannel(irc,'PATTERN: [%s] %s added "%s" for %ss (%s)' % (channel,mask,pattern,self.registryValue('computedPatternLife',channel=channel),kind)) |
ddcdfefc | 3391 | logs.enqueue(text) |
6f7f2f85 NC |
3392 | if result and pattern: |
3393 | return result | |
3394 | return False | |
ddcdfefc | 3395 | |
6f7f2f85 NC |
3396 | def logChannel(self,irc,message): |
3397 | channel = self.registryValue('logChannel') | |
4aae7f45 | 3398 | i = self.getIrc(irc) |
6f7f2f85 | 3399 | if channel in irc.state.channels: |
13f59897 | 3400 | self.log.info('logChannel : %s' % message) |
4aae7f45 | 3401 | msg = ircmsgs.privmsg(channel,message) |
6f7f2f85 | 3402 | if self.registryValue('useNotice'): |
4aae7f45 | 3403 | msg = ircmsgs.notice(channel,message) |
b888f64b NC |
3404 | life = self.registryValue('announceLife') |
3405 | limit = self.registryValue('announcePermit') | |
c27f7c2a | 3406 | if limit > -1: |
b888f64b NC |
3407 | q = self.getIrcQueueFor(irc,'status','announce',life) |
3408 | q.enqueue(message) | |
3409 | if len(q) > limit: | |
3410 | if not i.throttled: | |
3411 | i.throttled = True | |
3412 | irc.queueMsg(ircmsgs.privmsg(channel,'NOTE: messages throttled to avoid spam for %ss' % life)) | |
70bd3e0c NC |
3413 | if not i.defcon: |
3414 | self.logChannel(irc,"INFO: ignores lifted and abuses end to klines for %ss due to abuses" % self.registryValue('defcon')) | |
c0bfbf12 NC |
3415 | if not i.god: |
3416 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick)) | |
3417 | else: | |
3418 | for channel in irc.state.channels: | |
3419 | if irc.isChannel(channel) and self.registryValue('defconMode',channel=channel): | |
3420 | if not 'z' in irc.state.channels[channel].modes: | |
3421 | if irc.nick in list(irc.state.channels[channel].ops): | |
3422 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s +qz $~a' % channel)) | |
3423 | else: | |
3424 | irc.sendMsg(ircmsgs.IrcMsg('MODE %s +oqz %s $~a' % (channel,irc.nick))) | |
3425 | ||
70bd3e0c | 3426 | i.defcon = time.time() |
b888f64b NC |
3427 | else: |
3428 | i.throttled = False | |
3429 | if i.opered: | |
3430 | irc.sendMsg(msg) | |
3431 | else: | |
3432 | irc.queueMsg(msg) | |
3433 | else: | |
3434 | if i.opered: | |
3435 | irc.sendMsg(msg) | |
3436 | else: | |
3437 | irc.queueMsg(msg) | |
4aae7f45 | 3438 | |
6f7f2f85 NC |
3439 | def doJoin (self,irc,msg): |
3440 | if irc.prefix == msg.prefix: | |
3441 | i = self.getIrc(irc) | |
3442 | return | |
3443 | channels = msg.args[0].split(',') | |
3444 | if not ircutils.isUserHostmask(msg.prefix): | |
3445 | return | |
4aae7f45 NC |
3446 | if ircdb.checkCapability(msg.prefix, 'protected'): |
3447 | return | |
6f7f2f85 | 3448 | i = self.getIrc(irc) |
6f7f2f85 NC |
3449 | prefix = msg.prefix |
3450 | gecos = None | |
3451 | account = None | |
3452 | if len(msg.args) == 3: | |
3453 | gecos = msg.args[2] | |
3454 | account = msg.args[1] | |
3455 | if account == '*': | |
3456 | account = None | |
ff95c471 NC |
3457 | else: |
3458 | aa = account.lower() | |
99ebdad5 NC |
3459 | for u in i.klinednicks: |
3460 | if aa == u: | |
18f102f7 | 3461 | self.logChannel(irc,"SERVICE: %s (%s) lethaled account (extended-join %s)" % (msg.prefix,account,msg.args[0])) |
99ebdad5 | 3462 | src = msg.nick |
18f102f7 | 3463 | i.klinednicks.enqueue(aa) |
99ebdad5 NC |
3464 | if not src in i.tokline: |
3465 | i.toklineresults[src] = {} | |
3466 | i.toklineresults[src]['kind'] = 'evade' | |
3467 | i.tokline[src] = src | |
18f102f7 NC |
3468 | def f (): |
3469 | irc.sendMsg(ircmsgs.IrcMsg('WHOIS %s %s' % (src,src))) | |
3470 | schedule.addEvent(f,time.time()+random.randint(0,7)) | |
3471 | #irc.sendMsg(ircmsgs.IrcMsg('WHOIS %s %s' % (src,src))) | |
99ebdad5 | 3472 | break |
6f7f2f85 NC |
3473 | for channel in channels: |
3474 | if ircutils.isChannel(channel) and channel in irc.state.channels: | |
3475 | if self.registryValue('ignoreChannel',channel): | |
3476 | continue | |
3477 | chan = self.getChan(irc,channel) | |
3478 | t = time.time() | |
f90ccc3a | 3479 | mask = self.prefixToMask(irc,msg.prefix,channel) |
b21941a8 | 3480 | if isCloaked(msg.prefix,self) or account: |
6f7f2f85 NC |
3481 | t = t - self.registryValue('ignoreDuration',channel=channel) - 1 |
3482 | chan.nicks[msg.nick] = [t,msg.prefix,mask,gecos,account] | |
54deeac6 NC |
3483 | if self.registryValue('ignoreRegisteredUser',channel=channel): |
3484 | if account: | |
3485 | continue | |
c27f7c2a NC |
3486 | if i.netsplit: |
3487 | continue | |
3488 | if 'gateway/shell/matrix.org' in msg.prefix: | |
3489 | continue | |
70bd3e0c | 3490 | life = self.registryValue('massJoinLife',channel=channel) |
c27f7c2a | 3491 | limit = self.registryValue('massJoinPermit',channel=channel) |
70bd3e0c NC |
3492 | trigger = self.registryValue('massJoinPercent',channel=channel) |
3493 | length = self.registryValue('massJoinMinimum',channel=channel) | |
c27f7c2a | 3494 | # massJoin for the whole channel |
645a036d | 3495 | flags = [] |
c27f7c2a NC |
3496 | if limit > -1: |
3497 | b = self.isBadOnChannel(irc,channel,'massJoin',channel) | |
99ebdad5 NC |
3498 | if b: |
3499 | self.log.info('Massjoin detected in %s (%s/%s)' % (channel,life,limit)) | |
c27f7c2a NC |
3500 | life = self.registryValue('massJoinHostLife',channel=channel) |
3501 | limit = self.registryValue('massJoinHostPermit',channel=channel) | |
3502 | ## massJoin same ip/host | |
3503 | if limit > -1: | |
3504 | b = self.isBadOnChannel(irc,channel,'massJoinHost',mask) | |
3505 | if b: | |
d8d19c3a NC |
3506 | if not mask in flags: |
3507 | flags.append(mask) | |
ff95c471 NC |
3508 | # self.logChannel(irc,'NOTE: [%s] %s (%s)' % (channel,b,mask)) |
3509 | # life = self.registryValue('massJoinNickLife',channel=channel) | |
3510 | # limit = self.registryValue('massJoinNickPermit',channel=channel) | |
c27f7c2a | 3511 | ## massJoin similar nicks |
ff95c471 NC |
3512 | # if limit > -1: |
3513 | # key = 'massJoinNick' | |
3514 | # if not key in chan.logs: | |
3515 | # chan.logs[key] = utils.structures.TimeoutQueue(life) | |
3516 | # elif chan.logs[key].timeout != life: | |
3517 | # chan.logs[key].setTimeout(life) | |
3518 | # logs = chan.logs[key] | |
3519 | # flag = False | |
3520 | # pattern = '' | |
3521 | # for m in logs: | |
3522 | # if compareString(m,msg.nick) > trigger: | |
3523 | # flag = True | |
3524 | # p = largestString(m,msg.nick) | |
3525 | # if len(p) > len(pattern): | |
3526 | # pattern = p | |
3527 | # if flag and len(pattern) > length and not 'Guest' in pattern: | |
3528 | # b = self.isBadOnChannel(irc,channel,key,pattern) | |
3529 | # if b: | |
3530 | # if not mask in flags: | |
3531 | # flags.append(mask) | |
3532 | # self.logChannel(irc,'NOTE: [%s] %s (%s)' % (channel,b,pattern)) | |
3533 | # logs.enqueue(msg.nick) | |
c27f7c2a | 3534 | ## massJoin similar gecos |
ff95c471 NC |
3535 | # life = self.registryValue('massJoinGecosLife',channel=channel) |
3536 | # limit = self.registryValue('massJoinGecosPermit',channel=channel) | |
3537 | # if limit > -1: | |
3538 | # key = 'massJoinGecos' | |
3539 | # if not key in chan.logs: | |
3540 | # chan.logs[key] = utils.structures.TimeoutQueue(life) | |
3541 | # elif chan.logs[key].timeout != life: | |
3542 | # chan.logs[key].setTimeout(life) | |
3543 | # logs = chan.logs[key] | |
3544 | # flag = False | |
3545 | # pattern = '' | |
3546 | # for m in logs: | |
3547 | # if compareString(m,gecos) > trigger: | |
3548 | # flag = True | |
3549 | # p = largestString(m,gecos) | |
3550 | # if len(p) > len(pattern): | |
3551 | # pattern = p | |
3552 | # if flag and len(pattern) > length: | |
3553 | # b = self.isBadOnChannel(irc,channel,key,pattern) | |
3554 | # if b: | |
3555 | # if not mask in flags: | |
3556 | # flags.append(mask) | |
3557 | # self.logChannel(irc,'NOTE: [%s] %s (%s)' % (channel,b,pattern)) | |
3558 | # logs.enqueue(gecos) | |
3559 | if self.hasAbuseOnChannel(irc,channel,'cycle') and self.hasAbuseOnChannel(irc,channel,'massJoinHost') and len(flags) > 0 and self.registryValue('massJoinTakeAction',channel=channel): | |
e6957b3f | 3560 | for u in flags: |
ff95c471 NC |
3561 | if not u in i.klines: |
3562 | self.kill(irc,msg.nick,self.registryValue('killMessage',channel=channel)) | |
3563 | uid = random.randint(0,1000000) | |
3564 | self.kline(irc,msg.prefix,u,self.registryValue('klineDuration'),'%s - cycle/massJoinHost %s !dnsbl' % (uid,channel)) | |
3565 | self.logChannel(irc,'BAD: [%s] %s (cycle/massJoinHost %s - %s)' % (channel,u,msg.prefix,uid)) | |
43c56ec1 | 3566 | |
6f7f2f85 NC |
3567 | def doPart (self,irc,msg): |
3568 | channels = msg.args[0].split(',') | |
3569 | i = self.getIrc(irc) | |
21efdd22 NC |
3570 | reason = '' |
3571 | if len(msg.args) == 2: | |
3572 | reason = msg.args[1].lstrip().rstrip() | |
6f7f2f85 NC |
3573 | if not ircutils.isUserHostmask(msg.prefix): |
3574 | return | |
3575 | if msg.prefix == irc.prefix: | |
3576 | for channel in channels: | |
87804149 | 3577 | if ircutils.isChannel(channel): |
79971e5d | 3578 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) |
21efdd22 | 3579 | self.logChannel(irc,'PART: [%s] %s' % (channel,reason)) |
6f7f2f85 | 3580 | if channel in i.channels: |
cf659704 | 3581 | del i.channels[channel] |
6f7f2f85 | 3582 | return |
b78d8847 | 3583 | mask = self.prefixToMask(irc,msg.prefix) |
6f7f2f85 NC |
3584 | isBanned = False |
3585 | reason = '' | |
3586 | if len(msg.args) == 2: | |
3587 | reason = msg.args[1].lstrip().rstrip() | |
3588 | for channel in channels: | |
3589 | if ircutils.isChannel(channel) and channel in irc.state.channels and not isBanned: | |
3590 | chan = self.getChan(irc,channel) | |
3591 | if msg.nick in chan.nicks: | |
3592 | if self.registryValue('ignoreChannel',channel): | |
3593 | continue | |
54deeac6 NC |
3594 | if self.registryValue('ignoreRegisteredUser',channel=channel): |
3595 | if len(chan.nicks[msg.nick]) > 4: | |
3596 | if chan.nicks[msg.nick][4]: | |
3597 | continue | |
6f7f2f85 NC |
3598 | protected = ircdb.makeChannelCapability(channel, 'protected') |
3599 | if ircdb.checkCapability(msg.prefix, protected): | |
3600 | continue | |
99ebdad5 | 3601 | if reason == 'Changing Host' or i.netsplit: |
c27f7c2a | 3602 | continue |
0603d7b7 | 3603 | bad = False |
cc9ace5d NC |
3604 | if len(reason) and 'Kicked by @appservice-irc:matrix.org' in reason: |
3605 | continue | |
0603d7b7 | 3606 | flag = ircdb.makeChannelCapability(channel, 'cycle') |
96fdc6a9 | 3607 | if ircdb.checkCapability(msg.prefix, flag): |
0603d7b7 | 3608 | bad = self.isBadOnChannel(irc,channel,'cycle',mask) |
930cc640 NC |
3609 | if bad: |
3610 | self.isAbuseOnChannel(irc,channel,'cycle',mask) | |
6f7f2f85 NC |
3611 | if bad: |
3612 | isBanned = True | |
992db209 NC |
3613 | uid = random.randint(0,1000000) |
3614 | log = "BAD: [%s] %s (join/part - %s)" % (channel,msg.prefix,uid) | |
3615 | comment = '%s - join/part flood in %s' % (uid,channel) | |
6f7f2f85 | 3616 | self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),comment,self.registryValue('klineMessage'),log) |
e35a4b0e | 3617 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) |
43c56ec1 | 3618 | if len(reason): |
99ebdad5 NC |
3619 | if 'Kicked by @appservice-irc:matrix.org' in reason or 'requested by' in reason: |
3620 | continue | |
6f7f2f85 NC |
3621 | bad = self.isChannelMassRepeat(irc,msg,channel,mask,reason) |
3622 | if bad: | |
3623 | # todo, needs to see more on that one to avoid false positive | |
3624 | #self.kill(irc,msg.nick,msg.prefix) | |
3625 | #self.kline(irc,msg.prefix,mask,self.registryValue('klineDuration'),'%s in %s' % (bad,channel)) | |
3626 | self.logChannel(irc,"IGNORED: [%s] %s (Part's message %s) : %s" % (channel,msg.prefix,bad,reason)) | |
730f1fc2 | 3627 | if not isBanned: |
930cc640 | 3628 | life = self.registryValue('cycleLife',channel=channel) |
276258c4 NC |
3629 | if self.hasAbuseOnChannel(irc,channel,'cycle') and time.time() - chan.nicks[msg.nick][0] < life: |
3630 | isBanned = True | |
3631 | uid = random.randint(0,1000000) | |
3632 | log = "BAD: [%s] %s (cycle abuse - %s)" % (channel,msg.prefix,uid) | |
3633 | comment = '%s - cycle abuse in %s' % (uid,channel) | |
3634 | self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),comment,self.registryValue('klineMessage'),log) | |
3635 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) | |
730f1fc2 | 3636 | flag = ircdb.makeChannelCapability(channel, 'joinSpamPart') |
276258c4 | 3637 | if ircdb.checkCapability(msg.prefix, flag) and not isBanned: |
730f1fc2 NC |
3638 | limit = self.registryValue('joinSpamPartPermit',channel=channel) |
3639 | if limit > -1: | |
3640 | kind = 'joinSpamPart' | |
3641 | life = self.registryValue('joinSpamPartLife',channel=channel) | |
3642 | key = mask | |
3643 | if kind in chan.buffers and key in chan.buffers[kind] and len(chan.buffers[kind][key]) == limit and msg.nick in chan.nicks and time.time() - chan.nicks[msg.nick][0] < life: | |
13bac464 NC |
3644 | self.isAbuseOnChannel(irc,channel,'joinSpamPart',mask) |
3645 | if self.hasAbuseOnChannel(irc,channel,'joinSpamPart'): | |
992db209 | 3646 | uid = random.randint(0,1000000) |
13bac464 | 3647 | reason = '(%s/%ss joinSpamPart)' % (limit,life) |
992db209 | 3648 | klinereason = '%s - %s' % (uid,reason) |
13bac464 | 3649 | if i.defcon: |
992db209 | 3650 | klinereason = '%s !dnsbl' % reason |
13bac464 | 3651 | self.kline(irc,msg.prefix,mask,self.registryValue('klineDuration'),klinereason) |
43c56ec1 | 3652 | self.logChannel(irc,'BAD: [%s] %s (%s - %s)' (channel,msg.prefix,reason,uid)) |
13bac464 NC |
3653 | isBanned = True |
3654 | chan.buffers[kind][key].reset() | |
3655 | continue | |
6f7f2f85 NC |
3656 | def doKick (self,irc,msg): |
3657 | channel = target = reason = None | |
3658 | if len(msg.args) == 3: | |
3659 | (channel,target,reason) = msg.args | |
3660 | else: | |
3661 | (channel,target) = msg.args | |
3662 | reason = '' | |
3663 | i = self.getIrc(irc) | |
3664 | if target == irc.nick: | |
3665 | if channel in i.channels: | |
645a036d | 3666 | self.setRegistryValue('lastActionTaken',-1.0,channel=channel) |
43c56ec1 | 3667 | self.logChannel(irc,'PART: [%s] %s (kicked)' % (channel,reason)) |
cf659704 | 3668 | del i.channels[channel] |
27cc952d NC |
3669 | try: |
3670 | network = conf.supybot.networks.get(irc.network) | |
3671 | network.channels().remove(channel) | |
3672 | except KeyError: | |
afd11ab6 | 3673 | pass |
b78d8847 | 3674 | |
6f7f2f85 NC |
3675 | def doQuit (self,irc,msg): |
3676 | if msg.prefix == irc.prefix: | |
3677 | return | |
3678 | reason = '' | |
3679 | if len(msg.args) == 1: | |
3680 | reason = msg.args[0].lstrip().rstrip() | |
3681 | i = self.getIrc(irc) | |
3682 | if reason == '*.net *.split': | |
6f7f2f85 NC |
3683 | if not i.netsplit: |
3684 | self.logChannel(irc,'INFO: netsplit activated for %ss : some abuses are ignored' % self.registryValue('netsplitDuration')) | |
3685 | i.netsplit = time.time() + self.registryValue('netsplitDuration') | |
f12749a8 NC |
3686 | if i.netsplit: |
3687 | return | |
b78d8847 | 3688 | mask = self.prefixToMask(irc,msg.prefix) |
6f7f2f85 NC |
3689 | isBanned = False |
3690 | (nick,ident,host) = ircutils.splitHostmask(msg.prefix) | |
3691 | for channel in irc.state.channels: | |
99ebdad5 | 3692 | if ircutils.isChannel(channel) and not i.netsplit: |
6f7f2f85 | 3693 | chan = self.getChan(irc,channel) |
43c56ec1 NC |
3694 | if self.registryValue('ignoreChannel',channel): |
3695 | continue | |
6f7f2f85 | 3696 | if msg.nick in chan.nicks: |
54deeac6 NC |
3697 | if self.registryValue('ignoreRegisteredUser',channel=channel): |
3698 | if len(chan.nicks[msg.nick]) > 4: | |
3699 | if chan.nicks[msg.nick][4]: | |
3700 | continue | |
6f7f2f85 NC |
3701 | protected = ircdb.makeChannelCapability(channel, 'protected') |
3702 | if ircdb.checkCapability(msg.prefix, protected): | |
3703 | continue | |
0603d7b7 NC |
3704 | bad = False |
3705 | flag = ircdb.makeChannelCapability(channel, 'broken') | |
5ecb3350 NC |
3706 | if 'tor-sasl' in mask: |
3707 | continue | |
96fdc6a9 | 3708 | if ircdb.checkCapability(msg.prefix, flag): |
0603d7b7 | 3709 | bad = self.isBadOnChannel(irc,channel,'broken',mask) |
42f3350d NC |
3710 | if isBanned: |
3711 | continue | |
b78d8847 | 3712 | if bad and not i.netsplit: |
992db209 NC |
3713 | uid = random.randint(0,1000000) |
3714 | self.kline(irc,msg.prefix,mask,self.registryValue('brokenDuration'),'%s - %s in %s' % (uid,'join/quit flood',channel),self.registryValue('brokenReason') % self.registryValue('brokenDuration')) | |
3715 | self.logChannel(irc,'BAD: [%s] %s (%s - %s)' % (channel,msg.prefix,'broken client',uid)) | |
6f7f2f85 | 3716 | isBanned = True |
6f7f2f85 | 3717 | continue |
730f1fc2 NC |
3718 | flag = ircdb.makeChannelCapability(channel, 'joinSpamPart') |
3719 | if ircdb.checkCapability(msg.prefix, flag) and reason == 'Remote host closed the connection': | |
3720 | limit = self.registryValue('joinSpamPartPermit',channel=channel) | |
3721 | if limit > -1: | |
3722 | kind = 'joinSpamPart' | |
3723 | life = self.registryValue('joinSpamPartLife',channel=channel) | |
3724 | key = mask | |
3725 | if kind in chan.buffers and key in chan.buffers[kind] and len(chan.buffers[kind][key]) == limit and msg.nick in chan.nicks and time.time() - chan.nicks[msg.nick][0] < life: | |
13bac464 NC |
3726 | self.isAbuseOnChannel(irc,channel,'joinSpamPart',mask) |
3727 | if self.hasAbuseOnChannel(irc,channel,'joinSpamPart'): | |
992db209 | 3728 | uid = random.randint(0,1000000) |
13bac464 | 3729 | reason = '(%s/%ss joinSpamPart)' % (limit,life) |
992db209 | 3730 | klinereason = '%s - %s' % (uid,reason) |
13bac464 | 3731 | if i.defcon: |
992db209 | 3732 | klinereason = '%s !dnsbl' % reason |
13bac464 | 3733 | self.kline(irc,msg.prefix,mask,self.registryValue('klineDuration'),klinereason) |
3774e684 | 3734 | self.logChannel(irc,'BAD: [%s] %s (%s - %s)' % (channel,msg.prefix,reason,uid)) |
13bac464 NC |
3735 | isBanned = True |
3736 | chan.buffers[kind][key].reset() | |
3737 | continue | |
6f7f2f85 NC |
3738 | hosts = self.registryValue('brokenHost',channel=channel) |
3739 | reasons = ['Read error: Connection reset by peer','Client Quit','Excess Flood','Max SendQ exceeded','Remote host closed the connection'] | |
322f3858 | 3740 | if 'broken' in chan.buffers and mask in chan.buffers['broken'] and len(chan.buffers['broken'][mask]) > 1 and reason in reasons and len(hosts): |
6f7f2f85 NC |
3741 | found = False |
3742 | for h in hosts: | |
3743 | if len(h): | |
3744 | if h.isdigit() and host.startswith(h): | |
3745 | found = True | |
3746 | break | |
3747 | if h in host: | |
3748 | found = True | |
3749 | break | |
3750 | if found and len(chan.nicks[msg.nick]) == 5: | |
3751 | gecos = chan.nicks[msg.nick][3] | |
3752 | account = chan.nicks[msg.nick][4] | |
3753 | if not account and gecos == msg.nick and gecos in ident and len(msg.nick) < 6: | |
3754 | isBanned = True | |
992db209 NC |
3755 | uid = random.randint(0,1000000) |
3756 | self.kline(irc,msg.prefix,mask,self.registryValue('brokenDuration')*4,'%s - %s in %s' % (uid,'join/quit flood',channel),self.registryValue('brokenReason') % (self.registryValue('brokenDuration')*4)) | |
3757 | self.logChannel(irc,'BAD: [%s] %s (%s - %s)' % (channel,msg.prefix,'broken bottish client',uid)) | |
b78d8847 | 3758 | |
6f7f2f85 NC |
3759 | def doNick (self,irc,msg): |
3760 | oldNick = msg.prefix.split('!')[0] | |
3761 | newNick = msg.args[0] | |
3762 | if oldNick == irc.nick or newNick == irc.nick: | |
3763 | return | |
3764 | newPrefix = '%s!%s' % (newNick,msg.prefix.split('!')[1]) | |
b78d8847 | 3765 | mask = self.prefixToMask(irc,newPrefix) |
6f7f2f85 | 3766 | i = self.getIrc(irc) |
f12749a8 NC |
3767 | if i.netsplit: |
3768 | return | |
6f7f2f85 NC |
3769 | isBanned = False |
3770 | for channel in irc.state.channels: | |
3771 | if ircutils.isChannel(channel): | |
3772 | if self.registryValue('ignoreChannel',channel): | |
3773 | continue | |
3774 | protected = ircdb.makeChannelCapability(channel, 'protected') | |
3775 | if ircdb.checkCapability(newPrefix, protected): | |
3776 | continue | |
3777 | chan = self.getChan(irc,channel) | |
3778 | if oldNick in chan.nicks: | |
3779 | chan.nicks[newNick] = chan.nicks[oldNick] | |
f12749a8 | 3780 | # todo check digit/hexa nicks too |
6f7f2f85 NC |
3781 | if not newNick.startswith('Guest'): |
3782 | if not isBanned: | |
0603d7b7 | 3783 | reason = False |
54deeac6 NC |
3784 | if self.registryValue('ignoreRegisteredUser',channel=channel): |
3785 | if newNick in chan.nicks and len(chan.nicks[newNick]) > 4 and chan.nicks[newNick][4]: | |
3786 | continue | |
0603d7b7 | 3787 | flag = ircdb.makeChannelCapability(channel, 'nick') |
96fdc6a9 | 3788 | if ircdb.checkCapability(msg.prefix, flag): |
0603d7b7 | 3789 | reason = self.isBadOnChannel(irc,channel,'nick',mask) |
6f7f2f85 NC |
3790 | hasBeenIgnored = False |
3791 | ignore = self.registryValue('ignoreDuration',channel=channel) | |
3792 | if ignore > 0: | |
3793 | ts = chan.nicks[newNick][0] | |
3794 | if time.time()-ts > ignore: | |
3795 | hasBeenIgnored = True | |
4165a619 | 3796 | if not isCloaked(msg.prefix,self): |
6f7f2f85 NC |
3797 | if i.defcon or chan.called: |
3798 | hasBeenIgnored = False | |
92740f96 NC |
3799 | if not reason and i.defcon and self.hasAbuseOnChannel(irc,channel,'nick'): |
3800 | reason = 'nick changes, due to abuses' | |
6f7f2f85 | 3801 | if reason: |
6f7f2f85 NC |
3802 | if hasBeenIgnored: |
3803 | bypass = self.isBadOnChannel(irc,channel,'bypassIgnore',mask) | |
3804 | if bypass: | |
992db209 | 3805 | uid = random.randint(0,1000000) |
6f7f2f85 | 3806 | comment = '%s %s' % (reason,bypass) |
992db209 NC |
3807 | log = 'BAD: [%s] %s (%s - %s)' % (channel,newPrefix,comment,uid) |
3808 | self.ban(irc,newNick,newPrefix,mask,self.registryValue('klineDuration'),'%s - %s' % (uid,comment),self.registryValue('klineMessage'),log) | |
e35a4b0e | 3809 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) |
6f7f2f85 NC |
3810 | isBanned = True |
3811 | else: | |
3812 | self.logChannel(irc,'IGNORED: [%s] %s (%s)' % (channel,newPrefix,reason)) | |
3813 | else: | |
992db209 NC |
3814 | uid = random.randint(0,1000000) |
3815 | log = 'BAD: [%s] %s (%s - %s)' % (channel,newPrefix,reason,uid) | |
3816 | self.ban(irc,newNick,newPrefix,mask,self.registryValue('klineDuration'),'%s - %s' % (uid,reason),self.registryValue('klineMessage'),log) | |
e35a4b0e | 3817 | self.setRegistryValue('lastActionTaken',time.time(),channel=channel) |
6f7f2f85 NC |
3818 | isBanned = True |
3819 | del chan.nicks[oldNick] | |
b78d8847 | 3820 | |
6f7f2f85 NC |
3821 | def reset(self): |
3822 | self._ircs = ircutils.IrcDict() | |
b78d8847 | 3823 | |
6f7f2f85 | 3824 | def die(self): |
99ebdad5 | 3825 | self.log.info('die() called') |
7f2e65ae | 3826 | self.cache = {} |
4aae7f45 NC |
3827 | try: |
3828 | conf.supybot.protocols.irc.throttleTime.setValue(1.6) | |
3829 | except: | |
7f2e65ae | 3830 | pass |
6f7f2f85 | 3831 | self._ircs = ircutils.IrcDict() |
99ebdad5 | 3832 | super().die() |
6f7f2f85 NC |
3833 | |
3834 | def doError (self,irc,msg): | |
3835 | self._ircs = ircutils.IrcDict() | |
b78d8847 | 3836 | |
6f7f2f85 NC |
3837 | def makeDb(self, filename): |
3838 | """Create a database and connect to it.""" | |
3839 | if os.path.exists(filename): | |
3840 | db = sqlite3.connect(filename,timeout=10) | |
3841 | db.text_factory = str | |
3842 | return db | |
3843 | db = sqlite3.connect(filename) | |
3844 | db.text_factory = str | |
3845 | c = db.cursor() | |
3846 | c.execute("""CREATE TABLE patterns ( | |
3847 | id INTEGER PRIMARY KEY, | |
3848 | pattern VARCHAR(512) NOT NULL, | |
3849 | regexp INTEGER, | |
3850 | mini INTEGER, | |
3851 | life INTEGER, | |
3852 | operator VARCHAR(512) NOT NULL, | |
3853 | comment VARCHAR(512), | |
3854 | triggered INTEGER, | |
3855 | at TIMESTAMP NOT NULL, | |
3856 | removed_at TIMESTAMP, | |
3857 | removed_by VARCHAR(512) | |
3858 | )""") | |
3859 | db.commit() | |
3860 | c.close() | |
3861 | return db | |
b78d8847 | 3862 | |
6f7f2f85 NC |
3863 | def getDb(self, irc): |
3864 | """Use this to get a database for a specific irc.""" | |
3865 | currentThread = threading.currentThread() | |
3866 | if irc not in self.dbCache and currentThread == world.mainThread: | |
3867 | self.dbCache[irc] = self.makeDb(self.makeFilename(irc)) | |
3868 | if currentThread != world.mainThread: | |
3869 | db = self.makeDb(self.makeFilename(irc)) | |
3870 | else: | |
3871 | db = self.dbCache[irc] | |
3872 | db.isolation_level = None | |
3873 | return db | |
3874 | ||
3875 | Class = Sigyn | |
54deeac6 | 3876 |