2 from datetime
import datetime
3 from fnmatch
import fnmatch
4 from operator
import itemgetter
5 from socket
import getaddrinfo
, gaierror
9 def __init__(self
, row
):
10 self
.nick
, self
.reason
, self
.banned_by
, self
.date
= row
12 class Blacklist(object):
13 def __init__(self
, row
):
14 self
.vhost
, self
.added_by
, self
.reason
, self
.date
= row
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
20 class RejectedRequest(Request
):
21 def __init__(self
, row
):
22 Request
.__init
__(self
, row
[:-1])
25 class Suspicious(object):
26 def __init__(self
, row
):
27 self
.vhost
, self
.added_by
, self
.reason
, self
.date
= row
29 URL
= 'http://s.rizon.net/vhost'
30 RULES
= 'For basic vhost rules and restrictions, see: %s' % URL
32 class RequestManager(object):
33 def __init__(self
, module
):
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 ,
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
)
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()]
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()]
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 ,
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()]
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 ,
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()]
90 def add(self
, vhost
, nick
, main
=None):
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']:
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
)
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' % {
108 'int' : ' %s' % internal
if internal
else ''})
110 t
= (nick
, main
, vhost
, 'py-moo', 0, now
, rating
)
111 r
= RejectedRequest(t
)
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
))
117 self
.rejections
.append(r
)
120 if nick
.lower() in self
.list:
121 if self
.list[nick
.lower()]['vhost'] == vhost
:
122 return # don't repeat if no change
124 id = self
.list[nick
.lower()]['id'] # replace the old request with the new vhost
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' % {
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 ''}))
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
)
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
))
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
))
158 self
.module
.msg(self
.module
.chan
, '@b%s@b not found in blacklisted vHost list' % vhost
)
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
)
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
))
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
))
180 self
.module
.msg(self
.module
.chan
, '@b%s@b not found in suspicious vHost list' % vhost
)
182 def __get_nick(self
, list, s
):
186 for k
, v
in list.iteritems():
187 if k
== str(s
).lower() or str(v
['id']) == str(s
):
192 def __get_last_request(self
, nick
):
194 for r
in reversed(self
.requests
):
195 if r
.main
.lower() == nick
or r
.nickname
.lower() == nick
:
205 sorted_list
= sorted(map(itemgetter('id'), self
.list.values()))
206 for req
in sorted_list
:
212 return len(sorted_list
) + 1
214 def approve(self
, s
, resolved_by
, silent
=False):
215 nick
= self
.__get
_nick
(self
.list, s
)
217 self
.module
.msg(self
.module
.chan
, 'No pending vHost request for nick/id @b%s@b' % s
)
220 req
= self
.list[nick
]
222 t
= (req
['nick'], req
['main'], req
['vhost'], resolved_by
, (now
- req
['date']).seconds
, now
)
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
229 self
.requests
.append(r
)
233 return # was already manually approved
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.')
239 def ban(self
, nick
, banned_by
, reason
):
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
))
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
)
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' % {
262 'sep' : '@b@c%s::@o' % sepc
,
266 'appr' : r
.resolved_by
,
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 ''})
273 self
.module
.msg(self
.module
.chan
, 'No previous requests by nick @b%s@b' % nick
)
275 def reject(self
, s
, resolved_by
, reason
=None, internal
=None):
276 nick
= self
.__get
_nick
(self
.list, s
)
278 self
.module
.msg(self
.module
.chan
, 'No pending vHost request for nick/id @b%s@b' % s
)
281 req
= self
.list[nick
]
282 self
.module
.msg('HostServ', 'REJECT %s %s' % (req
['nick'], reason
if reason
else RULES
))
284 self
.module
.msg(self
.module
.chan
, 'Rejected vhost for @b%(nick)s@b.%(int)s' % {
286 'int' : ' %s' % internal
if internal
else ''})
289 t
= (req
['nick'], req
['main'], req
['vhost'], resolved_by
, (now
- req
['date']).seconds
, now
, reason
if reason
else RULES
)
290 r
= RejectedRequest(t
)
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
))
296 self
.rejections
.append(r
)
298 def delete(self
, nick
): # remove this request from pending requests, used when manually approving/rejecting with hostserv
299 del self
.list[nick
.lower()]
301 def search(self
, vhost
):
302 list = [req
for req
in self
.requests
if req
.vhost
.lower() == vhost
.lower()]
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' % {
312 'appr': r
.resolved_by
})
314 self
.module
.msg(self
.module
.chan
, 'vHost @b%s@b was never requested' % vhost
)
316 def unban(self
, nick
):
317 is_banned
, reason
, by
, date
= self
.__is
_banned
(nick
)
319 self
.module
.msg(self
.module
.chan
, 'No bans found for nick @b%s@b' % nick
)
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
)
326 def __verify(self
, nick
, vhost
, main
=None):
327 is_banned
, reason
, by
, date
= self
.__is
_banned
(nick
)
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
)))
333 if main
and main
!= nick
:
334 is_banned
, reason
, by
, date
= self
.__is
_banned
(main
)
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
)))
340 is_blacklisted
, reason
, int = self
.__is
_blacklisted
(vhost
)
342 return (False, reason
, int)
344 is_resolvable
, resolved
, host
= self
.__is
_resolvable
(vhost
)
347 'Your vHost resolves. %s' % RULES
,
348 'vHost resolves (%s => %s)' % (host
, resolved
))
350 for req
in self
.list.values():
351 if main
== req
['main'] and nick
!= req
['nick']:
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'])
356 return (True, '@c9Acceptable@c', None)
358 def __is_resolvable(self
, host
):
360 res
= getaddrinfo(host
, 80)
361 if res
[0][4][0] == '127.0.53.53': # https://www.icann.org/news/announcement-2-2014-08-01-en
362 return (False, None, None)
363 return (True, res
[0][4][0], host
)
365 return (False, None, None)
366 except UnicodeError, e
:
367 return (False, None, None)
369 def __is_banned(self
, nick
):
371 if nick
in self
.banlist
:
372 b
= self
.banlist
[nick
]
373 return (True, b
.reason
, b
.banned_by
, b
.date
)
375 return (False, None, None, None)
377 def __is_blacklisted(self
, host
):
379 for b
in self
.blacklist
:
380 if (fnmatch(h
, b
.vhost
.lower())):
381 return (True, RULES
, 'Host matches %s (%s)' % (b
.vhost
, host
))
383 return (False, None, None)
385 def __is_suspicious(self
, host
):
387 for b
in self
.suspicious
:
388 if (fnmatch(h
, b
.vhost
.lower())):
389 return 'matches @c10%s@c%s' % (b
.vhost
, ' (%s)' % b
.reason
if b
.reason
else '')