]> jfr.im git - irc/rizon/acid.git/blob - pyva/pyva/src/main/python/ctcp/ctcp.py
Split pyva plugin into pyva.core and pyva.pyva
[irc/rizon/acid.git] / pyva / pyva / src / main / python / ctcp / ctcp.py
1 #!/usr/bin/python pseudoserver.py
2 #psm-ctcp.py
3 # module for pypseudoserver
4 # written by radicand
5 # doesn't do much by itself
6
7 import task
8 import istring
9 from operator import itemgetter #for sort
10 from collections import defaultdict
11 import time
12
13 from pyva import *
14 import logging
15 from core import *
16 from plugin import *
17 from threading import Timer
18
19 class ctcp(AcidPlugin):
20 CTCP_BANNED = 0
21 CTCP_TOTAL = 0
22
23 blacklist = []
24 uid = ""
25
26 def __init__(self):
27 AcidPlugin.__init__(self)
28 self.name = "ctcp"
29 self.log = logging.getLogger(__name__)
30
31 def start(self):
32 try:
33 self.dbp.execute("CREATE TABLE IF NOT EXISTS ctcp_vbl (item SMALLINT(5) NOT NULL AUTO_INCREMENT, reply VARCHAR(255), found INT(10), PRIMARY KEY (item), UNIQUE KEY (reply));")
34 self.dbp.execute("CREATE TABLE IF NOT EXISTS ctcp_vlist (reply VARCHAR(255), found INT(10), UNIQUE KEY (reply));")
35 self.dbp.execute("CREATE TABLE IF NOT EXISTS ctcp_website (reply VARCHAR(255), found INT(10), UNIQUE KEY (reply));")
36 self.dbp.execute("CREATE TABLE IF NOT EXISTS ctcp_website_replies (keyword VARCHAR(255), reply VARCHAR(255), UNIQUE KEY (keyword));")
37 except Exception, err:
38 self.log.exception("Error creating tables for CTCP module (%s)" % err)
39 raise
40
41 try:
42 self.dbp.execute("SELECT reply FROM ctcp_vbl;")
43 for row in self.dbp.fetchall(): self.blacklist.append(row[0])
44 except Exception, err:
45 self.log.exception("Error loading blacklists from DB for CTCP module (%s)" % err)
46
47 try:
48 num = self.dbp.execute("SELECT num FROM record WHERE name='ctcp-total';")
49 if num:
50 self.CTCP_TOTAL = self.dbp.fetchone()[0]
51
52 num = self.dbp.execute("SELECT num FROM record WHERE name='ctcp-found';")
53 if num:
54 self.CTCP_BANNED = self.dbp.fetchone()[0]
55
56 except Exception, err:
57 self.log.exception("Error loading in historical records for CTCP module (%s)" % err)
58
59 self.client = istring(self.config.get('ctcp', 'nick'))
60
61 return True
62
63 def stop(self):
64 try:
65 self.dbp.execute("INSERT INTO record (name, num) VALUES ('ctcp-total', %s) ON DUPLICATE KEY UPDATE num=%s;", (self.CTCP_TOTAL,self.CTCP_TOTAL))
66 self.dbp.execute("INSERT INTO record (name, num) VALUES ('ctcp-found', %s) ON DUPLICATE KEY UPDATE num=%s;", (self.CTCP_BANNED,self.CTCP_BANNED))
67 except Exception, err:
68 self.log.exception("Error updating historical records for CTCP module (%s)" % err)
69
70 def mkts():
71 return int(time.time())
72
73 ## Begin event hooks
74 def onUserConnect(self, user):
75 """Sends a CTCP VERSION request to a user
76 We setup a list with the timestamp we sent the request
77 and if we havent received the reply in X seconds, akill them.
78 Also akill if they have a blacklisted reply.
79 If user is from mibbit, request a CTCP WEBSITE, so we can track it.
80 Rizon: Temporarily ignore nick `bRO-*` / eth32 / RAC until they fix their 'client'.
81 """
82
83 nick = user.getNick()
84 if nick.startswith(u'bRO-'): return #rizon
85 elif nick.startswith(u'RAC_'): return #rizon
86 elif u'eth32' in nick: return #rizon
87
88 #if mibbit req website
89 if user.hasMode("W"):
90 self.inter.privmsg(self.client, user.getUID(), "\001WEBSITE\001")
91
92 self.inter.privmsg(self.client, user.getUID(), "\001VERSION\001")
93 self.CTCP_TOTAL += 1
94
95 def onCtcpReply(self, creator, recipient, message):
96 """Receiver for CTCP VERSION replies.
97 Check against active blacklist.
98 Remove from timeout list too.
99 #checks ctcp website too
100 TODO: clean this up
101 """
102
103 user = self.inter.findUser(creator)
104 if not user:
105 return
106
107 try:
108 reply = istring(message[1:-1])
109 except:
110 return # Can this throw?
111
112 #ctcp website reply
113 if reply.lower().startswith(u"website"):
114 reply = reply[8:]
115 try:
116 self.dbp.execute(u"INSERT INTO ctcp_website (reply, found) VALUES(%s, 1) ON DUPLICATE KEY UPDATE found=found+1;", (reply,))
117 except Exception, err:
118 self.log.exception(u"Error updating ctcp_website: %s (%s)" % (reply, err))
119 user['website'] = reply
120 return
121
122 #ctcp version reply
123 if not reply.lower().startswith(u"version"): return
124 reply = unicode(reply[8:])
125 self.log.debug(u"Got CTCP VERSION: %s" % reply)
126
127 try:
128 self.dbp.execute(u"INSERT INTO ctcp_vlist (reply, found) VALUES(%s, 1) ON DUPLICATE KEY UPDATE found=found+1;", (reply,))
129 except Exception, err:
130 self.log.exception(u"Error updating ctcp_vlist: %s (%s)" % (reply, err))
131
132 user['version'] = reply
133 self.handleWebsiteReply(user)
134
135 if reply in self.blacklist:
136 self.CTCP_BANNED += 1
137
138 akillserv = self.inter.findUser("GeoServ") or self.inter.findUser("OperServ")
139 if akillserv:
140 self.inter.privmsg(self.client, akillserv.getUID(), "AKILL ADD +3d *@%s Compromised Host - Blacklisted VERSION reply." %
141 (user.getIP(),))
142
143 self.inter.privmsg(self.client, self.logchan, "CTCP: Banned %s!%s@%s - Blacklisted VERSION: %s (#%d/%d)" %
144 (user.getNick(), user.getUser(), user.getIP(), reply, self.CTCP_BANNED, self.CTCP_TOTAL))
145
146 try:
147 self.dbp.execute(u"UPDATE ctcp_vbl SET found=found+1 WHERE reply=%s;", (reply,))
148 except Exception, err:
149 self.log.exception("Error updating ctcp_vbl found count: %s (%s)" % (reply, err))
150
151 #if "dnsbl_admin" in self.parent.modules:
152 # uid2, user2 = self.parent.get_user(self.uid)
153 # self.parent.modules['dnsbl_admin'].add(user['ip'], "5", "Compromised host found by %s (%s!%s@%s)" %
154 # (user2['nick'],user['nick'], user['user'], user['ip']))
155
156 ## Begin Command hooks
157 def cmd_ctcpVersionAdd(self, source, target, pieces):
158 if not pieces: return False
159 try:
160 self.dbp.execute("INSERT INTO ctcp_vbl (reply,found) VALUES(%s,0);", (u' '.join(pieces),))
161 self.inter.privmsg(self.client, target, "Added VERSION blacklist #%d" % self.dbp.lastrowid)
162 except Exception, err:
163 self.log.exception("Error adding version to CTCP module blacklist: (%s)" % err)
164 return False
165
166 self.blacklist.append(u' '.join(pieces))
167 return True
168
169 def cmd_ctcpVersionDel(self, source, target, pieces):
170 if not pieces: return False
171 try:
172 num = self.dbp.execute("SELECT reply FROM ctcp_vbl WHERE item=%s", (pieces[0],))
173 if not num: raise Exception("No such ID")
174 else: reply = self.dbp.fetchone()[0]
175
176 self.dbp.execute("DELETE FROM ctcp_vbl WHERE item=%s;", (pieces[0],))
177 self.blacklist.remove(reply)
178 self.inter.privmsg(self.client, target, "Removed blacklist for: %s" % reply)
179 except Exception, err:
180 self.log.exception("Error deleting version blacklist for CTCP module: (%s)" % err)
181 self.inter.privmsg(self.client, target, "No such ID")
182
183 return True
184
185 def cmd_ctcpVersionBLList(self, source, target, pieces):
186 try:
187 self.dbp.execute("SELECT item, reply FROM ctcp_vbl;")
188 for row in self.dbp.fetchall():
189 self.inter.privmsg(self.client, target, "#%d: %s" % (row[0], row[1]))
190 self.log.debug("CVMEMORY: %s" % self.blacklist)
191 except Exception, err:
192 self.log.exception("Error selecting version blacklists for CTCP module: (%s)" % err)
193
194 self.inter.privmsg(self.client, target, "END OF CVLIST")
195 return True
196 def cmd_ctcpWebsiteList(self, source, target, pieces):
197 try:
198 w = defaultdict(lambda: 0)
199 cm = 0
200 cq = 0
201 for k,v in self.parent.user_t.iteritems():
202 if 'website' in v:
203 tmp = v['website']
204 w[tmp] += 1
205 if v['user'] == u'cgiirc': cm += 1
206 if v['user'] == u'qwebirc': cq += 1
207 if v['user'].endswith(u'webchat'): cq += 1
208 except Exception, err:
209 self.log.exception("Error comprehending website list creation: %s" % (err,))
210 self.inter.privmsg(self.client, target, "Error creating list response")
211 return True
212 #sort by number of hits
213 w = sorted(w.items(), key=itemgetter(1))
214 for k,v in w:
215 self.inter.notice(self.client, source, "%dx: %s" % (v,k))
216 self.inter.notice(self.client, source, "END OF CWLIST(m=%d,q=%d)" % (cm,cq))
217 return True
218
219 ctcpReplyCache = None
220
221 def handleWebsiteReply(self, user):
222 if self.ctcpReplyCache == None:
223 self.ctcpReplyCache = []
224 self.dbp.execute("SELECT * FROM ctcp_website_replies")
225 for row in self.dbp.fetchall():
226 self.ctcpReplyCache.append((row[0], row[1]))
227
228 for reply in self.ctcpReplyCache:
229 if 'version' in user and user['version'].lower().find(reply[0].lower()) != -1:
230 self.inter.notice(self.client, user.getNick(), reply[1])
231 elif 'website' in user and user['website'].lower().find(reply[0].lower()) != -1:
232 self.inter.notice(self.client, user.getNick(), reply[1])
233 return
234
235 def cmd_ctcpReplyAdd(self, source, target, pieces):
236 if len(pieces) < 2:
237 return False
238 try:
239 self.dbp.execute("INSERT INTO ctcp_website_replies (keyword, reply) VALUES(%s, %s)", (pieces[0], ' '.join(pieces[1:])))
240 self.inter.privmsg(self.client, target, "Added reply for keyword %s" % pieces[0])
241 self.ctcpReplyCache = None
242 except Exception, err:
243 self.log.exception("Error adding website reply for %s: %s" % (pieces[0], err))
244 self.inter.privmsg(self.client, target, "Error creating reply keyword")
245 return True
246
247 def cmd_ctcpReplyDel(self, source, target, pieces):
248 if not pieces:
249 return False
250 try:
251 self.dbp.execute("DELETE FROM ctcp_website_replies WHERE keyword=%s", pieces[0])
252 self.inter.privmsg(self.client, target, "Removed reply for keyword %s" % pieces[0])
253 self.ctcpReplyCache = None
254 except Exception, err:
255 self.log.exception("Error removing website reply for %s: %s" % (pieces[0], err))
256 self.inter.privmsg(self.client, target, "Error removing reply keyword")
257 return True
258
259 def cmd_ctcpReplyList(self, source, target, pieces):
260 try:
261 self.dbp.execute("SELECT * FROM ctcp_website_replies")
262 for row in self.dbp.fetchall():
263 self.inter.privmsg(self.client, target, "%s: %s" % (row[0], row[1]))
264 except Exception, err:
265 self.log.exception("Error listing website replies: %s" % err)
266 self.inter.privmsg(self.client, target, "Error listing website replies.")
267 return True
268
269 def cmd_ctcpVersionList(self, source, target, pieces):
270 try:
271 subcmd = pieces[0].lower()
272 except IndexError:
273 subcmd = "top"
274
275 try:
276 arg = pieces[1]
277 except IndexError:
278 arg = ""
279
280 maxrow = 25
281 count = 0
282 if subcmd == "top":
283 if arg != "":
284 try:
285 maxrow = int(arg)
286 except:
287 pass
288
289 # pymysql apparently converts ints to strings, the %s here is intentional!
290 self.dbp.execute("SELECT reply, found FROM ctcp_vlist ORDER BY found DESC LIMIT %s", (maxrow,))
291
292 try:
293 for row in self.dbp.fetchall():
294 self.inter.privmsg(self.client, target, u"%s [%s times]" % (row[0], row[1]))
295 count += 1
296 except Exception, err:
297 self.log.exception("Error listing version replies [top]: %s" % err)
298 self.inter.privmsg(self.client, target, "Error listing version replies.")
299
300 elif subcmd == "search":
301 if arg == "":
302 self.inter.privmsg(self.client, target, 'Please give a string to search for.')
303 return False
304
305 try:
306 maxrow = int(pieces[2])
307 except:
308 pass
309
310 self.dbp.execute(u"SELECT reply, found FROM ctcp_vlist WHERE reply LIKE '%%%s%%' LIMIT %s" % (arg, maxrow))
311 try:
312 for row in self.dbp.fetchall():
313 self.inter.privmsg(self.client, target, u"%s [%s times]" % (row[0], row[1]))
314 count += 1
315 except Exception, err:
316 self.log.exception("Error listing version replies [search]: %s" % err)
317 self.inter.privmsg(self.client, target, "Error listing version replies.")
318 else:
319 self.inter.privmsg(self.client, target, 'Please use one of "top" or "search."')
320 return False
321
322 self.inter.privmsg(self.client, target, "END OF VLIST (%d results)" % count)
323 return True
324
325 ## End Command hooks
326
327 def getCommands(self):
328 return (('addbl', {
329 'permission':'v',
330 'callback':self.cmd_ctcpVersionAdd,
331 'usage':"<version string> - blacklists a VERSION string"}),
332 ('delbl', {
333 'permission':'v',
334 'callback':self.cmd_ctcpVersionDel,
335 'usage':"<version string> - removes a blacklisted VERSION string"}),
336 ('listbl', {
337 'permission':'v',
338 'callback':self.cmd_ctcpVersionBLList,
339 'usage':"- shows all active blacklisted VERSION strings"}),
340 ('cwlist', {
341 'permission':'v',
342 'callback':self.cmd_ctcpWebsiteList,
343 'usage':"- shows source of all online mibbit users"}),
344 ('wadd', {
345 'permission' : 'v',
346 'callback' : self.cmd_ctcpReplyAdd,
347 'usage' : "<match> <reply> - sends a custom notice to specific clients"}),
348 ('wdel', {
349 'permission' : 'v',
350 'callback' : self.cmd_ctcpReplyDel,
351 'usage' : "<match> - removes the welcome notice for match"}),
352 ('wlist', {
353 'permission' : 'v',
354 'callback' : self.cmd_ctcpReplyList,
355 'usage' : "- shows the welcome notices for version matches"}),
356 ('vlist', {
357 'permission': 'v',
358 'callback': self.cmd_ctcpVersionList,
359 'usage': "{top|search} [num|searchstring] - shows the top client version replies or searches them. Defaults to showing top"}),
360 )