]> jfr.im git - irc/freenode/Sigyn.git/blob - plugin.py
Merge branch 'master' of github.com:freenode/Sigyn
[irc/freenode/Sigyn.git] / plugin.py
1 #!/usr/bin/env/python
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 ###
33 # coding: utf-8
34 import os
35 import re
36 import sys
37 import time
38 import requests
39 from urllib.parse import urlencode
40 import sqlite3
41 import http.client
42 import threading
43 import dns.resolver
44 import json
45 import ipaddress
46 import random
47 import supybot.log as log
48 import supybot.conf as conf
49 import supybot.utils as utils
50 import supybot.ircdb as ircdb
51 import supybot.world as world
52 from supybot.commands import *
53 import supybot.ircmsgs as ircmsgs
54 import supybot.plugins as plugins
55 import supybot.commands as commands
56 import supybot.ircutils as ircutils
57 import supybot.callbacks as callbacks
58 import supybot.schedule as schedule
59 import supybot.registry as registry
60 from ftfy.badness import sequence_weirdness
61 from ftfy.badness import text_cost
62 try:
63 from supybot.i18n import PluginInternationalization
64 _ = PluginInternationalization('Sigyn')
65 except:
66 _ = lambda x:x
67
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
74 def isCloaked (prefix,sig):
75 if sig.registryValue('useWhoWas'):
76 return False
77 if not ircutils.isUserHostmask(prefix):
78 return False
79 (nick,ident,host) = ircutils.splitHostmask(prefix)
80 if '/' in host:
81 if host.startswith('gateway/') or host.startswith('nat/'):
82 return False
83 return True
84 return False
85
86 def compareString (a,b):
87 """return 0 to 1 float percent of similarity ( 0.85 seems to be a good average )"""
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):
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
101 m = [[0] * (1 + len(s2)) for i in range(1 + len(s1))]
102 longest, x_longest = 0, 0
103 for x in range(1, 1 + len(s1)):
104 for y in range(1, 1 + len(s2)):
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):
149
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')
151
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 = {}
162 # contains various TimeoutQueue for detection purpose
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 = {}
175 # flag or time
176 self.netsplit = time.time() + 300
177 self.ping = None
178 self.servers = {}
179 self.resolving = {}
180 self.stats = {}
181 self.ilines = {}
182 self.throttled = False
183 self.lastDefcon = False
184 self.god = False
185 self.mx = {}
186 self.tokline = {}
187 self.toklineresults = {}
188 self.dlines = []
189 self.invites = {}
190 self.nicks = {}
191 self.cleandomains = {}
192 self.klinednicks = utils.structures.TimeoutQueue(86400*2)
193 self.lastKlineOper = ''
194
195 def __repr__(self):
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)
198
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
206 regexp = int(regexp)
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()
213
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)
221 self.patterns[uid] = Pattern(uid,pattern,regexp,limit,life)
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])
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))
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))
274 return results
275 return []
276
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))
294
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]
303 regexp = int(regexp)
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
308 elif not removed_at and not active:
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
316
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
334 class Chan (object):
335 __slots__ = ('channel', 'patterns', 'buffers', 'logs', 'nicks', 'called', 'klines', 'requestedBySpam')
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
343 self.klines = utils.structures.TimeoutQueue(1800)
344 self.requestedBySpam = False
345
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):
351 __slots__ = ('uid', 'pattern', 'limit', 'life', '_match')
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)
360 else:
361 self.pattern = pattern.lower()
362
363 def match (self,text):
364 s = False
365 if isinstance(text,bytes):
366 text = str(text, "utf-8")
367 if self._match:
368 s = self._match.search (text) != None
369 else:
370 text = text.lower()
371 s = self.pattern in text
372 return s
373
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
378 class Sigyn(callbacks.Plugin,plugins.ChannelDBHandler):
379 """Network and Channels Spam protections"""
380 threaded = True
381 noIgnore = True
382
383 def __init__(self, irc):
384 callbacks.Plugin.__init__(self, irc)
385 plugins.ChannelDBHandler.__init__(self)
386 self._ircs = ircutils.IrcDict()
387 self.cache = {}
388 self.getIrc(irc)
389 self.starting = world.starting
390 self.recaps = re.compile("[A-Z]")
391 self.ipfiltered = {}
392 self.rmrequestors = {}
393 self.spamchars = {'Ḕ', 'Î', 'Ù', 'Ṋ', 'ℰ', 'Ừ', 'ś', 'ï', 'ℯ', 'ļ', 'ẋ', 'ᾒ', 'ἶ', 'ệ', 'ℓ', 'Ŋ', 'Ḝ', 'ξ', 'ṵ', 'û', 'ẻ', 'Ũ', 'ṡ', '§', 'Ƚ', 'Š', 'ᶙ', 'ṩ', '¹', 'ư', 'Ῐ', 'Ü', 'ŝ', 'ὴ', 'Ș', 'ũ', 'ῑ', 'ⱷ', 'Ǘ', 'Ɇ', 'ĭ', 'ἤ', 'Ɲ', 'Ǝ', 'ủ', 'µ', 'Ỵ', 'Ű', 'ū', 'į', 'ἳ', 'ΐ', 'ḝ', 'Ɛ', 'ṇ', 'È', 'ῆ', 'ử', 'Ň', 'υ', 'Ǜ', 'Ἔ', 'Ὑ', 'μ', 'Ļ', 'ů', 'Ɫ', 'ŷ', 'Ǚ', 'ἠ', 'Ĺ', 'Ę', 'Ὲ', 'Ẍ', 'Ɣ', 'Ϊ', 'ℇ', 'ẍ', 'ῧ', 'ϵ', 'ἦ', 'ừ', 'ṳ', 'ᾕ', 'ṋ', 'ù', 'ῦ', 'Ι', 'ῠ', 'ṥ', 'ὲ', 'ê', 'š', 'ě', 'ề', 'ẽ', 'ī', 'Ė', 'ỷ', 'Ủ', 'ḯ', 'Ἓ', 'Ὓ', 'Ş', 'ύ', 'Ṧ', 'Ŷ', 'ἒ', 'ἵ', 'ė', 'ἰ', 'ẹ', 'Ȇ', 'Ɏ', 'Ί', 'ὶ', 'Ε', 'ḛ', 'Ὤ', 'ǐ', 'ȇ', 'ἢ', 'í', 'ȕ', 'Ữ', '$', 'ή', 'Ṡ', 'ἷ', 'Ḙ', 'Ὢ', 'Ṉ', 'Ľ', 'ῃ', 'Ụ', 'Ṇ', 'ᾐ', 'Ů', 'Ἕ', 'ý', 'Ȅ', 'ᴌ', 'ύ', 'ņ', 'ὒ', 'Ý', 'ế', 'ĩ', 'ǘ', 'Ē', 'ṹ', 'Ư', 'é', 'Ÿ', 'ΰ', 'Ὦ', 'Ë', 'ỳ', 'ἓ', 'ĕ', 'ἑ', 'ṅ', 'ȗ', 'Ν', 'ί', 'ể', 'ᴟ', 'è', 'ᴇ', 'ḭ', 'ȝ', 'ϊ', 'ƪ', 'Ὗ', 'Ų', 'Ề', 'Ṷ', 'ü', 'Ɨ', 'Ώ', 'ň', 'ṷ', 'ƞ', 'Ȗ', 'ș', 'ῒ', 'Ś', 'Ự', 'Ń', 'Ἳ', 'Ứ', 'Ἷ', 'ἱ', 'ᾔ', 'ÿ', 'Ẽ', 'ὖ', 'ὑ', 'ἧ', 'Ὥ', 'ṉ', 'Ὠ', 'ℒ', 'Ệ', 'Ὼ', 'Ẻ', 'ḙ', 'Ŭ', '₴', 'Ὡ', 'ȉ', 'Ṅ', 'ᵪ', 'ữ', 'Ὧ', 'ń', 'Ἐ', 'Ú', 'ɏ', 'î', 'Ⱡ', 'Ƨ', 'Ě', 'ȿ', 'ᴉ', 'Ṩ', 'Ê', 'ȅ', 'ᶊ', 'Ṻ', 'Ḗ', 'ǹ', 'ᴣ', 'ş', 'Ï', 'ᾗ', 'ự', 'ὗ', 'ǔ', 'ᶓ', 'Ǹ', 'Ἶ', 'Ṳ', 'Ʊ', 'ṻ', 'Ǐ', 'ᵴ', 'ῇ', 'Ẹ', 'Ế', 'Ϋ', 'Ū', 'Ῑ', 'ί', 'ỹ', 'Ḯ', 'ǀ', 'Ὣ', 'Ȳ', 'ǃ', 'ų', 'ϴ', 'Ώ', 'Í', 'ì', 'ι', 'ῄ', 'ΰ', 'ἣ', 'ῡ', 'Ἒ', 'Ḽ', 'Ȉ', 'Έ', 'ἴ', 'ᶇ', 'ἕ', 'ǚ', 'Ī', 'Έ', '¥', 'Ṵ', 'ὔ', 'Ŝ', 'ῢ', 'Ἱ', 'ű', 'Ḷ', 'Ὶ', 'ḗ', 'ᴜ', 'ę', 'ὐ', 'Û', 'ᾑ', 'Ʋ', 'Ἑ', 'Ì', 'ŋ', 'Ḛ', 'ỵ', 'Ễ', '℮', '×', 'Ῠ', 'Ἵ', 'Ύ', 'Ử', 'ᴈ', 'ē', 'Ἰ', 'ᶖ', 'ȳ', 'Ǯ', 'ὓ', 'ὕ', 'ῂ', 'Ĕ', 'É', 'ᾓ', 'Ḻ', 'Ņ', 'ἥ', 'ḕ', 'ὺ', 'Ȋ', 'ı', 'Ȕ', 'ṧ', 'ᾖ', 'Ί', 'ΐ', '€', 'Ḭ', 'Ƴ', 'ȵ', 'Ṹ', 'Ñ', 'Ƞ', 'Ȩ', 'ῐ', 'ứ', 'έ', 'ł', 'ŭ', '϶', 'ƴ', '₤', 'ƨ', '£', 'Ł', 'ñ', 'ë', 'ễ', 'ǯ', 'ᶕ', 'ή', 'ᶔ', 'Π', 'ȩ', 'ἐ', 'Ể', 'ε', 'Ĩ', 'ǜ', 'Į', 'Ξ', 'Ḹ', 'Ῡ', '∩', 'ú', 'Χ', 'ụ'}
394
395 def removeDnsbl (self,irc,ip,droneblHost,droneblKey):
396 headers = {
397 'Content-Type' : 'text/xml'
398 }
399 def check(answer):
400 found = False
401 for line in answer.split('\n'):
402 if line.find('listed="1"') != -1:
403 id = line.split('id="')[1]
404 id = id.split('"')[0]
405 if line.find('type="18"') != -1:
406 self.logChannel(irc,'RMDNSBL: %s (%s) not removed: is type 18' % (ip,id))
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]
410 continue
411 data = "<?xml version=\"1.0\"?><request key='"+droneblKey+"'><remove id='"+id+"' /></request>"
412 found = True
413 try:
414 r = requests.post(droneblHost,data=data,headers=headers)
415 response = r.text.replace('\n','')
416 if "You are not authorized to remove this incident" in response:
417 self.logChannel(irc,'RMDNSBL: %s (%s) failed: You are not authorized to remove this incident' % (ip,id))
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]
421 else:
422 self.logChannel(irc,'RMDNSBL: %s (%s) removed' % (ip,id))
423 if ip in self.rmrequestors:
424 irc.queueMsg(ircmsgs.privmsg(self.rmrequestors[ip],'%s (%s) removed' % (ip,id)))
425 del self.rmrequestors[ip]
426 except:
427 self.logChannel(irc,'RMDNSBL: %s (%s) failed: unknown error' % (ip,id))
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]
431 if not found:
432 self.logChannel(irc,'RMDNSBL: %s (none) not removed: no listing found' % ip)
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]
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)
440 else:
441 self.logChannel(irc,'RMDNSBL: %s (unknown) failed: status code %s' % (ip,r.status_code))
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)))
444
445 def fillDnsbl (self,irc,ip,droneblHost,droneblKey,comment=None):
446 headers = {
447 'Content-Type' : 'text/xml'
448 }
449 def check(answer):
450 self.log.info ('fillDnsbl, answered %s' % ip)
451 if 'listed="1"' in answer:
452 self.logChannel(irc,'DNSBL: %s (already listed)' % ip)
453 return
454 type = 3
455 if comment == 'Bottler':
456 type = 5
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
483 elif comment == "Abused VPN Service":
484 type = 19
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))
489 if comment:
490 self.logChannel(irc,'DNSBL: %s (%s,type:%s)' % (ip,comment,type))
491 else:
492 self.logChannel(irc,'DNSBL: %s' % ip)
493 self.log.info('fillDnsbl, checking %s' % ip)
494 data = "<?xml version=\"1.0\"?><request key='"+droneblKey+"'><lookup ip='"+ip+"' /></request>"
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))
503
504 def state (self,irc,msg,args,channel):
505 """[<channel>]
506
507 returns state of the plugin, for optional <channel>"""
508 self.cleanup(irc)
509 i = self.getIrc(irc)
510 if not channel:
511 irc.queueMsg(ircmsgs.privmsg(msg.nick,'Opered %s, enable %s, defcon %s, netsplit %s' % (i.opered,self.registryValue('enable'),(i.defcon),i.netsplit)))
512 irc.queueMsg(ircmsgs.privmsg(msg.nick,'There are %s permanent patterns and %s channels directly monitored' % (len(i.patterns),len(i.channels))))
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:
522 if channel == chan:
523 ch = self.getChan(irc,chan)
524 if not self.registryValue('ignoreChannel',channel=chan):
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)))
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:
532 permit = self.registryValue('%sPermit' % protection,channel=chan)
533 life = self.registryValue('%sLife' % protection,channel=chan)
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 = ""
552 irc.queueMsg(ircmsgs.privmsg(msg.nick," - %s : %s/%ss %s%s" % (protection,permit,life,abuse,count)))
553 irc.replySuccess()
554 state = wrap(state,['owner',optional('channel')])
555
556 def defcon (self,irc,msg,args,channel):
557 """[<channel>]
558
559 limits are lowered, globally or for a specific <channel>"""
560 i = self.getIrc(irc)
561 if channel and channel != self.registryValue('logChannel'):
562 if channel in i.channels and self.registryValue('abuseDuration',channel=channel) > 0:
563 chan = self.getChan(irc,channel)
564 if chan.called:
565 self.logChannel(irc,'INFO: [%s] rescheduled ignores lifted, limits lowered (by %s) for %ss' % (channel,msg.nick,self.registryValue('abuseDuration',channel=channel)))
566 chan.called = time.time()
567 else:
568 self.logChannel(irc,'INFO: [%s] ignores lifted, limits lowered (by %s) for %ss' % (channel,msg.nick,self.registryValue('abuseDuration',channel=channel)))
569 chan.called = time.time()
570 else:
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))
577 if not i.god:
578 irc.sendMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick))
579 else:
580 self.applyDefcon (irc)
581 irc.replySuccess()
582 defcon = wrap(defcon,['owner',optional('channel')])
583
584 def vacuum (self,irc,msg,args):
585 """takes no arguments
586
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
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 """
600 if channel in irc.state.channels:
601 reason = conf.supybot.plugins.channel.partMsg.getValue()
602 irc.queueMsg(ircmsgs.part(channel,reason))
603 try:
604 network = conf.supybot.networks.get(irc.network)
605 network.channels().remove(channel)
606 except:
607 pass
608 self.setRegistryValue('lastActionTaken',-1.0,channel=channel)
609 irc.replySuccess()
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)
618 if not channel in irc.state.channels:
619 self.setRegistryValue('lastActionTaken',time.time(),channel=channel)
620 irc.queueMsg(ircmsgs.join(channel))
621 try:
622 network = conf.supybot.networks.get(irc.network)
623 network.channels().add(channel)
624 except KeyError:
625 pass
626 irc.replySuccess()
627 stay = wrap(stay,['owner','channel'])
628
629 def isprotected (self,irc,msg,args,hostmask,channel):
630 """<hostmask> [<channel>]
631
632 returns true if <hostmask> is protected, in optional <channel>"""
633 if ircdb.checkCapability(hostmask, 'protected'):
634 irc.reply('%s is globally protected' % hostmask)
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);
644 isprotected = wrap(isprotected,['owner','hostmask',optional('channel')])
645
646 def checkactions (self,irc,msg,args,duration):
647 """<duration> in days
648
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):
654 if self.registryValue('mainChannel') in channel or channel == self.registryValue('reportChannel') or self.registryValue('snoopChannel') == channel or self.registryValue('secretChannel') == channel:
655 continue
656 if self.registryValue('ignoreChannel',channel):
657 continue
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
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
681 def checkpattern (self,irc,msg,args,text):
682 """ <text>
683
684 returns permanents patterns triggered by <text>"""
685 i = self.getIrc(irc)
686 patterns = []
687 text = text.encode('utf-8').strip()
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):
693 irc.queueMsg(ircmsgs.privmsg(msg.nick,'%s matches: %s' % (len(patterns),', '.join(patterns))))
694 else:
695 irc.reply('No matches')
696 checkpattern = wrap(checkpattern,['owner','text'])
697
698 def lspattern (self,irc,msg,args,optlist,pattern):
699 """[--deep] <id|pattern>
700
701 returns patterns which matches pattern or info about pattern #id, use --deep to search on deactivated patterns, * to return all pattern"""
702 i = self.getIrc(irc)
703 deep = pattern == '*'
704 for (option, arg) in optlist:
705 if option == 'deep':
706 deep = True
707 results = i.ls(self.getDb(irc.network),pattern,deep)
708 if len(results):
709 if deep or pattern == '*':
710 for r in results:
711 irc.queueMsg(ircmsgs.privmsg(msg.nick,r))
712 else:
713 irc.replies(results,None,None,False)
714 else:
715 irc.reply('no pattern found')
716 lspattern = wrap(lspattern,['owner',getopts({'deep': ''}),'text'])
717
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)))
729 irc.replySuccess()
730 rmpattern = wrap(rmpattern,['owner',many('positiveInt')])
731
732 def addpattern (self,irc,msg,args,limit,life,pattern):
733 """<limit> <life> <pattern>
734
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)
738 pattern = pattern.lower()
739 result = i.add(self.getDb(irc.network),msg.prefix,pattern,limit,life,False)
740 self.logChannel(irc,'PATTERN: %s added #%s : "%s" %s/%ss' % (msg.nick,result,pattern,limit,life))
741 irc.reply('#%s added' % result)
742 addpattern = wrap(addpattern,['owner','nonNegativeInt','positiveInt','text'])
743
744 def addregexpattern (self,irc,msg,args,limit,life,pattern):
745 """<limit> <life> /<pattern>/
746
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))
752 irc.reply('#%s added' % result)
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>]
757
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>
773
774 activate or deactivate #<id>"""
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>]
789
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:
799 irc.reply('[%s] no active pattern' % channel)
800 else:
801 irc.reply('[%s] no active pattern' % channel)
802 else:
803 irc.reply('[%s] is unknown' % channel)
804 lstmp = wrap(lstmp,['op'])
805
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')])
823
824 def dnsbl (self,irc,msg,args,ips,comment):
825 """<ip> [,<ip>] [<comment>]
826
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)"""
828 for ip in ips:
829 if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip):
830 t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'),comment))
831 t.setDaemon(True)
832 t.start()
833 irc.replySuccess()
834 dnsbl = wrap(dnsbl,['owner',commalist('ip'),rest('text')])
835
836 def rmdnsbl (self,irc,msg,args,ips):
837 """<ip> [<ip>]
838
839 remove <ips> from dronebl"""
840 for ip in ips:
841 if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip):
842 self.rmrequestors[ip] = msg.nick
843 t = world.SupyThread(target=self.removeDnsbl,name=format('rmDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey')))
844 t.setDaemon(True)
845 t.start()
846 irc.replySuccess()
847 rmdnsbl = wrap(rmdnsbl,['owner',many('ip')])
848
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)
857 shareID = self.registryValue('shareComputedPatternID',channel=channel)
858 if shareID == -1 or not i.defcon:
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
869 l = self.registryValue('computedPatternLife',channel=channel)
870 for channel in i.channels:
871 chan = self.getChan(irc,channel)
872 id = self.registryValue('shareComputedPatternID',channel=channel)
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
881 self.logChannel(irc,'PATTERN: added tmp "%s" for %ss by %s in %s channels' % (text,l,msg.nick,n))
882 irc.replySuccess()
883 else:
884 irc.reply('unknown channel')
885 addtmp = wrap(addtmp,['op','text'])
886
887 def addglobaltmp (self,irc,msg,args,text):
888 """<text>
889
890 add <text> to temporary patterns in all channels"""
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
907 def rmtmp (self,irc,msg,args,channel):
908 """[<channel>]
909
910 remove temporary patterns for given channel"""
911 i = self.getIrc(irc)
912 if channel in i.channels:
913 chan = self.getChan(irc,channel)
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()
922 n = n + 1
923 self.logChannel(irc,'PATTERN: removed tmp patterns in %s channels by %s' % (n,msg.nick))
924 elif chan.patterns:
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
938 def unkline (self,irc,msg,args,nick):
939 """<nick>
940 request unkline of <nick>, klined recently from your channel
941 """
942 channels = []
943 ops = []
944 nick = nick.lower()
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:
950 self.log.info('klines found %s' % q)
951 if q.startswith(nick):
952 ip = q.split(' ')[1]
953 channels.append(channel)
954 if not isCloaked('%s!%s' % (nick,ip),self):
955 if self.registryValue('useOperServ'):
956 irc.sendMsg(ircmsgs.IrcMsg('PRIVMSG OperServ :AKILL DEL %s' % ip))
957 else:
958 irc.queueMsg(ircmsgs.IrcMsg('UNKLINE %s' % ip))
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()
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))
965 else:
966 self.logChannel(irc,'OP: [%s] %s asked for removal of %s (%s)' % (channel,msg.nick,ip,nick))
967 irc.reply(self.registryValue('msgInviteConfirm'))
968 ops.append(channel)
969 if len(ops):
970 if not len(channels):
971 irc.replyError("'%s' does not match any recent bans from %s" % (nick,', '.join(ops)))
972 else:
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)")
974 unkline = wrap(unkline,['private','text'])
975
976
977 def oper (self,irc,msg,args):
978 """takes no arguments
979
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'])
987
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):
998 """<nick!ident@hostmask>
999
1000 returns computed hostmask"""
1001 irc.reply(self.prefixToMask(irc,txt))
1002 checkresolve = wrap(checkresolve,['owner','hostmask'])
1003
1004 # internal stuff
1005
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
1020 def _ip_ranges (self, h):
1021 if '/' in h:
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
1030 if utils.net.isIPV4(h):
1031 prefixes = [27, 26, 25, 24]
1032 elif utils.net.bruteIsIPV6(h):
1033 # noteworthy IPv6 allocation information
1034 # - linode assigns a /128 by default. can also offer /56, /64 & /116
1035 # - xfinity (comcast) has been reported as offering /60
1036 # - hurricane electric tunnel brokers get a /48
1037
1038 prefixes = [120, 118, 116, 114, 112, 110, 64, 60, 56, 48]
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
1047
1048 def resolve (self,irc,prefix,channel='',dnsbl=False,comment=False):
1049 (nick,ident,host) = ircutils.splitHostmask(prefix)
1050 if ident.startswith('~'):
1051 ident = '*'
1052 if prefix in self.cache:
1053 return self.cache[prefix]
1054 try:
1055 resolver = dns.resolver.Resolver()
1056 resolver.timeout = self.registryValue('resolverTimeout')
1057 resolver.lifetime = self.registryValue('resolverTimeout')
1058 L = []
1059 ips = None
1060 try:
1061 ips = resolver.query(host,'AAAA')
1062 except:
1063 ips = None
1064 if ips:
1065 for ip in ips:
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))
1076 #self.log.debug('%s resolved as %s' % (prefix,L))
1077 if len(L) == 1:
1078 h = L[0]
1079 #self.log.debug('%s is resolved as %s@%s' % (prefix,ident,h))
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
1089 self.cache[prefix] = '%s@%s' % (ident,h)
1090 else:
1091 self.cache[prefix] = '%s@%s' % (ident,host)
1092 except:
1093 self.cache[prefix] = '%s@%s' % (ident,host)
1094 i = self.getIrc(irc)
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]
1100 if prefix in i.resolving:
1101 del i.resolving[prefix]
1102
1103 def prefixToMask (self,irc,prefix,channel='',dnsbl=False,comment=None):
1104 if prefix in self.cache:
1105 return self.cache[prefix]
1106 prefix = prefix
1107 (nick,ident,host) = ircutils.splitHostmask(prefix)
1108 if '/' in host:
1109 if host.startswith('gateway/web/freenode'):
1110 if 'ip.' in host:
1111 self.cache[prefix] = '*@%s' % host.split('ip.')[1]
1112 else:
1113 # syn offline / busy
1114 self.cache[prefix] = '%s@gateway/web/freenode/*' % ident
1115 elif host.startswith('gateway/tor-sasl'):
1116 self.cache[prefix] = '*@%s' % host
1117 elif host.startswith('gateway/vpn') or host.startswith('nat/'):
1118 if ident.startswith('~'):
1119 ident = '*'
1120 if '/x-' in host:
1121 host = host.split('/x-')[0] + '/*'
1122 self.cache[prefix] = '%s@%s' % (ident,host)
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:
1129 if '/x-' in host:
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
1141 self.cache[prefix] = '%s@%s' % (ident,h)
1142 else:
1143 if ident.startswith('~'):
1144 ident = '*'
1145 self.cache[prefix] = '%s@%s' % (ident,host)
1146 else:
1147 if ident.startswith('~'):
1148 ident = '*'
1149 if utils.net.isIPV4(host):
1150 self.cache[prefix] = '%s@%s' % (ident,host)
1151 elif utils.net.bruteIsIPV6(host):
1152 self.cache[prefix] = '%s@%s' % (ident,host)
1153 else:
1154 i = self.getIrc(irc)
1155 if self.registryValue('useWhoWas'):
1156 self.cache[prefix] = '%s@%s' % (ident,host)
1157 elif not prefix in i.resolving:
1158 i.resolving[prefix] = True
1159 t = world.SupyThread(target=self.resolve,name=format('resolve %s', prefix),args=(irc,prefix,channel,dnsbl,comment))
1160 t.setDaemon(True)
1161 t.start()
1162 return '%s@%s' % (ident,host)
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)
1169
1170 def do352 (self,irc,msg):
1171 # RPL_WHOREPLY
1172 channel = msg.args[1]
1173 (nick, ident, host) = (msg.args[5], msg.args[2], msg.args[3])
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,'','']
1182
1183 def spam (self,irc,msg,args,channel):
1184 """<channel>
1185
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:
1189 t = time.time() - (self.registryValue('leaveChannelIfNoActivity',channel=channel) * 24 * 3600) + 3600
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
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)
1214 if ircutils.isUserHostmask(hostmask) and self.registryValue('staffCloak') in hostmask:
1215 found = True
1216 break
1217 except:
1218 continue
1219 if not found:
1220 channels.append(channel)
1221 irc.reply('%s channels: %s' %(len(channels),', '.join(channels)))
1222 unstaffed = wrap(unstaffed,['owner'])
1223
1224 def list (self,irc,msg,args):
1225 """
1226
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)
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))
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
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
1247 def do001 (self,irc,msg):
1248 i = self.getIrc(irc)
1249 if not i.opered:
1250 if len(self.registryValue('operatorNick')) and len(self.registryValue('operatorPassword')):
1251 irc.queueMsg(ircmsgs.IrcMsg('OPER %s %s' % (self.registryValue('operatorNick'),self.registryValue('operatorPassword'))))
1252
1253 def do381 (self,irc,msg):
1254 i = self.getIrc(irc)
1255 if not i.opered:
1256 i.opered = True
1257 irc.queueMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick))
1258 irc.queueMsg(ircmsgs.IrcMsg('MODE %s +s +Fbnfl' % irc.nick))
1259 try:
1260 conf.supybot.protocols.irc.throttleTime.setValue(0.0)
1261 except:
1262 t = True
1263
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
1271 if mode == '-o':
1272 i.opered = False
1273 if len(self.registryValue('operatorNick')) and len(self.registryValue('operatorPassword')):
1274 irc.queueMsg(ircmsgs.IrcMsg('OPER %s %s' % (self.registryValue('operatorNick'),self.registryValue('operatorPassword'))))
1275 elif mode == '+p':
1276 i.god = True
1277 self.log.debug('%s is switching to god' % irc.nick)
1278 self.applyDefcon(irc)
1279 elif mode == '-p':
1280 i.god = False
1281 self.log.debug('%s is switching to mortal' % irc.nick)
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],'','']
1294 elif target in irc.state.channels:
1295 modes = ircutils.separateModes(msg.args[1:])
1296 for change in modes:
1297 (mode,value) = change
1298 if mode == '+z':
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)
1306 ip = mask.split('@')[1]
1307 permit = self.registryValue('banPermit')
1308 if permit > -1:
1309 ipranges = self._ip_ranges(ip)
1310 announced = False
1311 for range in ipranges:
1312 range = range
1313 q = self.getIrcQueueFor(irc,'ban-check',range,self.registryValue('banLife'))
1314 q.enqueue(target)
1315 if len(q) > permit:
1316 chs = []
1317 for m in q:
1318 chs.append(m)
1319 q.reset()
1320 if not announced:
1321 announced = True
1322 self.logChannel(irc,"INFO: *@%s is collecting bans (%s/%ss) %s" % (range, permit, self.registryValue('banLife'), ','.join(chs)))
1323 permit = permit + 1
1324
1325 def opStaffers (self,irc):
1326 ops = []
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):
1330 try:
1331 mask = irc.state.nickToHostmask(nick)
1332 if mask and self.registryValue('staffCloak') in mask:
1333 ops.append(nick)
1334 except:
1335 continue
1336 if len(ops):
1337 for i in range(0, len(ops), 4):
1338 irc.sendMsg(ircmsgs.ops(self.registryValue('mainChannel'),ops[i:i+4],irc.prefix))
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')):
1345 irc.queueMsg(ircmsgs.IrcMsg('OPER %s %s' % (self.registryValue('operatorNick'),self.registryValue('operatorPassword'))))
1346 return self._ircs[irc.network]
1347
1348 def doAccount (self,irc,msg):
1349 i = self.getIrc(irc)
1350 if ircutils.isUserHostmask(msg.prefix):
1351 nick = ircutils.nickFromHostmask(msg.prefix)
1352 acc = msg.args[0]
1353 if acc == '*':
1354 acc = None
1355 else:
1356 aa = acc.lower()
1357 for u in i.klinednicks:
1358 if aa == u:
1359 self.logChannel(irc,"SERVICE: %s (%s) lethal account (account-notify)" % (msg.prefix,acc))
1360 src = msg.nick
1361 i.klinednicks.enqueue(aa)
1362 if not src in i.tokline:
1363 i.toklineresults[src] = {}
1364 i.toklineresults[src]['kind'] = 'evade'
1365 i.tokline[src] = src
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)))
1370 break
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]
1380
1381 def getChan (self,irc,channel):
1382 i = self.getIrc(irc)
1383 if not channel in i.channels and irc.isChannel(channel):
1384 i.channels[channel] = Chan(channel)
1385 if not self.starting:
1386 irc.queueMsg(ircmsgs.who(channel))
1387 return i.channels[channel]
1388
1389 def kill (self,irc,nick,reason=None):
1390 i = self.getIrc(irc)
1391 if i.defcon:
1392 i.defcon = time.time()
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
1399 if not reason:
1400 reason = self.registryValue('killMessage')
1401 irc.sendMsg(ircmsgs.IrcMsg('KILL %s :%s' % (nick,reason)))
1402
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]]
1408 (nick,ident,host) = ircutils.splitHostmask(pending[0])
1409 # [prefix,mask,duration,reason,klineMessage]
1410 ident = pending[1].split('@')[0]
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))
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
1425 self.log.info('KLINE %s|%s' % (mask,pending[3]))
1426 if self.registryValue('useOperServ'):
1427 irc.sendMsg(ircmsgs.IrcMsg('PRIVMSG OperServ :AKILL ADD %s !T %s %s | %s' % (mask,pending[2],pending[4],pending[3])))
1428 else:
1429 irc.sendMsg(ircmsgs.IrcMsg('KLINE %s %s :%s|%s' % (pending[2],mask,pending[4],pending[3])))
1430 nickLowered = nick.lower()
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:
1436 if k.startswith(nickLowered):
1437 (at, m) = chan.klines.queue[index]
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))
1440 break
1441 index = index + 1
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:
1450 self.log.info('Ignored kline %s due to no duration', mask)
1451 return
1452 if not klineMessage:
1453 klineMessage = self.registryValue('klineMessage')
1454 if '"' in klineMessage:
1455 klineMessage = self.registryValue('klineMessage')
1456 canKline = True
1457 i.klines[mask] = mask
1458 if "bc.googleusercontent.com" in prefix:
1459 reason = reason + ' !dnsbl Unknown spambot or drone'
1460 if ircutils.isUserHostmask(prefix):
1461 canKline = not self.registryValue('useWhoWas')
1462 if i.defcon or 'gateway/' in prefix:
1463 canKline = True
1464 elif '/' in prefix:
1465 canKline = False
1466 else:
1467 self.log.info('INVALID PREFIX %s : %s : %s' % (prefix,mask,reason))
1468 self.log.info('CANKLINE %s %s %s' % (prefix,mask,canKline))
1469 if canKline:
1470 if not self.registryValue('enable'):
1471 self.logChannel(irc,"INFO: disabled, can't kline %s (%s)" % (mask,reason))
1472 else:
1473 self.log.info('KLINE %s|%s' % (mask,reason))
1474 if self.registryValue('useOperServ'):
1475 irc.sendMsg(ircmsgs.IrcMsg('PRIVMSG OperServ :AKILL ADD %s !T %s %s | %s' % (mask,duration,klineMessage,reason)))
1476 else:
1477 irc.sendMsg(ircmsgs.IrcMsg('KLINE %s %s :%s|%s' % (duration,mask,klineMessage,reason)))
1478 if i.defcon:
1479 i.defcon = time.time()
1480 elif ircutils.isUserHostmask(prefix):
1481 (nick,ident,host) = ircutils.splitHostmask(prefix)
1482 self.log.info('whowas for %s | %s | %s' % (prefix,mask,reason))
1483 if not nick in i.whowas:
1484 i.whowas[nick] = [prefix,mask,duration,reason,klineMessage]
1485 irc.sendMsg(ircmsgs.IrcMsg('WHOWAS %s' % nick))
1486 def forgetKline ():
1487 i = self.getIrc(irc)
1488 if mask in i.klines:
1489 del i.klines[mask]
1490 schedule.addEvent(forgetKline,time.time()+7)
1491
1492 def ban (self,irc,nick,prefix,mask,duration,reason,message,log,killReason=None):
1493 self.kill(irc,nick,killReason)
1494 self.kline(irc,prefix,mask,duration,reason,message)
1495 self.logChannel(irc,log)
1496
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:
1504 i.queues[key][kind].setTimeout(life)
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
1517 def do015 (self,irc,msg):
1518 try:
1519 (targets,text) = msg.args
1520 i = self.getIrc(irc)
1521 reg = r".*-\s+([a-z]+\.freenode\.net)\[.*Users:\s+(\d{2,6})\s+"
1522 result = re.match(reg,text)
1523 # here we store server name and users count, and we will ping the server with the most users
1524 if result:
1525 i.servers[result.group(1)] = int(result.group(2))
1526 except:
1527 pass
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]
1537 server = None
1538 if found:
1539 i.servers = {}
1540 server = '%s' % found
1541 i.servers[server] = time.time()
1542 def bye():
1543 i = self.getIrc(irc)
1544 if server in i.servers:
1545 del i.servers[server]
1546 if not i.netsplit:
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))
1548 i.netsplit = time.time() + self.registryValue('netsplitDuration')
1549 schedule.addEvent(bye,time.time()+self.registryValue('lagPermit'))
1550 irc.queueMsg(ircmsgs.IrcMsg('TIME %s' % server))
1551
1552 def resync (self,irc,msg,args):
1553 """in case of plugin being reloaded
1554 call this to recompute user to ignore (ignoreDuration)"""
1555 for channel in irc.state.channels:
1556 irc.queueMsg(ircmsgs.who(channel))
1557 irc.replySuccess()
1558 resync = wrap(resync,['owner'])
1559
1560 def lethalaccount (self,irc,msg,args,text):
1561 """<accountname> monitor account and kline it on sight
1562 during 24h, via extended-join, account-notify, account's name change"""
1563 i = self.getIrc(irc)
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:
1572 if len(c.nicks[u]) > 4:
1573 if c.nicks[u][4] and c.nicks[u][4].lower() == account:
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'))
1575 irc.replySuccess()
1576 lethalaccount = wrap(lethalaccount,['owner','text'])
1577
1578 def cleanup (self,irc):
1579 i = self.getIrc(irc)
1580 partReason = 'Leaving the channel. /invite %s %s again if needed'
1581 for channel in irc.state.channels:
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'):
1583 if self.registryValue('lastActionTaken',channel=channel) > 1.0 and self.registryValue('leaveChannelIfNoActivity',channel=channel) > -1 and not i.defcon:
1584 if time.time() - self.registryValue('lastActionTaken',channel=channel) > (self.registryValue('leaveChannelIfNoActivity',channel=channel) * 24 * 3600):
1585 irc.queueMsg(ircmsgs.part(channel, partReason % (irc.nick,channel)))
1586 chan = self.getChan(irc,channel)
1587 if chan.requestedBySpam:
1588 self.setRegistryValue('lastActionTaken',self.registryValue('lastActionTaken'),channel=channel)
1589 else:
1590 self.setRegistryValue('lastActionTaken',time.time(),channel=channel)
1591 self.logChannel(irc,'PART: [%s] due to inactivity for %s days' % (channel,self.registryValue('leaveChannelIfNoActivity',channel=channel)))
1592 try:
1593 network = conf.supybot.networks.get(irc.network)
1594 network.channels().remove(channel)
1595 except KeyError:
1596 pass
1597 kinds = []
1598 for kind in i.queues:
1599 count = 0
1600 ks = []
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
1608 else:
1609 count += 1
1610 except:
1611 self.log.error('Exception with %s' % kind)
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]
1619 chs = []
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:
1625 if not n in irc.state.channels[channel].users:
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:
1642 count +=1
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]
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)))
1658
1659 def do391 (self,irc,msg):
1660 i = self.getIrc(irc)
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:
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))
1667 i.netsplit = time.time() + self.registryValue('netsplitDuration')
1668
1669 def do219 (self,irc,msg):
1670 i = self.getIrc(irc)
1671 r = []
1672 for k in i.stats:
1673 if i.stats[k] > self.registryValue('ghostPermit'):
1674 r.append(k.replace('[unknown@','').replace(']',''))
1675 for ip in r:
1676 irc.sendMsg(ircmsgs.IrcMsg('DLINE %s %s on * :%s' % (1440,ip,self.registryValue('msgTooManyGhost'))))
1677 i.stats = {}
1678 if len(r):
1679 self.logChannel(irc,'DOS: %s ip(s) %s' % (len(r),', '.join(r)))
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 = []
1691 i.ilines = {}
1692
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])
1699 email = i.mx[nick][0]
1700 badmail = i.mx[nick][1]
1701 mx = i.mx[nick][2]
1702 freeze = i.mx[nick][3]
1703 del i.mx[nick]
1704 mask = self.prefixToMask(irc,hostmask)
1705 self.logChannel(irc,'SERVICE: %s registered %s with *@%s is in mxbl (%s)' % (hostmask,nick,email,mx))
1706 if badmail and len(email) and len(nick):
1707 if not freeze:
1708 irc.queueMsg(ircmsgs.notice(nick,'Your account has been dropped, please register it again with a valid email address (no disposable temporary email)'))
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)
1715 gecos = msg.args[5]
1716 i.toklineresults[nick]['hostmask'] = hostmask
1717 i.toklineresults[nick]['mask'] = mask
1718 i.toklineresults[nick]['gecos'] = gecos
1719
1720 def do317 (self,irc,msg):
1721 i = self.getIrc(irc)
1722 nick = msg.args[1]
1723 if nick in i.tokline:
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]
1739 if nick in i.toklineresults:
1740 if i.toklineresults[nick]['kind'] == 'evade':
1741 uid = random.randint(0,1000000)
1742 irc.sendMsg(ircmsgs.IrcMsg('KLINE %s %s :%s|%s' % (self.registryValue('klineDuration'),i.toklineresults[nick]['mask'],self.registryValue('klineMessage'),'%s - kline evasion' % (uid))))
1743 self.logChannel(irc,'BAD: [%s] %s (kline evasion)' % (i.toklineresults[nick]['hostmask'],uid))
1744 del i.tokline[nick]
1745 del i.toklineresults[nick]
1746
1747 def doInvite(self, irc, msg):
1748 channel = msg.args[1]
1749 i = self.getIrc(irc)
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')))
1751 if channel and not channel in irc.state.channels and not ircdb.checkIgnored(msg.prefix):
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:
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
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)))
1774 else:
1775 self.logChannel(irc,'INVITE: [%s] %s is asking for %s' % (channel,msg.prefix,irc.nick))
1776 irc.queueMsg(ircmsgs.privmsg(msg.nick,'The invitation to %s will be reviewed by staff' % channel))
1777
1778 def do322 (self,irc,msg):
1779 i = self.getIrc(irc)
1780 if msg.args[1] in i.invites:
1781 if int(msg.args[2]) >= self.registryValue('minimumUsersInChannel'):
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]))
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)))
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]])
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)))
1795 del i.invites[msg.args[1]]
1796
1797 def resolveSnoopy (self,irc,account,email,badmail,freeze):
1798 resolver = dns.resolver.Resolver()
1799 resolver.timeout = 10
1800 resolver.lifetime = 10
1801 found = None
1802
1803 mxbl = set(self.registryValue('mxbl'))
1804 i = self.getIrc(irc)
1805 if email.lower() in mxbl:
1806 found = email
1807 else:
1808 to_resolve = [(email,'MX'), (email,'A'), (email,'AAAA')]
1809 while to_resolve:
1810 domain, type = to_resolve.pop(0)
1811 try:
1812 res = resolver.query(domain, type)
1813 except:
1814 pass
1815 else:
1816 for record in res:
1817 record = record.to_text()
1818
1819 if type == 'MX':
1820 record = record.split(" ", 1)[1].lower()
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'))
1825
1826 if record in mxbl:
1827 found = record
1828 break
1829 if found is not None:
1830 break
1831
1832 if found is not None:
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:
1839 irc.queueMsg(ircmsgs.IrcMsg('PRIVMSG NickServ :FREEZE %s ON changed email to (%s which is in mxbl %s)' % (account,email,found)))
1840 irc.queueMsg(ircmsgs.IrcMsg('WHOIS %s' % account))
1841 else:
1842 i.cleandomains[email] = True
1843
1844 def handleSnoopMessage (self,irc,msg):
1845 (targets, text) = msg.args
1846 text = text.replace('\x02','')
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()
1855 account = account.lower().strip()
1856 q = self.getIrcQueueFor(irc,account,'nsregister',600)
1857 q.enqueue(email)
1858 if msg.nick == 'NickServ':
1859 src = text.split(' ')[0].lower().strip()
1860 target = ''
1861 registering = True
1862 grouping = False
1863 if ' GROUP:' in text:
1864 grouping = True
1865 target = text.split('(')[1].split(')')[0]
1866 elif 'SET:ACCOUNTNAME:' in text:
1867 grouping = True
1868 t = text.split('(')
1869 if len(t) > 1:
1870 target = text.split('(')[1].split(')')[0]
1871 else:
1872 return
1873 elif 'UNGROUP: ' in text:
1874 grouping = True
1875 target = text.split('UNGROUP: ')[1]
1876 if len(target) and grouping:
1877 q = self.getIrcQueueFor(irc,src,'nsAccountGroup',120)
1878 q.enqueue(text)
1879 if len(q) == 3:
1880 index = 0
1881 a = b = c = False
1882 oldAccount = None
1883 for m in q:
1884 if ' GROUP:' in m and index == 0:
1885 a = True
1886 elif ' SET:ACCOUNTNAME:' in m and index == 1:
1887 oldAccount = m.split(' ')[1].replace('(','').replace('(','')
1888 b = True
1889 elif ' UNGROUP:' in m and index == 2:
1890 c = True
1891 index = index + 1
1892 q.reset()
1893 if a and b and c:
1894 self.logChannel(irc,"SERVICE: %s suspicious evades/abuses with GROUP/ACCOUNTNAME/UNGROUP (was %s)" % (src,oldAccount))
1895 i = self.getIrc(irc)
1896 oldAccount = oldAccount.lower().strip()
1897 for u in i.klinednicks:
1898 if u == oldAccount:
1899 self.logChannel(irc,"SERVICE: %s lethaled (%s), enforcing" % (src,oldAccount))
1900 i.klinednicks.enqueue(src)
1901 if not src in i.tokline:
1902 i.toklineresults[src] = {}
1903 i.toklineresults[src]['kind'] = 'evade'
1904 i.tokline[src] = src
1905 def f ():
1906 irc.sendMsg(ircmsgs.IrcMsg('WHOIS %s %s' % (src,src)))
1907 schedule.addEvent(f,time.time()+random.randint(0,7))
1908 break
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
1916
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:
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
1929 def handleMsg (self,irc,msg,isNotice):
1930 if not ircutils.isUserHostmask(msg.prefix):
1931 return
1932 if msg.prefix == irc.prefix:
1933 return
1934 (targets, t) = msg.args
1935 if ircmsgs.isAction(msg):
1936 text = ircmsgs.unAction(msg)
1937 else:
1938 text = t
1939 try:
1940 raw = ircutils.stripFormatting(text)
1941 except:
1942 raw = text
1943 text = raw.lower()
1944 mask = self.prefixToMask(irc,msg.prefix)
1945 i = self.getIrc(irc)
1946 if not i.ping or time.time() - i.ping > self.registryValue('lagInterval'):
1947 i.ping = time.time()
1948 self.cleanup(irc)
1949 if self.registryValue('lagPermit') > -1:
1950 i.stats = {}
1951 if self.registryValue('ghostPermit') > -1:
1952 irc.queueMsg(ircmsgs.IrcMsg('STATS L'))
1953 irc.queueMsg(ircmsgs.IrcMsg('MAP'))
1954 if i.defcon:
1955 if time.time() > i.defcon + self.registryValue('defcon'):
1956 i.lastDefcon = time.time()
1957 i.defcon = False
1958 self.logChannel(irc,"INFO: triggers restored to normal behaviour")
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:
1962 irc.queueMsg(ircmsgs.IrcMsg('MODE %s q' % channel))
1963 if i.netsplit:
1964 if time.time() > i.netsplit:
1965 i.netsplit = False
1966 self.logChannel(irc,"INFO: netsplit mode deactivated")
1967 if mask in i.klines:
1968 self.log.debug('Ignoring %s (%s) - kline in progress', msg.prefix,mask)
1969 return
1970 isBanned = False
1971 for channel in targets.split(','):
1972 if channel.startswith('@'):
1973 channel = channel.replace('@','',1)
1974 if channel.startswith('+'):
1975 channel = channel.replace('+','',1)
1976 if irc.isChannel(channel) and channel in irc.state.channels:
1977 if self.registryValue('reportChannel') == channel:
1978 self.handleReportMessage(irc,msg)
1979 if self.registryValue('snoopChannel') == channel:
1980 self.handleSnoopMessage(irc,msg)
1981 if self.registryValue('secretChannel') == channel:
1982 self.handleSecretMessage(irc,msg)
1983 if self.registryValue('ignoreChannel',channel):
1984 continue
1985 if ircdb.checkCapability(msg.prefix, 'protected'):
1986 if msg.nick in list(irc.state.channels[channel].ops) and irc.nick in text:
1987 self.logChannel(irc,'OP: [%s] <%s> %s' % (channel,msg.nick,text))
1988 continue
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
1993 if not i.defcon:
1994 self.logChannel(irc,'INFO: [%s] returns to regular state' % channel)
1995 if irc.isChannel(channel) and self.registryValue('defconMode',channel=channel) and not i.defcon:
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:
1997 irc.queueMsg(ircmsgs.IrcMsg('MODE %s q' % channel))
1998 if isBanned:
1999 continue
2000 if msg.nick in list(irc.state.channels[channel].ops):
2001 if irc.nick in raw:
2002 self.logChannel(irc,'OP: [%s] <%s> %s' % (channel,msg.nick,text))
2003 continue
2004 if self.registryValue('ignoreVoicedUser',channel=channel):
2005 if msg.nick in list(irc.state.channels[channel].voices):
2006 continue
2007 protected = ircdb.makeChannelCapability(channel, 'protected')
2008 if ircdb.checkCapability(msg.prefix, protected):
2009 continue
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
2014 killReason = self.registryValue('killMessage',channel=channel)
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
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:
2025 isBanned = True
2026 uid = random.randint(0,1000000)
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)
2029 self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),reason,self.registryValue('klineMessage'),log,killReason)
2030 i.count(self.getDb(irc.network),pattern.uid)
2031 chan.klines.enqueue('%s %s' % (msg.nick.lower(),mask))
2032 self.isAbuseOnChannel(irc,channel,'pattern',mask)
2033 self.setRegistryValue('lastActionTaken',time.time(),channel=channel)
2034 break
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)
2051 if isBanned:
2052 continue
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()
2063 if isBanned:
2064 continue
2065 ignoreDuration = self.registryValue('ignoreDuration',channel=channel)
2066 if not msg.nick in chan.nicks:
2067 t = time.time()
2068 if isCloaked(msg.prefix,self):
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:
2075 isIgnored = True
2076 reason = ''
2077 publicreason = ''
2078 if self.registryValue('joinSpamPartPermit',channel=channel) > -1:
2079 kind = 'joinSpamPart'
2080 life = self.registryValue('joinSpamPartLife',channel=channel)
2081 key = mask
2082 isNew = False
2083 if not kind in chan.buffers:
2084 chan.buffers[kind] = {}
2085 if not key in chan.buffers[kind]:
2086 isNew = True
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)
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'
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
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:
2110 publicreason = 'nicks/hilight spam'
2111 reason = hilight
2112 if chan.patterns and not len(reason):
2113 for pattern in chan.patterns:
2114 if pattern in text:
2115 isIgnored = False
2116 reason = 'matches tmp pattern in %s' % channel
2117 publicreason = 'your sentence matches temporary blacklisted words'
2118 chan.patterns.enqueue(pattern)
2119 self.isAbuseOnChannel(irc,channel,'pattern',mask)
2120 break
2121 massrepeat = False
2122 flag = ircdb.makeChannelCapability(channel, 'massRepeat')
2123 if ircdb.checkCapability(msg.prefix, flag):
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')
2129 if ircdb.checkCapability(msg.prefix, flag):
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')
2135 if ircdb.checkCapability(msg.prefix, flag):
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')
2141 if ircdb.checkCapability(msg.prefix, flag):
2142 lowrepeat = self.isChannelLowRepeat(irc,msg,channel,mask,text)
2143 if lowrepeat and self.hasAbuseOnChannel(irc,channel,'lowRepeat'):
2144 isIgnored = False
2145 lowhilight = False
2146 flag = ircdb.makeChannelCapability(channel, 'lowHilight')
2147 if ircdb.checkCapability(msg.prefix, flag):
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')
2153 if ircdb.checkCapability(msg.prefix, flag):
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')
2159 if ircdb.checkCapability(msg.prefix, flag):
2160 lowflood = self.isChannelLowFlood(irc,msg,channel,mask,text)
2161 if lowflood and self.hasAbuseOnChannel(irc,channel,'lowFlood'):
2162 isIgnored = False
2163 ctcp = False
2164 flag = ircdb.makeChannelCapability(channel, 'ctcp')
2165 if ircdb.checkCapability(msg.prefix, flag):
2166 if not ircmsgs.isAction(msg) and ircmsgs.isCtcp(msg):
2167 ctcp = self.isChannelCtcp(irc,msg,channel,mask,text)
2168 if ctcp and self.hasAbuseOnChannel(irc,channel,'ctcp'):
2169 isIgnored = False
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
2177 cap = False
2178 flag = ircdb.makeChannelCapability(channel, 'cap')
2179 if ircdb.checkCapability(msg.prefix, flag):
2180 cap = self.isChannelCap(irc,msg,channel,mask,raw)
2181 if cap and self.hasAbuseOnChannel(irc,channel,'cap'):
2182 isIgnored = False
2183 if not reason:
2184 if massrepeat:
2185 reason = massrepeat
2186 publicreason = 'repetition detected'
2187 elif lowmassrepeat:
2188 reason = lowmassrepeat
2189 publicreason = 'repetition detected'
2190 elif repeat:
2191 reason = repeat
2192 publicreason = 'repetition detected'
2193 elif lowrepeat:
2194 reason = lowrepeat
2195 publicreason = 'repetition detected'
2196 elif hilight:
2197 reason = hilight
2198 publicreason = 'nicks/hilight spam'
2199 elif lowhilight:
2200 reason = lowhilight
2201 publicreason = 'nicks/hilight spam'
2202 elif cap:
2203 reason = cap
2204 publicreason = 'uppercase detected'
2205 elif flood:
2206 reason = flood
2207 publicreason = 'flood detected'
2208 elif lowflood:
2209 reason = lowflood
2210 publicreason = 'flood detected'
2211 elif ctcp:
2212 reason = ctcp
2213 publicreason = 'channel CTCP'
2214 elif notice:
2215 reason = notice
2216 publicreason = 'channel notice'
2217 if reason:
2218 if isIgnored:
2219 if self.warnedOnOtherChannel(irc,channel,mask):
2220 isIgnored = False
2221 elif self.isBadOnChannel(irc,channel,'bypassIgnore',mask):
2222 isIgnored = False
2223 if chan.called:
2224 isIgnored = False
2225 if isIgnored:
2226 bypassIgnore = self.isBadOnChannel(irc,channel,'bypassIgnore',mask)
2227 if bypassIgnore:
2228 isBanned = True
2229 uid = random.randint(0,1000000)
2230 reason = '%s %s' % (reason,bypassIgnore)
2231 log = 'BAD: [%s] %s (%s - %s)' % (channel,msg.prefix,reason,uid)
2232 chan.klines.enqueue('%s %s' % (msg.nick.lower(),mask))
2233 reason = '%s - %s' % (uid,reason)
2234 self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),reason,self.registryValue('klineMessage'),log,killReason)
2235 self.setRegistryValue('lastActionTaken',time.time(),channel=channel)
2236 if i.defcon:
2237 i.defcon = time.time()
2238 else:
2239 q = self.getIrcQueueFor(irc,mask,'warned-%s' % channel,self.registryValue('alertPeriod'))
2240 if len(q) == 0:
2241 q.enqueue(text)
2242 self.logChannel(irc,'IGNORED: [%s] %s (%s)' % (channel,msg.prefix,reason))
2243 matter = None
2244 if msg.nick:
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)))
2246 else:
2247 isBanned = True
2248 uid = random.randint(0,1000000)
2249 log = 'BAD: [%s] %s (%s - %s)' % (channel,msg.prefix,reason,uid)
2250 chan.klines.enqueue('%s %s' % (msg.nick.lower(),mask))
2251 reason = '%s - %s' % (uid,reason)
2252 self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),reason,self.registryValue('klineMessage'),log,killReason)
2253 if i.defcon:
2254 i.defcon = time.time()
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))
2259 i.defcon = time.time() + (self.registryValue('defcon')*2)
2260 if not i.god:
2261 irc.sendMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick))
2262 else:
2263 self.applyDefcon(irc)
2264 ip = mask.split('@')[1]
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()
2271 self.setRegistryValue('lastActionTaken',time.time(),channel=channel)
2272
2273 if not isBanned:
2274 mini = self.registryValue('amsgMinimum')
2275 if len(text) > mini or text.find('http') != -1:
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
2306 q = self.getIrcQueueFor(irc,key,'amsg',self.registryValue('alertPeriod'))
2307 if len(q) == 0:
2308 q.enqueue(mask)
2309 chs.append(channel)
2310 self.logChannel(irc,'AMSG: %s (%s) in %s' % (msg.nick,text,', '.join(chs)))
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())
2319
2320 def handleSecretMessage (self,irc,msg):
2321 (targets, text) = msg.args
2322 nicks = ['OperServ','NickServ']
2323 i = self.getIrc(irc)
2324 if msg.nick in nicks:
2325 if text.startswith('klinechan_check_join(): klining '):
2326 patterns = self.registryValue('droneblPatterns')
2327 found = False
2328 if len(patterns):
2329 for pattern in patterns:
2330 if len(pattern) and pattern in text:
2331 found = pattern
2332 break
2333 if found:
2334 a = text.split('klinechan_check_join(): klining ')[1].split(' ')
2335 a = a[0]
2336 ip = a.split('@')[1]
2337 if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip):
2338 if len(self.registryValue('droneblKey')) and len(self.registryValue('droneblHost')) and self.registryValue('enable'):
2339 t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'),found))
2340 t.setDaemon(True)
2341 t.start()
2342 else:
2343 self.prefixToMask(irc,'*!*@%s' % ip,'',True)
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)
2348 email = text.split('<')[1].split('>')[0]
2349 h = text.split('email for ')[1].split(']')[0].strip().replace('[','!')
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'))
2354 q.enqueue(email)
2355 if len(q) > self.registryValue('registerPermit'):
2356 ms = []
2357 for m in q:
2358 ms.append(m)
2359 if i.defcon:
2360 uid = random.randint(0,1000000)
2361 m = self.prefixToMask(irc,h)
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)))
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)))
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()
2385 self.logChannel(irc,'SERVICE: [%s] %s suspicious AKICK behaviour' % (target,origin))
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()
2392
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:
2412 queue.enqueue(target)
2413 if len(queue) > permit:
2414 queue.reset()
2415 if not i.defcon:
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')))
2417 i.defcon = time.time()
2418 if not i.god:
2419 irc.sendMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick))
2420 else:
2421 self.applyDefcon (irc)
2422 i.defcon = time.time()
2423 else:
2424 if i.netsplit and text.startswith('Join rate in '):
2425 i.netsplit = time.time() + self.registryValue('netsplitDuration')
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:
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))
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:
2449 found = pattern
2450 break
2451 if found:
2452 a = text.split('Killing client ')[1]
2453 a = a.split(')')[0]
2454 ip = a.split('@')[1]
2455 if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip):
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)
2462
2463 def doPrivmsg (self,irc,msg):
2464 self.handleMsg(irc,msg,False)
2465 try:
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(','):
2473 if channel.startswith('@'):
2474 channel = channel.replace('@','',1)
2475 if channel.startswith('+'):
2476 channel = channel.replace('+','',1)
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:
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)
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:
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)
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
2500 i.count(self.getDb(irc.network),pattern.uid)
2501 except:
2502 return
2503
2504 def doTopic(self, irc, msg):
2505 self.handleMsg(irc,msg,False)
2506
2507 def do903 (self,irc,msg):
2508 irc.queueMsg(ircmsgs.IrcMsg('CAP REQ :extended-join account-notify'))
2509
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()
2515 ar.pop()
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):
2524 limit = self.registryValue('channelFloodPermit')
2525 life = self.registryValue('channelFloodLife')
2526 key = 'snoteFloodAlerted'
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)
2532 if i.defcon:
2533 if limit > 0:
2534 limit = limit - 1
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)
2542 users = list(queue)
2543 if len(queue) > limit:
2544 self.logChannel(irc,'NOTE: [%s] is flooded by %s' % (target,', '.join(users)))
2545 queue.reset()
2546 queue = self.getIrcQueueFor(irc,target,'snoteFloodJoin',life)
2547 queue.enqueue(text)
2548 if len(queue) > 1 or i.defcon:
2549 if self.registryValue('lastActionTaken',channel=target) > 0.0 and not target in irc.state.channels:
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))
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()
2566 else:
2567 limit = self.registryValue('userFloodPermit')
2568 life = self.registryValue('userFloodLife')
2569 if limit > -1:
2570 if target.startswith('freenode-connect'):
2571 return
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)
2580 users = list(queue)
2581 if len(queue) > limit:
2582 queue.reset()
2583 queue = self.getIrcQueueFor(irc,target,'snoteFloodLethal',life)
2584 queue.enqueue(','.join(users))
2585 if i.defcon or len(queue) > 1:
2586 for m in queue:
2587 for q in m.split(','):
2588 if not (ircdb.checkCapability(q, 'protected') or target == 'freenode-connect'):
2589 mask = self.prefixToMask(irc,q)
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))
2593 else:
2594 self.logChannel(irc,'NOTE: %s is flooded by %s' % (target,', '.join(users)))
2595 if ircdb.checkCapability(user, 'protected'):
2596 return
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)
2605 if len(queue)> limit:
2606 targets = list(queue)
2607 queue.reset()
2608 queue = self.getIrcQueueFor(irc,user,'snoteFloodLethal',life)
2609 queue.enqueue(target)
2610 if i.defcon or len(queue) > 1:
2611 mask = self.prefixToMask(irc,user)
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))
2615 else:
2616 self.logChannel(irc,'NOTE: %s is flooding %s' % (user,', '.join(targets)))
2617
2618 def handleJoinSnote (self,irc,text):
2619 limit = self.registryValue('joinRatePermit')
2620 life = self.registryValue('joinRateLife')
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):
2627 return
2628 mask = self.prefixToMask(irc,user)
2629 if ircdb.checkCapability(user, 'protected'):
2630 return
2631 protected = ircdb.makeChannelCapability(target, 'protected')
2632 if ircdb.checkCapability(user, protected):
2633 return
2634 queue = self.getIrcQueueFor(irc,user,'snoteJoin',life)
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'
2644 if len(queue) > limit and limit > 0:
2645 users = list(queue)
2646 queue.reset()
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))
2651 life = self.registryValue('crawlLife')
2652 limit = self.registryValue('crawlPermit')
2653 if limit < 0:
2654 return
2655 queue = self.getIrcQueueFor(irc,mask,'snoteJoin',life)
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)
2663 if '1wm' in user:
2664 limit = 1
2665 if len(queue) > limit:
2666 channels = list(queue)
2667 queue.reset()
2668 queue = self.getIrcQueueFor(irc,mask,'snoteJoinLethal',self.registryValue('alertPeriod'))
2669 if len(queue) == 0:
2670 self.logChannel(irc,'NOTE: %s is indexing the network (%s)' % (user,', '.join(channels)))
2671 queue.enqueue(mask)
2672 else:
2673 self.kline(irc,user,mask,self.registryValue('klineDuration'),'crawling')
2674
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
2680 if user.split('!')[0].lower() == target.lower():
2681 return
2682 limit = self.registryValue('idPermit')
2683 life = self.registryValue('idLife')
2684 if limit < 0:
2685 return
2686 queue = self.getIrcQueueFor(irc,user,'snoteId',life)
2687 queue.enqueue(target)
2688 i = self.getIrc(irc)
2689 targets = []
2690 key = 'snoteIdAlerted'
2691 if len(queue) > limit:
2692 targets = list(queue)
2693 queue.reset()
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()
2701 schedule.addEvent(rcu,time.time()+self.registryValue('abuseLife'))
2702 if key in i.queues[user]:
2703 if len(queue):
2704 targets = list(queue)
2705 queue.reset()
2706 a = []
2707 for t in targets:
2708 if not t in a:
2709 a.append(t)
2710 mask = self.prefixToMask(irc,user)
2711 (nick,ident,host) = ircutils.splitHostmask(user)
2712 if not mask in i.klines:
2713 uid = random.randint(0,1000000)
2714 privateReason = '%s - ns id flood (%s)' % (uid,', '.join(a))
2715 if i.defcon:
2716 privateReason = '!dnsbl ' + privateReason
2717 self.kline(irc,user,mask,self.registryValue('klineDuration'), privateReason)
2718 self.logChannel(irc,"BAD: %s (%s)" % (user,privateReason))
2719 queue = self.getIrcQueueFor(irc,target,'snoteId',life)
2720 queue.enqueue(user)
2721 targets = []
2722 if len(queue) > limit:
2723 targets = list(queue)
2724 queue.reset()
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()
2731 schedule.addEvent(rct,time.time()+self.registryValue('abuseLife'))
2732 if key in i.queues[target]:
2733 if len(queue):
2734 targets = list(queue)
2735 queue.reset()
2736 a = {}
2737 for t in targets:
2738 if not t in a:
2739 a[t] = t
2740 for u in a:
2741 mask = self.prefixToMask(irc,u)
2742 (nick,ident,host) = ircutils.splitHostmask(u)
2743 if not mask in i.klines:
2744 self.kill(irc,nick,self.registryValue('killMessage'))
2745 uid = random.randint(0,1000000)
2746 privateReason = '%s - ns id flood on %s' % (uid,target)
2747 if i.defcon:
2748 privateReason = '!dnsbl ' + privateReason
2749 self.kline(irc,u,mask,self.registryValue('klineDuration'), privateReason)
2750 self.logChannel(irc,"BAD: %s (%s)" % (u,privateReason))
2751
2752 def handleKline(self,irc,text):
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)
2766 permit = self.registryValue('alertOnWideKline')
2767 found = ''
2768 if not i.lastKlineOper.find('freenode/staff/') == -1:
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:
2774 if chan.nicks[nick][4] and chan.nicks[nick][1] == user:
2775 found = chan.nicks[nick][4]
2776 break
2777 if found:
2778 self.log.info ('Account klined %s --> %s' % (found,user))
2779 if permit > -1:
2780 if '/' in host:
2781 if host.startswith('gateway/') or host.startswith('nat/'):
2782 h = host.split('/')
2783 h[-1] = '*'
2784 host = '/'.join(h)
2785 ranges = self._ip_ranges(host)
2786 announced = False
2787 for range in ranges:
2788 range = range
2789 queue = self.getIrcQueueFor(irc,range,'klineNote',7)
2790 queue.enqueue(user)
2791 if len(queue) == permit:
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')))
2795
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]
2803 limit = self.registryValue('nickChangePermit')
2804 life = self.registryValue('nickChangeLife')
2805 if limit < 0:
2806 return
2807 mask = self.prefixToMask(irc,'%s!%s' % (nick,host))
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()
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))
2819
2820 def handleChannelCreation (self,irc,text):
2821 text = text.replace(' is creating new channel ','')
2822 permit = self.registryValue('channelCreationPermit')
2823 user = text.split('#')[0]
2824 channel = '#' + text.split('#')[1]
2825 if '##' in text:
2826 channel = '##' + text.split('##')[1]
2827 i = self.getIrc(irc)
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:
2831 i.toklineresults[user] = {}
2832 i.toklineresults[user]['kind'] = 'lethal'
2833 i.tokline[user] = text
2834 self.log.info('WHOIS %s (%s)' % (user,channel))
2835 irc.sendMsg(ircmsgs.IrcMsg('WHOIS %s %s' % (user,user)))
2836 break
2837
2838 def handleClient (self,irc,text):
2839 i = self.getIrc(irc)
2840
2841 #if i.defcon:
2842
2843
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 -- ','')
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)
2859 if text.startswith('Possible Flooder '):
2860 self.handleFloodSnote(irc,text)
2861 #elif text.find('is creating new channel') != -1:
2862 # self.handleChannelCreation(irc,text)
2863 elif text.startswith('Nick change: From'):
2864 self.handleNickSnote(irc,text)
2865 elif text.startswith('User') and text.endswith('is a possible spambot'):
2866 self.handleJoinSnote(irc,text)
2867 elif 'failed login attempts to' in text and not 'SASL' in text:
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])
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')
2877 elif text.startswith('KLINE active') or text.startswith('K/DLINE active'):
2878 self.handleKline(irc,text)
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'))
2891 elif 'K-Line for [*@' in text:
2892 oper = text.split(' ')[0]
2893 i = self.getIrc(irc)
2894 i.lastKlineOper = oper
2895 reason = text.split('K-Line for [*@')[1]
2896 reason = reason.split(']')[1].replace('[','').replace(']','')
2897 hasPattern = False
2898 for p in self.registryValue('droneblPatterns'):
2899 if p in reason:
2900 hasPattern = p
2901 break
2902 ip = text.split('K-Line for [*@')[1].split(']')[0]
2903 permit = self.registryValue('ipv4AbusePermit')
2904 if not 'evilmquin' in oper and permit > -1:
2905 ranges = self._ip_ranges(ip)
2906 for range in ranges:
2907 range = range
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)
2914 q.reset()
2915 uid = random.randint(0,1000000)
2916 if self.registryValue('useOperServ'):
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')))))
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)))
2921 permit = permit + 1
2922 if '!dnsbl' in text or hasPattern:
2923 reason = ''
2924 if '!dnsbl' in text:
2925 reason = text.split('!dnsbl')[1].replace(']','').strip()
2926 else:
2927 reason = hasPattern
2928 if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip):
2929 if len(self.registryValue('droneblKey')) and len(self.registryValue('droneblHost')) and self.registryValue('enable'):
2930 t = world.SupyThread(target=self.fillDnsbl,name=format('fillDnsbl %s', ip),args=(irc,ip,self.registryValue('droneblHost'),self.registryValue('droneblKey'),reason))
2931 t.setDaemon(True)
2932 t.start()
2933 else:
2934 if len(self.registryValue('droneblKey')) and len(self.registryValue('droneblHost')) and self.registryValue('enable'):
2935 t = world.SupyThread(target=self.resolve,name=format('resolve %s', '*!*@%s' % ip),args=(irc,'*!*@%s' % ip,'',True, reason))
2936 t.setDaemon(True)
2937 t.start()
2938 else:
2939 self.prefixToMask(irc,'*!*@%s' % ip,'',True,reason)
2940 elif 'failed login attempts to' in text and 'SASL' in text:
2941 self.handleSaslFailure(irc,text)
2942 elif text.startswith('FILTER'):
2943 ip = text.split(' ')[2].split('[')[1].split(']')[0]
2944 if utils.net.isIPV4(ip) or utils.net.bruteIsIPV6(ip):
2945 if not ip in self.ipfiltered:
2946 if self.registryValue('serverFilteringPermit') > -1:
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)
2955 t.start()
2956 else:
2957 self.handleMsg(irc,msg,True)
2958
2959 def do215 (self,irc,msg):
2960 i = self.getIrc(irc)
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]))
2969
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]
2978 q = self.getIrcQueueFor(irc,'sasl',account,life)
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'):
2991 if len(hosts) > 0:
2992 for h in hosts:
2993 if len(i.dlines):
2994 i.dlines.append(h)
2995 else:
2996 i.dlines.append(h)
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))
3007
3008 def warnedOnOtherChannel (self,irc,channel,mask):
3009 for chan in list(irc.state.channels):
3010 if chan != channel:
3011 if self.hasAbuseOnChannel(irc,chan,mask):
3012 return True
3013 return False
3014
3015 def hasAbuseOnChannel (self,irc,channel,key):
3016 chan = self.getChan(irc,channel)
3017 kind = 'abuse'
3018 limit = self.registryValue('%sPermit' % kind,channel=channel)
3019 if kind in chan.buffers:
3020 if key in chan.buffers[kind]:
3021 if len(chan.buffers[kind][key]) > limit:
3022 return True
3023 return False
3024
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]:
3040 if mask == m:
3041 found = True
3042 break
3043 if not found:
3044 chan.buffers[kind][key].enqueue(mask)
3045 i = self.getIrc(irc)
3046 if i.defcon:
3047 limit = limit - 1
3048 if limit < 0:
3049 limit = 0
3050 if len(chan.buffers[kind][key]) > limit:
3051 self.log.debug('abuse in %s : %s : %s/%s' % (channel,key,len(chan.buffers[kind][key]),limit))
3052 # chan.buffers[kind][key].reset()
3053 # queue not reseted, that way during life, it returns True
3054 if not chan.called:
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)))
3057 if not i.defcon:
3058 i.defcon = time.time()
3059 if not i.god:
3060 irc.sendMsg(ircmsgs.IrcMsg('MODE %s +p' % irc.nick))
3061 else:
3062 self.applyDefcon(irc)
3063 chan.called = time.time()
3064 return True
3065 return False
3066
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:
3074 kinds = ['flood','lowFlood','nick','lowRepeat','lowMassRepeat','broken']
3075 if kind in kinds:
3076 return False
3077 life = self.registryValue('%sLife' % kind,channel=channel)
3078 if limit == 0:
3079 return '%s %s/%ss in %s' % (kind,limit,life,channel)
3080 if not kind in chan.buffers:
3081 chan.buffers[kind] = {}
3082 newUser = False
3083 if not key in chan.buffers[kind]:
3084 newUser = True
3085 chan.buffers[kind][key] = utils.structures.TimeoutQueue(life)
3086 chan.buffers[kind]['%s-creation' % key] = time.time()
3087 elif chan.buffers[kind][key].timeout != life:
3088 chan.buffers[kind][key].setTimeout(life)
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
3093 chan.buffers[kind][key].enqueue(key)
3094 if newUser or i.defcon or self.hasAbuseOnChannel(irc,channel,kind) or chan.called:
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()
3100 if not kind == 'broken':
3101 self.isAbuseOnChannel(irc,channel,kind,key)
3102 return '%s %s/%ss in %s' % (kind,limit,life,channel)
3103 return False
3104
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
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
3117 def isChannelCtcp (self,irc,msg,channel,mask,text):
3118 return self.isBadOnChannel(irc,channel,'ctcp',mask)
3119
3120 def isChannelNotice (self,irc,msg,channel,mask,text):
3121 return self.isBadOnChannel(irc,channel,'notice',mask)
3122
3123 def isChannelLowFlood (self,irc,msg,channel,mask,text):
3124 return self.isBadOnChannel(irc,channel,'lowFlood',mask)
3125
3126 def isChannelCap (self,irc,msg,channel,mask,text):
3127 text = text.replace(' ','')
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)
3134 #self.log.info ('%s : %s : %s :%s' % (mask,channel,text,len(matchs)))
3135 if len(matchs) and len(text):
3136 percent = (len(matchs)*100) / (len(text) * 1.0)
3137 #self.log.info ('%s: %s/%s %s' % (mask,percent,trigger,text))
3138 if percent >= trigger:
3139 return self.isBadOnChannel(irc,channel,'cap',mask)
3140 return False
3141
3142 def isChannelFlood (self,irc,msg,channel,mask,text):
3143 if len(text) == 0 or len(text) >= self.registryValue('floodMinimum',channel=channel) or text.isdigit():
3144 return self.isBadOnChannel(irc,channel,'flood',mask)
3145 return False
3146
3147 def isChannelHilight (self,irc,msg,channel,mask,text):
3148 return self.isHilight(irc,msg,channel,mask,text,False)
3149
3150 def isChannelLowHilight (self,irc,msg,channel,mask,text):
3151 return self.isHilight(irc,msg,channel,mask,text,True)
3152
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)
3157 count = self.registryValue('badunicodeScore',channel=channel)
3158 if count < score:
3159 return self.isBadOnChannel(irc,channel,'badunicode',mask)
3160 return False
3161
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)
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:
3179 if len(user) > 3:
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:
3188 result = self.isBadOnChannel(irc,channel,kind,mask)
3189 return result
3190
3191 def isChannelRepeat (self,irc,msg,channel,mask,text):
3192 return self.isRepeat(irc,msg,channel,mask,text,False)
3193
3194 def isChannelLowRepeat (self,irc,msg,channel,mask,text):
3195 return self.isRepeat(irc,msg,channel,mask,text,True)
3196
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)
3204 if limit < 0:
3205 return False
3206 if len(text) < self.registryValue('%sMinimum' % kind,channel=channel):
3207 return False
3208 chan = self.getChan(irc,channel)
3209 life = self.registryValue('%sLife' % kind,channel=channel)
3210 trigger = self.registryValue('%sPercent' % kind,channel=channel)
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]
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:
3223 result = self.isBadOnChannel(irc,channel,kind,mask)
3224 enough = False
3225 i = self.getIrc(irc)
3226 if flag and not i.netsplit:
3227 if kind in chan.buffers and key in chan.buffers[kind]:
3228 # we start to try to create pattern if user hits around 2/3 of his buffer
3229 if len(chan.buffers[kind][key])/(limit * 1.0) > 0.55:
3230 enough = True
3231 if result or enough:
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)
3237 if self.registryValue('computedPattern',channel=channel) > -1 and len(text) > self.registryValue('computedPattern',channel=channel):
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))
3251 candidate = ''
3252 patterns = {}
3253 for repeat in repeats:
3254 (p,c) = repeat
3255 #self.log.debug('%s :: %s' % (p,c))
3256 if len(p) < self.registryValue('%sMinimum' % kind, channel=channel):
3257 continue
3258 p = p.strip()
3259 if p in patterns:
3260 patterns[p] += c
3261 else:
3262 patterns[p] = c
3263 if len(p) > self.registryValue('computedPattern',channel=channel):
3264 if len(p) > len(candidate):
3265 candidate = p
3266 elif len(p) * c > self.registryValue('computedPattern',channel=channel):
3267 tentative = ''.join(list((p,) * int(c)))
3268 if not tentative in text:
3269 tentative = ''.join(list(((p + ' '),) * int(c)))
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
3276 elif patterns[p] > self.registryValue('%sCount' % kind,channel=channel):
3277 if len(p) > len(candidate):
3278 candidate = p
3279 if candidate.strip() == channel:
3280 self.log.debug('pattern candidate %s discared in %s' % (candidate,channel))
3281 candidate = ''
3282 if len(candidate) and len(candidate) > self.registryValue('%sMinimum' % kind, channel=channel):
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()
3290 shareID = self.registryValue('shareComputedPatternID',channel=channel)
3291 i = self.getIrc(irc)
3292 if shareID != -1 or i.defcon:
3293 nb = 0
3294 for chan in i.channels:
3295 ch = i.channels[chan]
3296 life = self.registryValue('computedPatternLife',channel=chan)
3297 if shareID != self.registryValue('shareComputedPatternID',channel=chan):
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)
3303 ch.patterns.enqueue(candidate)
3304 nb = nb + 1
3305 self.logChannel(irc,'PATTERN: [%s] %s added "%s" in %s channels (%s)' % (channel,mask,candidate,nb,kind))
3306 else:
3307 chan.patterns.enqueue(candidate)
3308 self.logChannel(irc,'PATTERN: [%s] %s added "%s" for %ss (%s)' % (channel,mask,candidate,self.registryValue('computedPatternLife',channel=channel),kind))
3309 logs.enqueue(text)
3310 return result
3311
3312 def isChannelMassRepeat (self,irc,msg,channel,mask,text):
3313 return self.isMassRepeat(irc,msg,channel,mask,text,False)
3314
3315 def isChannelLowMassRepeat (self,irc,msg,channel,mask,text):
3316 return self.isMassRepeat(irc,msg,channel,mask,text,True)
3317
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)
3325 if limit < 0:
3326 return False
3327 if len(text) < self.registryValue('%sMinimum' % kind,channel=channel):
3328 return False
3329 chan = self.getChan(irc,channel)
3330 life = self.registryValue('%sLife' % kind,channel=channel)
3331 trigger = self.registryValue('%sPercent' % kind,channel=channel)
3332 length = self.registryValue('computedPattern',channel=channel)
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)
3337 flag = False
3338 result = False
3339 pattern = None
3340 s = ''
3341 logs = chan.logs[key]
3342 for m in logs:
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:
3350 s = s.strip()
3351 if len(s) > len(pattern):
3352 pattern = s
3353 s = pattern
3354 flag = True
3355 break
3356 if flag:
3357 result = self.isBadOnChannel(irc,channel,kind,channel)
3358 if result and pattern and length > -1:
3359 life = self.registryValue('computedPatternLife',channel=channel)
3360 if not chan.patterns:
3361 chan.patterns = utils.structures.TimeoutQueue(life)
3362 elif chan.patterns.timeout != life:
3363 chan.patterns.setTimeout(life)
3364 if len(pattern) > length:
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:
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):
3379 continue
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)
3385 ch.patterns.enqueue(pattern)
3386 nb = nb + 1
3387 self.logChannel(irc,'PATTERN: [%s] %s added "%s" in %s channels (%s)' % (channel,mask,pattern,nb,kind))
3388 else:
3389 chan.patterns.enqueue(pattern)
3390 self.logChannel(irc,'PATTERN: [%s] %s added "%s" for %ss (%s)' % (channel,mask,pattern,self.registryValue('computedPatternLife',channel=channel),kind))
3391 logs.enqueue(text)
3392 if result and pattern:
3393 return result
3394 return False
3395
3396 def logChannel(self,irc,message):
3397 channel = self.registryValue('logChannel')
3398 i = self.getIrc(irc)
3399 if channel in irc.state.channels:
3400 self.log.info('logChannel : %s' % message)
3401 msg = ircmsgs.privmsg(channel,message)
3402 if self.registryValue('useNotice'):
3403 msg = ircmsgs.notice(channel,message)
3404 life = self.registryValue('announceLife')
3405 limit = self.registryValue('announcePermit')
3406 if limit > -1:
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))
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'))
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
3426 i.defcon = time.time()
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)
3438
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
3446 if ircdb.checkCapability(msg.prefix, 'protected'):
3447 return
3448 i = self.getIrc(irc)
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
3457 else:
3458 aa = account.lower()
3459 for u in i.klinednicks:
3460 if aa == u:
3461 self.logChannel(irc,"SERVICE: %s (%s) lethaled account (extended-join %s)" % (msg.prefix,account,msg.args[0]))
3462 src = msg.nick
3463 i.klinednicks.enqueue(aa)
3464 if not src in i.tokline:
3465 i.toklineresults[src] = {}
3466 i.toklineresults[src]['kind'] = 'evade'
3467 i.tokline[src] = src
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)))
3472 break
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()
3479 mask = self.prefixToMask(irc,msg.prefix,channel)
3480 if isCloaked(msg.prefix,self) or account:
3481 t = t - self.registryValue('ignoreDuration',channel=channel) - 1
3482 chan.nicks[msg.nick] = [t,msg.prefix,mask,gecos,account]
3483 if self.registryValue('ignoreRegisteredUser',channel=channel):
3484 if account:
3485 continue
3486 if i.netsplit:
3487 continue
3488 if 'gateway/shell/matrix.org' in msg.prefix:
3489 continue
3490 life = self.registryValue('massJoinLife',channel=channel)
3491 limit = self.registryValue('massJoinPermit',channel=channel)
3492 trigger = self.registryValue('massJoinPercent',channel=channel)
3493 length = self.registryValue('massJoinMinimum',channel=channel)
3494 # massJoin for the whole channel
3495 flags = []
3496 if limit > -1:
3497 b = self.isBadOnChannel(irc,channel,'massJoin',channel)
3498 if b:
3499 self.log.info('Massjoin detected in %s (%s/%s)' % (channel,life,limit))
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:
3506 if not mask in flags:
3507 flags.append(mask)
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)
3511 ## massJoin similar nicks
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)
3534 ## massJoin similar gecos
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):
3560 for u in flags:
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))
3566
3567 def doPart (self,irc,msg):
3568 channels = msg.args[0].split(',')
3569 i = self.getIrc(irc)
3570 reason = ''
3571 if len(msg.args) == 2:
3572 reason = msg.args[1].lstrip().rstrip()
3573 if not ircutils.isUserHostmask(msg.prefix):
3574 return
3575 if msg.prefix == irc.prefix:
3576 for channel in channels:
3577 if ircutils.isChannel(channel):
3578 self.setRegistryValue('lastActionTaken',time.time(),channel=channel)
3579 self.logChannel(irc,'PART: [%s] %s' % (channel,reason))
3580 if channel in i.channels:
3581 del i.channels[channel]
3582 return
3583 mask = self.prefixToMask(irc,msg.prefix)
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
3594 if self.registryValue('ignoreRegisteredUser',channel=channel):
3595 if len(chan.nicks[msg.nick]) > 4:
3596 if chan.nicks[msg.nick][4]:
3597 continue
3598 protected = ircdb.makeChannelCapability(channel, 'protected')
3599 if ircdb.checkCapability(msg.prefix, protected):
3600 continue
3601 if reason == 'Changing Host' or i.netsplit:
3602 continue
3603 bad = False
3604 if len(reason) and 'Kicked by @appservice-irc:matrix.org' in reason:
3605 continue
3606 flag = ircdb.makeChannelCapability(channel, 'cycle')
3607 if ircdb.checkCapability(msg.prefix, flag):
3608 bad = self.isBadOnChannel(irc,channel,'cycle',mask)
3609 if bad:
3610 self.isAbuseOnChannel(irc,channel,'cycle',mask)
3611 if bad:
3612 isBanned = True
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)
3616 self.ban(irc,msg.nick,msg.prefix,mask,self.registryValue('klineDuration'),comment,self.registryValue('klineMessage'),log)
3617 self.setRegistryValue('lastActionTaken',time.time(),channel=channel)
3618 if len(reason):
3619 if 'Kicked by @appservice-irc:matrix.org' in reason or 'requested by' in reason:
3620 continue
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))
3627 if not isBanned:
3628 life = self.registryValue('cycleLife',channel=channel)
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)
3636 flag = ircdb.makeChannelCapability(channel, 'joinSpamPart')
3637 if ircdb.checkCapability(msg.prefix, flag) and not isBanned:
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:
3644 self.isAbuseOnChannel(irc,channel,'joinSpamPart',mask)
3645 if self.hasAbuseOnChannel(irc,channel,'joinSpamPart'):
3646 uid = random.randint(0,1000000)
3647 reason = '(%s/%ss joinSpamPart)' % (limit,life)
3648 klinereason = '%s - %s' % (uid,reason)
3649 if i.defcon:
3650 klinereason = '%s !dnsbl' % reason
3651 self.kline(irc,msg.prefix,mask,self.registryValue('klineDuration'),klinereason)
3652 self.logChannel(irc,'BAD: [%s] %s (%s - %s)' (channel,msg.prefix,reason,uid))
3653 isBanned = True
3654 chan.buffers[kind][key].reset()
3655 continue
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:
3666 self.setRegistryValue('lastActionTaken',-1.0,channel=channel)
3667 self.logChannel(irc,'PART: [%s] %s (kicked)' % (channel,reason))
3668 del i.channels[channel]
3669 try:
3670 network = conf.supybot.networks.get(irc.network)
3671 network.channels().remove(channel)
3672 except KeyError:
3673 pass
3674
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':
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')
3686 if i.netsplit:
3687 return
3688 mask = self.prefixToMask(irc,msg.prefix)
3689 isBanned = False
3690 (nick,ident,host) = ircutils.splitHostmask(msg.prefix)
3691 for channel in irc.state.channels:
3692 if ircutils.isChannel(channel) and not i.netsplit:
3693 chan = self.getChan(irc,channel)
3694 if self.registryValue('ignoreChannel',channel):
3695 continue
3696 if msg.nick in chan.nicks:
3697 if self.registryValue('ignoreRegisteredUser',channel=channel):
3698 if len(chan.nicks[msg.nick]) > 4:
3699 if chan.nicks[msg.nick][4]:
3700 continue
3701 protected = ircdb.makeChannelCapability(channel, 'protected')
3702 if ircdb.checkCapability(msg.prefix, protected):
3703 continue
3704 bad = False
3705 flag = ircdb.makeChannelCapability(channel, 'broken')
3706 if 'tor-sasl' in mask:
3707 continue
3708 if ircdb.checkCapability(msg.prefix, flag):
3709 bad = self.isBadOnChannel(irc,channel,'broken',mask)
3710 if isBanned:
3711 continue
3712 if bad and not i.netsplit:
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))
3716 isBanned = True
3717 continue
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:
3726 self.isAbuseOnChannel(irc,channel,'joinSpamPart',mask)
3727 if self.hasAbuseOnChannel(irc,channel,'joinSpamPart'):
3728 uid = random.randint(0,1000000)
3729 reason = '(%s/%ss joinSpamPart)' % (limit,life)
3730 klinereason = '%s - %s' % (uid,reason)
3731 if i.defcon:
3732 klinereason = '%s !dnsbl' % reason
3733 self.kline(irc,msg.prefix,mask,self.registryValue('klineDuration'),klinereason)
3734 self.logChannel(irc,'BAD: [%s] %s (%s - %s)' % (channel,msg.prefix,reason,uid))
3735 isBanned = True
3736 chan.buffers[kind][key].reset()
3737 continue
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']
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):
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
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))
3758
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])
3765 mask = self.prefixToMask(irc,newPrefix)
3766 i = self.getIrc(irc)
3767 if i.netsplit:
3768 return
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]
3780 # todo check digit/hexa nicks too
3781 if not newNick.startswith('Guest'):
3782 if not isBanned:
3783 reason = False
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
3787 flag = ircdb.makeChannelCapability(channel, 'nick')
3788 if ircdb.checkCapability(msg.prefix, flag):
3789 reason = self.isBadOnChannel(irc,channel,'nick',mask)
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
3796 if not isCloaked(msg.prefix,self):
3797 if i.defcon or chan.called:
3798 hasBeenIgnored = False
3799 if not reason and i.defcon and self.hasAbuseOnChannel(irc,channel,'nick'):
3800 reason = 'nick changes, due to abuses'
3801 if reason:
3802 if hasBeenIgnored:
3803 bypass = self.isBadOnChannel(irc,channel,'bypassIgnore',mask)
3804 if bypass:
3805 uid = random.randint(0,1000000)
3806 comment = '%s %s' % (reason,bypass)
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)
3809 self.setRegistryValue('lastActionTaken',time.time(),channel=channel)
3810 isBanned = True
3811 else:
3812 self.logChannel(irc,'IGNORED: [%s] %s (%s)' % (channel,newPrefix,reason))
3813 else:
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)
3817 self.setRegistryValue('lastActionTaken',time.time(),channel=channel)
3818 isBanned = True
3819 del chan.nicks[oldNick]
3820
3821 def reset(self):
3822 self._ircs = ircutils.IrcDict()
3823
3824 def die(self):
3825 self.log.info('die() called')
3826 self.cache = {}
3827 try:
3828 conf.supybot.protocols.irc.throttleTime.setValue(1.6)
3829 except:
3830 pass
3831 self._ircs = ircutils.IrcDict()
3832 super().die()
3833
3834 def doError (self,irc,msg):
3835 self._ircs = ircutils.IrcDict()
3836
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
3862
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
3876