Skip to content
Browse files

SECURITY: Strip "\x01" out of commands being sent to the server. Only…

… things excluded are the .write for the owner and .me for admins

Hard coded in a few IRC codes for jenni not to report to #jenni-log
Made slight improvement on speed when raw logging is turned on.
Added full paths for raw logging.
Moved around safe() in irc.py to make it useful in several locations.
Optimized throttling for jenni and reporting to #jenni-log (the throttling for each runs in two different stacks)
Updated Copyright and header documentation for 'jenni' and bot.py
Added new option in the default.py configuration file to have the ability to turn on/off raw logging in log/raw.log
Added ability for 'owner' only to send RAW irc commands to the server. (.write)
Fixed indentation in admin.py
Updated path for unobot.py, fixes #53
  • Loading branch information...
1 parent 20eaf04 commit 4dae5754fd63a278274a4aa08d48627111742914 @myano committed Oct 7, 2012
Showing with 139 additions and 86 deletions.
  1. +8 −3 bot.py
  2. +79 −50 irc.py
  3. +7 −1 jenni
  4. +44 −31 modules/admin.py
  5. +1 −1 modules/unobot.py
View
11 bot.py
@@ -2,9 +2,12 @@
"""
bot.py - Jenni IRC Bot
Copyright 2008, Sean B. Palmer, inamidst.com
+Modified by: Michael Yanovich
Licensed under the Eiffel Forum License 2.
-http://inamidst.com/phenny/
+More info:
+ * Jenni: https://github.com/myano/jenni/
+ * Phenny: http://inamidst.com/phenny/
"""
import time, sys, os, re, threading, imp
@@ -24,7 +27,9 @@ class Jenni(irc.Bot):
def __init__(self, config):
if hasattr(config, "logchan_pm"): lc_pm = config.logchan_pm
else: lc_pm = None
- args = (config.nick, config.name, config.channels, config.password, lc_pm)
+ if hasattr(config, "logging"): logging = config.logging
+ else: logging = False
+ args = (config.nick, config.name, config.channels, config.password, lc_pm, logging)
irc.Bot.__init__(self, *args)
self.config = config
self.doc = {}
@@ -118,7 +123,7 @@ def sub(pattern, self=self):
if not hasattr(func, 'rate'):
if hasattr(func, 'commands'):
- func.rate = 20
+ func.rate = 0
else:
func.rate = 0
View
129 irc.py
@@ -2,6 +2,7 @@
"""
irc.py - A Utility IRC Bot
Copyright 2008, Sean B. Palmer, inamidst.com
+Modified by: Michael Yanovich
Licensed under the Eiffel Forum License 2.
More info:
@@ -13,6 +14,9 @@
import socket, asyncore, asynchat
import os, codecs
+IRC_CODES = ('251', '252', '254', '255', '265', '266', '250', '333', '353', '366', '372', '375', '376', 'QUIT', 'NICK')
+cwd = os.getcwd()
+
class Origin(object):
source = re.compile(r'([^!]*)!?([^@]*)@?(.*)')
@@ -28,20 +32,20 @@ def __init__(self, bot, source, args):
self.sender = mappings.get(target, target)
def create_logdir():
- try: os.mkdir("logs")
+ try: os.mkdir(cwd + "/logs")
except Exception, e:
print >> sys.stderr, 'There was a problem creating the logs directory.'
print >> sys.stderr, e.__class__, str(e)
print >> sys.stderr, 'Please fix this and then run jenni again.'
sys.exit(1)
def check_logdir():
- if not os.path.isdir("logs"):
+ if not os.path.isdir(cwd + "/logs"):
create_logdir()
def log_raw(line):
check_logdir()
- f = codecs.open("logs/raw.log", 'a', encoding='utf-8')
+ f = codecs.open(cwd + "/logs/raw.log", 'a', encoding='utf-8')
f.write(str(time.time()) + "\t")
temp = line.replace('\n', '')
try:
@@ -56,7 +60,7 @@ def log_raw(line):
f.close()
class Bot(asynchat.async_chat):
- def __init__(self, nick, name, channels, password=None, logchan_pm=None):
+ def __init__(self, nick, name, channels, password=None, logchan_pm=None, logging=False):
asynchat.async_chat.__init__(self)
self.set_terminator('\n')
self.buffer = ''
@@ -67,43 +71,56 @@ def __init__(self, nick, name, channels, password=None, logchan_pm=None):
self.password = password
self.verbose = True
- self.channels = channels or []
- self.stack = []
+ self.channels = channels or list()
+ self.stack = list()
+ self.stack_log = list()
self.logchan_pm = logchan_pm
+ self.logging = logging
import threading
self.sending = threading.RLock()
# def push(self, *args, **kargs):
# asynchat.async_chat.push(self, *args, **kargs)
- def __write(self, args, text=None):
+ def __write(self, args, text=None, raw=False):
# print '%r %r %r' % (self, args, text)
try:
- if text is not None:
- # 510 because CR and LF count too, as nyuszika7h points out
- temp = (' '.join(args) + ' :' + text)[:510] + '\r\n'
- else:
- temp = ' '.join(args)[:510] + '\r\n'
- log_raw(temp)
- self.push(temp)
+ if raw:
+ temp = ' '.join(args)[:510] + " :" + text + '\r\n'
+ self.push(temp)
+ elif not raw:
+ if text:
+ # 510 because CR and LF count too, as nyuszika7h points out
+ temp = (' '.join(args) + ' :' + text)[:510] + '\r\n'
+ else:
+ temp = ' '.join(args)[:510] + '\r\n'
+ self.push(temp)
+ if self.logging:
+ log_raw(temp)
except IndexError:
print "INDEXERROR", text
#pass
- def write(self, args, text=None):
- # This is a safe version of __write
- def safe(input):
- input = input.replace('\n', '')
- input = input.replace('\r', '')
- return input.encode('utf-8')
+ def write(self, args, text=None, raw=False):
try:
- args = [safe(arg) for arg in args]
+ args = [self.safe(arg, u=True) for arg in args]
if text is not None:
- text = safe(text)
- self.__write(args, text)
+ text = self.safe(text, u=True)
+ if raw:
+ self.__write(args, text, raw)
+ else:
+ self.__write(args, text)
except Exception, e: pass
+ def safe(self, input, u=False):
+ if input:
+ input = input.replace('\n', '')
+ input = input.replace('\r', '')
+ if u:
+ input = input.encode('utf-8')
+ return input
+
def run(self, host, port=6667):
self.initiate_connect(host, port)
@@ -130,13 +147,15 @@ def handle_close(self):
print >> sys.stderr, 'Closed!'
def collect_incoming_data(self, data):
- if data:
- log_raw(data)
- if hasattr(self, "logchan_pm") and self.logchan_pm and ("PRIVMSG" in data or "NOTICE" in data):
- if len(data.split()) > 1:
- if "#" not in data.split()[2]:
- self.msg(self.logchan_pm, data)
self.buffer += data
+ if data:
+ if self.logchan_pm:
+ dlist = data.split()
+ if len(dlist) >= 3:
+ if "#" not in dlist[2] and dlist[1].strip() not in IRC_CODES:
+ self.msg(self.logchan_pm, data, True)
+ if self.logging:
+ log_raw(data)
def found_terminator(self):
line = self.buffer
@@ -163,7 +182,7 @@ def found_terminator(self):
def dispatch(self, origin, args):
pass
- def msg(self, recipient, text):
+ def msg(self, recipient, text, log=False, x=False):
self.sending.acquire()
# Cf. http://swhack.com/logs/2006-03-01#T19-43-25
@@ -176,30 +195,40 @@ def msg(self, recipient, text):
except UnicodeEncodeError, e:
return
+ if not x:
+ text = text.replace('\x01', '')
+
# No messages within the last 3 seconds? Go ahead!
# Otherwise, wait so it's been at least 0.8 seconds + penalty
- if self.stack:
- elapsed = time.time() - self.stack[-1][0]
- if elapsed < 3:
- penalty = float(max(0, len(text) - 50)) / 70
- wait = 0.8 + penalty
- if elapsed < wait:
- time.sleep(wait - elapsed)
+ def wait(sk, txt):
+ if sk:
+ elapsed = time.time() - sk[-1][0]
+ if elapsed < 3:
+ penalty = float(max(0, len(txt) - 50)) / 70
+ wait = 0.8 + penalty
+ if elapsed < wait:
+ time.sleep(wait - elapsed)
+ if log:
+ wait(self.stack_log, text)
+ else:
+ wait(self.stack, text)
# Loop detection
- messages = [m[1] for m in self.stack[-8:]]
- if messages.count(text) >= 5:
- text = '...'
- if messages.count('...') >= 3:
- self.sending.release()
- return
-
- def safe(input):
- input = input.replace('\n', '')
- return input.replace('\r', '')
- self.__write(('PRIVMSG', safe(recipient)), safe(text))
- self.stack.append((time.time(), text))
+ if not log:
+ messages = [m[1] for m in self.stack[-8:]]
+ if messages.count(text) >= 5:
+ text = '...'
+ if messages.count('...') >= 3:
+ self.sending.release()
+ return
+
+ self.__write(('PRIVMSG', self.safe(recipient)), self.safe(text))
+ if log:
+ self.stack_log.append((time.time(), text))
+ else:
+ self.stack.append((time.time(), text))
self.stack = self.stack[-10:]
+ self.stack_log = self.stack_log[-10:]
self.sending.release()
@@ -239,5 +268,5 @@ def main():
# bot.run('irc.freenode.net')
print __doc__
-if __name__=="__main__":
+if __name__ == "__main__":
main()
View
8 jenni
@@ -2,9 +2,12 @@
"""
jenni - An IRC Bot
Copyright 2008, Sean B. Palmer, inamidst.com
+Modified by: Michael Yanovich
Licensed under the Eiffel Forum License 2.
-http://inamidst.com/phenny/
+More info:
+ * Jenni: https://github.com/myano/jenni/
+ * Phenny: http://inamidst.com/phenny/
Note: DO NOT EDIT THIS FILE.
Run ./jenni, then edit ~/.jenni/default.py
@@ -42,6 +45,9 @@ def create_default_config(fn):
# But admin.py is disabled by default, as follows:
exclude = ['admin', 'adminchannel', 'ai', 'resp', 'school']
+ # Enable raw logging of everything jenni sees
+ logging = False
+
# Block modules from specific channels
# To not block anything for a channel, just don't mention it
excludes = {
View
75 modules/admin.py
@@ -12,54 +12,55 @@
import os
def join(jenni, input):
- """Join the specified channel. This is an admin-only command."""
- # Can only be done in privmsg by an admin
- if input.sender.startswith('#'): return
- if input.admin:
- channel, key = input.group(1), input.group(2)
- if not key:
- jenni.write(['JOIN'], channel)
- else: jenni.write(['JOIN', channel, key])
+ """Join the specified channel. This is an admin-only command."""
+ # Can only be done in privmsg by an admin
+ if input.sender.startswith('#'): return
+ if input.admin:
+ channel, key = input.group(1), input.group(2)
+ if not key:
+ jenni.write(['JOIN'], channel)
+ else: jenni.write(['JOIN', channel, key])
join.rule = r'\.join (#\S+)(?: *(\S+))?'
join.priority = 'low'
join.example = '.join #example or .join #example key'
def part(jenni, input):
- """Part the specified channel. This is an admin-only command."""
- # Can only be done in privmsg by an admin
- if input.sender.startswith('#'): return
- if input.admin:
- jenni.write(['PART'], input.group(2))
+ """Part the specified channel. This is an admin-only command."""
+ # Can only be done in privmsg by an admin
+ if input.sender.startswith('#'): return
+ if input.admin:
+ jenni.write(['PART'], input.group(2))
part.commands = ['part']
part.priority = 'low'
part.example = '.part #example'
def quit(jenni, input):
- """Quit from the server. This is an owner-only command."""
- # Can only be done in privmsg by the owner
- if input.sender.startswith('#'): return
- if input.owner:
- jenni.write(['QUIT'])
- __import__('os')._exit(0)
+ """Quit from the server. This is an owner-only command."""
+ # Can only be done in privmsg by the owner
+ if input.sender.startswith('#'): return
+ if input.owner:
+ jenni.write(['QUIT'])
+ __import__('os')._exit(0)
quit.commands = ['quit']
quit.priority = 'low'
def msg(jenni, input):
- # Can only be done in privmsg by an admin
- if input.sender.startswith('#'): return
- a, b = input.group(2), input.group(3)
- if (not a) or (not b): return
- if input.admin:
- jenni.msg(a, b)
+ # Can only be done in privmsg by an admin
+ if input.sender.startswith('#'): return
+ a, b = input.group(2), input.group(3)
+ if (not a) or (not b): return
+ if input.admin:
+ jenni.msg(a, b)
msg.rule = (['msg'], r'(#?\S+) (.+)')
msg.priority = 'low'
def me(jenni, input):
- # Can only be done in privmsg by an admin
- if input.sender.startswith('#'): return
- if input.admin:
- msg = '\x01ACTION %s\x01' % input.group(3)
- jenni.msg(input.group(2), msg)
+ # Can only be done in privmsg by an admin
+ if input.sender.startswith('#'): return
+ if input.admin:
+ if input.group(2) and input.group(3):
+ msg = '\x01ACTION %s\x01' % input.group(3)
+ jenni.msg(input.group(2), msg, x=True)
me.rule = (['me'], r'(#?\S+) (.*)')
me.priority = 'low'
@@ -175,6 +176,18 @@ def blocks(jenni, input):
blocks.priority = 'low'
blocks.thread = False
+def write_raw(jenni, input):
+ if not input.owner: return
+ txt = input.bytes[7:]
+ jenni.reply(txt)
+ txt = txt.encode('utf-8')
+ a = txt.split(":")
+ jenni.reply(str(a))
+ jenni.write([a[0].strip()],a[1].strip(),True)
+write_raw.commands = ['write']
+write_raw.priority = 'high'
+write_raw.thread = False
+
if __name__ == '__main__':
- print __doc__.strip()
+ print __doc__.strip()
View
2 modules/unobot.py
@@ -37,7 +37,7 @@
# Remember to change these 3 lines or nothing will work
CHANNEL = '##uno'
-SCOREFILE = "/home/jenni/dev/unoscores.txt"
+SCOREFILE = "/home/jenni/jenni/unoscores.txt"
# Only the owner (starter of the game) can call .unostop to stop the game.
# But this calls for a way to allow others to stop it after the game has been idle for a while.
# After this set time, anyone can stop the game via .unostop

0 comments on commit 4dae575

Please sign in to comment.
Something went wrong with that request. Please try again.