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