]> jfr.im git - irc/rizon/acid.git/blob - pyva/src/main/python/moo/requests.py
.gitignore: Ignore all pyva logs
[irc/rizon/acid.git] / pyva / src / main / python / moo / requests.py
1 import utils
2 from datetime import datetime
3 from fnmatch import fnmatch
4 from operator import itemgetter
5 from socket import getaddrinfo, gaierror
6 import moo_utils
7
8 class Ban(object):
9 def __init__(self, row):
10 self.nick, self.reason, self.banned_by, self.date = row
11
12 class Blacklist(object):
13 def __init__(self, row):
14 self.vhost, self.added_by, self.reason, self.date = row
15
16 class Request(object):
17 def __init__(self, row):
18 self.nickname, self.main, self.vhost, self.resolved_by, self.waited_for, self.date = row
19
20 class RejectedRequest(Request):
21 def __init__(self, row):
22 Request.__init__(self, row[:-1])
23 self.reason = row[-1]
24
25 class Suspicious(object):
26 def __init__(self, row):
27 self.vhost, self.added_by, self.reason, self.date = row
28
29 URL = 'http://s.rizon.net/vhost'
30 RULES = 'For basic vhost rules and restrictions, see: %s' % URL
31
32 class RequestManager(object):
33 def __init__(self, module):
34 self.module = module
35 self.dbp = module.dbp
36 self.list = {}
37 self.banlist = {}
38
39 self.dbp.execute("""CREATE TABLE IF NOT EXISTS `moo_bans` (
40 `id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
41 `nickname` VARCHAR( 31 ) NOT NULL UNIQUE KEY ,
42 `reason` TEXT NULL ,
43 `banned_by` VARCHAR( 31 ) NOT NULL ,
44 `date` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)""")
45 self.dbp.execute('SELECT nickname, reason, banned_by, date FROM moo_bans')
46 for row in self.dbp.fetchall():
47 self.banlist[row[0].lower()] = Ban(row)
48
49 self.dbp.execute("""CREATE TABLE IF NOT EXISTS `moo_requests` (
50 `id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
51 `nickname` VARCHAR( 31 ) NOT NULL ,
52 `main` VARCHAR ( 31 ) NOT NULL ,
53 `vhost` VARCHAR( 64 ) NOT NULL ,
54 `resolved_by` VARCHAR( 31 ) NULL DEFAULT NULL ,
55 `waited_for` MEDIUMINT UNSIGNED NOT NULL ,
56 `date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)""")
57 self.dbp.execute('SELECT nickname, main, vhost, resolved_by, waited_for, date FROM moo_requests ORDER BY date ASC')
58 self.requests = [Request(req) for req in self.dbp.fetchall()]
59
60 self.dbp.execute("""CREATE TABLE IF NOT EXISTS `moo_rejections` (
61 `id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
62 `nickname` VARCHAR( 31 ) NOT NULL ,
63 `main` VARCHAR ( 31 ) NOT NULL ,
64 `vhost` VARCHAR( 64 ) NOT NULL ,
65 `resolved_by` VARCHAR( 31 ) NULL DEFAULT NULL ,
66 `reason` VARCHAR ( 512 ) NOT NULL ,
67 `waited_for` MEDIUMINT UNSIGNED NOT NULL ,
68 `date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)""")
69 self.dbp.execute('SELECT nickname, main, vhost, resolved_by, waited_for, date, reason FROM moo_rejections ORDER BY date ASC')
70 self.rejections = [RejectedRequest(req) for req in self.dbp.fetchall()]
71
72 self.dbp.execute("""CREATE TABLE IF NOT EXISTS `moo_blacklist` (
73 `id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
74 `vhost` VARCHAR( 64 ) NOT NULL UNIQUE KEY ,
75 `added_by` VARCHAR( 31 ) NOT NULL ,
76 `reason` TEXT NULL ,
77 `date` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)""")
78 self.dbp.execute('SELECT vhost, added_by, reason, date FROM moo_blacklist')
79 self.blacklist = [Blacklist(row) for row in self.dbp.fetchall()]
80
81 self.dbp.execute("""CREATE TABLE IF NOT EXISTS `moo_suspicious` (
82 `id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
83 `vhost` VARCHAR( 64 ) NOT NULL UNIQUE KEY ,
84 `added_by` VARCHAR( 31 ) NOT NULL ,
85 `reason` TEXT NULL ,
86 `date` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)""")
87 self.dbp.execute('SELECT vhost, added_by, reason, date FROM moo_suspicious')
88 self.suspicious = [Suspicious(row) for row in self.dbp.fetchall()]
89
90 def add(self, vhost, nick, main=None):
91 if main:
92 last_req = self.__get_last_request(main)
93 acceptable, rating, internal = self.__verify(nick, vhost, main)
94 else: # only for requests missed during downtime
95 userinfo = self.module.inter.findUser(nick)
96 if userinfo and userinfo['su']:
97 main = userinfo['su']
98 last_req = self.__get_last_request(main)
99 acceptable, rating, internal = self.__verify(nick, vhost, main)
100 else: # user no longer online or SU is empty, can't check main nick / last request
101 last_req = self.__get_last_request(nick)
102 acceptable, rating, internal = self.__verify(nick, vhost)
103
104 if not acceptable:
105 self.module.msg('HostServ', 'REJECT %s %s' % (nick, rating if rating else ''))
106 self.module.msg(self.module.chan, 'Rejected vhost for @b%(nick)s@b.%(int)s' % {
107 'nick': nick,
108 'int' : ' %s' % internal if internal else ''})
109 now = datetime.now()
110 t = (nick, main, vhost, 'py-moo', 0, now, rating)
111 r = RejectedRequest(t)
112 if r.main:
113 self.dbp.execute('INSERT INTO moo_rejections (nickname, main, vhost, resolved_by, waited_for, reason) VALUES (%s, %s, %s, %s, %s, %s)',
114 (r.nickname, r.main, r.vhost, r.resolved_by, r.waited_for, r.reason))
115 else:
116 r.main = r.nickname
117 self.rejections.append(r)
118 return
119
120 if nick.lower() in self.list:
121 if self.list[nick.lower()]['vhost'] == vhost:
122 return # don't repeat if no change
123 else:
124 id = self.list[nick.lower()]['id'] # replace the old request with the new vhost
125 else:
126 id = self.__get_id()
127
128 suspicious = self.__is_suspicious(vhost)
129 date = datetime.now()
130 self.list[nick.lower()] = {'id': id, 'vhost': vhost, 'rating': rating, 'date': date, 'main': main, 'nick': nick, 'last': last_req, 'suspicious': suspicious}
131 self.module.msg(self.module.chan, moo_utils.format_last_req(last_req, '[new] %(id)d.@o %(nick)s @sep %(vhost)s%(susp)s @sep%(lastreq)s' % {
132 'id' : id,
133 'nick' : nick,
134 'vhost' : vhost if not suspicious else '@c10@b[!]@o ' + vhost,
135 'susp' : ' @sep @bSuspicious@b %s' % suspicious if suspicious else '',
136 'lastreq': ' @bLast request@b %s ago as %s @sep' % (utils.get_timespan(last_req.date), last_req.nickname) if last_req else ''}))
137
138 def add_blacklist(self, vhost, by, reason=None):
139 b = (vhost, by, reason, datetime.now())
140 self.dbp.execute('INSERT INTO moo_blacklist (vhost, added_by, reason) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE added_by=%s, reason=%s',
141 b[:-1] + (by, reason))
142 for bla in self.blacklist:
143 if bla.vhost.lower() == vhost.lower():
144 self.blacklist.remove(bla)
145 break
146
147 self.blacklist.append(Blacklist(b))
148 self.module.msg(self.module.chan, '@b%s@b added @b%s@b to blacklisted vHost list' % (by, vhost))
149
150 def del_blacklist(self, vhost, by):
151 for bla in self.blacklist:
152 if bla.vhost.lower() == vhost.lower():
153 self.blacklist.remove(bla)
154 self.dbp.execute('DELETE FROM moo_blacklist WHERE vhost = %s', bla.vhost)
155 self.module.msg(self.module.chan, '@b%s@b removed @b%s@b from blacklisted vHost list' % (by, vhost))
156 return
157
158 self.module.msg(self.module.chan, '@b%s@b not found in blacklisted vHost list' % vhost)
159
160 def add_suspicious(self, vhost, by, reason=None):
161 s = (vhost, by, reason, datetime.now())
162 self.dbp.execute('INSERT INTO moo_suspicious (vhost, added_by, reason) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE added_by=%s, reason=%s',
163 s[:-1] + (by, reason))
164 for sus in self.suspicious:
165 if sus.vhost.lower() == vhost.lower():
166 self.suspicious.remove(sus)
167 break
168
169 self.suspicious.append(Suspicious(s))
170 self.module.msg(self.module.chan, '@b%s@b added @b%s@b to suspicious vHost list' % (by, vhost))
171
172 def del_suspicious(self, vhost, by):
173 for sus in self.suspicious:
174 if sus.vhost.lower() == vhost.lower():
175 self.suspicious.remove(sus)
176 self.dbp.execute('DELETE FROM moo_suspicious WHERE vhost = %s', sus.vhost)
177 self.module.msg(self.module.chan, '@b%s@b removed @b%s@b from suspicious vHost list' % (by, vhost))
178 return
179
180 self.module.msg(self.module.chan, '@b%s@b not found in suspicious vHost list' % vhost)
181
182 def __get_nick(self, list, s):
183 if s in list:
184 return s
185 else:
186 for k, v in list.iteritems():
187 if k == str(s).lower() or str(v['id']) == str(s):
188 return k
189
190 return None
191
192 def __get_last_request(self, nick):
193 nick = nick.lower()
194 for r in reversed(self.requests):
195 if r.main.lower() == nick or r.nickname.lower() == nick:
196 return r
197
198 return None
199
200 def __get_id(self):
201 id = 1
202 if not self.list:
203 return 1
204 while(True):
205 sorted_list = sorted(map(itemgetter('id'), self.list.values()))
206 for req in sorted_list:
207 if req != id:
208 return id
209
210 id = id + 1
211
212 return len(sorted_list) + 1
213
214 def approve(self, s, resolved_by, silent=False):
215 nick = self.__get_nick(self.list, s)
216 if not nick:
217 self.module.msg(self.module.chan, 'No pending vHost request for nick/id @b%s@b' % s)
218 return
219
220 req = self.list[nick]
221 now = datetime.now()
222 t = (req['nick'], req['main'], req['vhost'], resolved_by, (now - req['date']).seconds, now)
223 r = Request(t)
224 if r.main:
225 self.dbp.execute('INSERT INTO moo_requests (nickname, main, vhost, resolved_by, waited_for) VALUES (%s, %s, %s, %s, %s)', t[:-1])
226 else: #if the request was made while moo was offline and it couldn't check main nick, it will consider the requesting nick as main nick for that group
227 r.main = r.nickname
228
229 self.requests.append(r)
230 del self.list[nick]
231
232 if silent:
233 return # was already manually approved
234
235 self.module.msg('HostServ', 'ACTIVATE %s' % req['nick'])
236 self.module.msg(self.module.chan, 'Activated vhost for @b%s@b' % req['nick'])
237 # self.module.notice(nick, 'Your requested vHost has been approved. Type "/msg HostServ ON" to activate it.')
238
239 def ban(self, nick, banned_by, reason):
240 nick = nick.lower()
241 b = Ban((nick, reason, banned_by, datetime.now()))
242 self.banlist[nick.lower()] = b
243 self.dbp.execute('INSERT INTO moo_bans (nickname, reason, banned_by) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE reason=%s, banned_by=%s',
244 (nick, reason, banned_by, reason, banned_by))
245 self.module.msg('HostServ', 'DELALL %s' % nick)
246 self.module.msg(self.module.chan, '@b%s@b banned nick @b%s@b with reason @b%s@b. Deleted vHosts for all nicks in group @b%s@b.' % (
247 banned_by, nick, reason if reason else 'No reason', nick))
248
249 def list_vhosts(self, nick):
250 approved = [req for req in self.requests if req.main.lower() == nick.lower() or req.nickname.lower() == nick.lower()]
251 rejections = [req for req in self.rejections if req.main.lower() == nick.lower() or req.nickname.lower() == nick.lower()]
252 merge = sorted(approved + rejections, key=lambda x: x.date)
253 if merge:
254 self.module.msg(self.module.chan, '@b[list]@b %d entries found for nick @b%s@b:' % (len(merge), nick))
255 for n, r in enumerate(merge):
256 is_rejection = r in rejections
257 sepc = 4 if is_rejection else 3
258 ago = utils.get_timespan(r.date)
259 wait = int(r.waited_for / 60)
260 self.module.msg(self.module.chan, '@b%(num)d.@b @bNick@b %(nick)s %(sep)s %(ago)s ago %(sep)s @bvHost@b %(vhost)s %(sep)s @bResolved by@b %(appr)s in %(wait)s minute%(s)s %(sep)s%(reason)s' % {
261 'num' : n + 1,
262 'sep' : '@b@c%s::@o' % sepc,
263 'ago' : ago,
264 'nick' : r.nickname,
265 'vhost' : r.vhost,
266 'appr' : r.resolved_by,
267 'wait' : wait,
268 'reason': ' @bReason@b %(r)s %(sep)s' % {
269 'sep': '@b@c%s::@o' % sepc,
270 'r': r.reason} if is_rejection else '',
271 's' : 's' if wait != 1 else ''})
272 else:
273 self.module.msg(self.module.chan, 'No previous requests by nick @b%s@b' % nick)
274
275 def reject(self, s, resolved_by, reason=None, internal=None):
276 nick = self.__get_nick(self.list, s)
277 if not nick:
278 self.module.msg(self.module.chan, 'No pending vHost request for nick/id @b%s@b' % s)
279 return
280
281 req = self.list[nick]
282 self.module.msg('HostServ', 'REJECT %s %s' % (req['nick'], reason if reason else RULES))
283 del self.list[nick]
284 self.module.msg(self.module.chan, 'Rejected vhost for @b%(nick)s@b.%(int)s' % {
285 'nick': req['nick'],
286 'int' : ' %s' % internal if internal else ''})
287
288 now = datetime.now()
289 t = (req['nick'], req['main'], req['vhost'], resolved_by, (now - req['date']).seconds, now, reason if reason else RULES)
290 r = RejectedRequest(t)
291 if r.main:
292 self.dbp.execute('INSERT INTO moo_rejections (nickname, main, vhost, resolved_by, waited_for, reason) VALUES (%s, %s, %s, %s, %s, %s)',
293 (r.nickname, r.main, r.vhost, r.resolved_by, r.waited_for, r.reason))
294 else:
295 r.main = r.nickname
296 self.rejections.append(r)
297
298 def delete(self, nick): # remove this request from pending requests, used when manually approving/rejecting with hostserv
299 del self.list[nick.lower()]
300
301 def search(self, vhost):
302 list = [req for req in self.requests if req.vhost.lower() == vhost.lower()]
303 if list:
304 self.module.msg(self.module.chan, '@b[search]@b %d entries found for vhost @b%s@b' % (len(list), vhost))
305 for n, r in enumerate(list):
306 ago = utils.get_timespan(r.date)
307 self.module.msg(self.module.chan, '@b%(num)d.@b %(ago)s ago @sep @bNick@b %(nick)s @sep @bvHost@b %(vhost)s @sep @bApproved by@b %(appr)s @sep' % {
308 'num': n + 1,
309 'ago': ago,
310 'nick': r.nickname,
311 'vhost': r.vhost,
312 'appr': r.resolved_by})
313 else:
314 self.module.msg(self.module.chan, 'vHost @b%s@b was never requested' % vhost)
315
316 def unban(self, nick):
317 is_banned, reason, by, date = self.__is_banned(nick)
318 if not is_banned:
319 self.module.msg(self.module.chan, 'No bans found for nick @b%s@b' % nick)
320 return
321
322 self.dbp.execute('DELETE FROM moo_bans WHERE nickname = %s', (nick,))
323 del self.banlist[nick.lower()]
324 self.module.msg(self.module.chan, 'Unbanned nick @b%s@b' % nick)
325
326 def __verify(self, nick, vhost, main=None):
327 is_banned, reason, by, date = self.__is_banned(nick)
328 if is_banned:
329 return (False,
330 'You have been banned from requesting vHosts. For more information, join #services',
331 'vHost: %s. Ban reason: %s - %s ago' % (vhost, reason if reason else 'No reason', utils.get_timespan(date)))
332
333 if main and main != nick:
334 is_banned, reason, by, date = self.__is_banned(main)
335 if is_banned:
336 return (False,
337 'You have been banned from requesting vHosts%s.' % (('. Reason: %s' % reason) if reason else ''),
338 'vHost: %s. Ban reason: %s - %s ago' % (vhost, reason if reason else 'No reason', utils.get_timespan(date)))
339
340 is_blacklisted, reason, int = self.__is_blacklisted(vhost)
341 if is_blacklisted:
342 return (False, reason, int)
343
344 is_resolvable, resolved, host = self.__is_resolvable(vhost)
345 if is_resolvable:
346 return (False,
347 'Your vHost resolves. %s' % RULES,
348 'vHost resolves (%s => %s)' % (host, resolved))
349
350 for req in self.list.values():
351 if main == req['main'] and nick != req['nick']:
352 return (False,
353 'You already have a pending vHost request for a grouped nickname. Please wait for it to be reviewed before requesting a new one.',
354 'Has a pending request with grouped nick %s' % req['nick'])
355
356 return (True, '@c9Acceptable@c', None)
357
358 def __is_resolvable(self, host):
359 try:
360 res = getaddrinfo(host, 80)
361 return (True, res[0][4][0], host)
362 except gaierror, e:
363 return (False, None, None)
364 except UnicodeError, e:
365 return (False, None, None)
366
367 def __is_banned(self, nick):
368 nick = nick.lower()
369 if nick in self.banlist:
370 b = self.banlist[nick]
371 return (True, b.reason, b.banned_by, b.date)
372
373 return (False, None, None, None)
374
375 def __is_blacklisted(self, host):
376 h = host.lower()
377 for b in self.blacklist:
378 if (fnmatch(h, b.vhost.lower())):
379 return (True, RULES, 'Host matches %s (%s)' % (b.vhost, host))
380
381 return (False, None, None)
382
383 def __is_suspicious(self, host):
384 h = host.lower()
385 for b in self.suspicious:
386 if (fnmatch(h, b.vhost.lower())):
387 return 'matches @c10%s@c%s' % (b.vhost, ' (%s)' % b.reason if b.reason else '')
388
389 return False