Limnoria/plugins/Unix/plugin.py

259 lines
9.6 KiB
Python

###
# Copyright (c) 2002-2005, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import os
import re
import pwd
import sys
import crypt
import errno
import popen2
import random
import select
import struct
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
def progstats():
pw = pwd.getpwuid(os.getuid())
response = format('Process ID %i running as user %q and as group %q '
'from directory %q with the command line %q. '
'Running on Python %s.',
os.getpid(), pw[0], pw[3],
os.getcwd(), ' '.join(sys.argv),
sys.version.translate(utils.str.chars, '\r\n'))
return response
class TimeoutError(IOError):
pass
def pipeReadline(fd, timeout=2):
(r, _, _) = select.select([fd], [], [], timeout)
if r:
return r[0].readline()
else:
raise TimeoutError
class Unix(callbacks.Plugin):
def errno(self, irc, msg, args, s):
"""<error number or code>
Returns the number of an errno code, or the errno code of a number.
"""
try:
i = int(s)
name = errno.errorcode[i]
except ValueError:
name = s.upper()
try:
i = getattr(errno, name)
except AttributeError:
irc.reply('I can\'t find the errno number for that code.')
return
except KeyError:
name = '(unknown)'
irc.reply(format('%s (#%i): %s', name, i, os.strerror(i)))
errno = wrap(errno, ['something'])
def progstats(self, irc, msg, args):
"""takes no arguments
Returns various unix-y information on the running supybot process.
"""
irc.reply(progstats())
def pid(self, irc, msg, args):
"""takes no arguments
Returns the current pid of the process for this Supybot.
"""
irc.reply(format('%i', os.getpid()), private=True)
pid = wrap(pid, [('checkCapability', 'owner')])
_cryptre = re.compile(r'[./0-9A-Za-z]')
def crypt(self, irc, msg, args, password, salt):
"""<password> [<salt>]
Returns the resulting of doing a crypt() on <password> If <salt> is
not given, uses a random salt. If running on a glibc2 system,
prepending '$1$' to your salt will cause crypt to return an MD5sum
based crypt rather than the standard DES based crypt.
"""
def makeSalt():
s = '\x00'
while self._cryptre.sub('', s) != '':
s = struct.pack('<h', random.randrange(2**16))
return s
if not salt:
salt = makeSalt()
irc.reply(crypt.crypt(password, salt))
crypt = wrap(crypt, ['something', additional('something')])
def spell(self, irc, msg, args, word):
"""<word>
Returns the result of passing <word> to aspell/ispell. The results
shown are sorted from best to worst in terms of being a likely match
for the spelling of <word>.
"""
# We are only checking the first word
spellCmd = self.registryValue('spell.command')
if not spellCmd:
irc.error('A spell checking command doesn\'t seem to be '
'installed on this computer. If one is installed, '
'reconfigure supybot.plugins.Unix.spell.command '
'appropriately.', Raise=True)
if word and not word[0].isalpha():
irc.error('<word> must begin with an alphabet character.')
return
if ' ' in word:
irc.error('Spaces aren\'t allowed in the word.')
return
inst = popen2.Popen4([spellCmd, '-a'])
(r, w) = (inst.fromchild, inst.tochild)
try:
s = r.readline() # Banner, hopefully.
if 'sorry' in s.lower():
irc.error(s)
return
w.write(word)
w.write('\n')
w.flush()
try:
line = pipeReadline(r)
# aspell puts extra whitespace, ignore it
while not line.strip('\r\n'):
line = pipeReadline(r)
# cache an extra line in case aspell's first line says the word
# is spelled correctly, but subsequent lines offer spelling
# suggestions
line2 = pipeReadline(r)
except TimeoutError:
irc.error('The spell command timed out.')
return
finally:
r.close()
w.close()
inst.wait()
# parse the output
# aspell will sometimes list spelling suggestions after a '*' or '+'
# line for complex words.
if line[0] in '*+' and line2.strip('\r\n'):
line = line2
if line[0] in '*+':
resp = format('%q may be spelled correctly.', word)
elif line[0] == '#':
resp = format('I could not find an alternate spelling for %q',word)
elif line[0] == '&':
matches = line.split(':')[1].strip()
resp = format('Possible spellings for %q: %L.',
word, matches.split(', '))
else:
resp = 'Something unexpected was seen in the [ai]spell output.'
irc.reply(resp)
spell = wrap(spell, ['something'])
def fortune(self, irc, msg, args):
"""takes no arguments
Returns a fortune from the *nix fortune program.
"""
fortuneCmd = self.registryValue('fortune.command')
if fortuneCmd:
args = [fortuneCmd]
if self.registryValue('fortune.short'):
args.append('-s')
if self.registryValue('fortune.equal'):
args.append('-e')
if self.registryValue('fortune.offensive'):
args.append('-a')
args.extend(self.registryValue('fortune.files'))
inst = popen2.Popen4(args)
(r, w) = (inst.fromchild, inst.tochild)
try:
lines = r.readlines()
lines = map(str.rstrip, lines)
lines = filter(None, lines)
if lines:
irc.replies(lines, joiner=' ')
else:
irc.error('It seems the configured fortune command was '
'not available.')
finally:
w.close()
r.close()
inst.wait()
else:
irc.error('I couldn\'t find the fortune command on this system. '
'If it is installed on this system, reconfigure the '
'supybot.plugins.Unix.fortune.command configuration '
'variable appropriately.')
def wtf(self, irc, msg, args, _, something):
"""[is] <something>
Returns wtf <something> is. 'wtf' is a *nix command that first
appeared in NetBSD 1.5. In most *nices, it's available in some sort
of 'bsdgames' package.
"""
wtfCmd = self.registryValue('wtf.command')
if wtfCmd:
def commandError():
irc.error('It seems the configured wtf command '
'was not available.')
something = something.rstrip('?')
inst = popen2.Popen4([wtfCmd, something])
(r, w) = (inst.fromchild, inst.tochild)
try:
response = utils.str.normalizeWhitespace(r.readline().strip())
if response:
irc.reply(response)
else:
commandError()
finally:
r.close()
w.close()
inst.wait()
else:
irc.error('I couldn\'t find the wtf command on this system. '
'If it is installed on this system, reconfigure the '
'supybot.plugins.Unix.wtf.command configuration '
'variable appropriately.')
wtf = wrap(wtf, [optional(('literal', ['is'])), 'something'])
Class = Unix
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: