PackageInfo: Various improvements (by Valentin Lorentz)

* Add depends command.
* Improve handling of find and info.
* Fix crash by double reloading.
* Fix shell code injection vulnerability.
This commit is contained in:
Krytarik Raido 2017-05-27 04:45:04 +02:00
parent 075b45ce9f
commit 4956995f9a
3 changed files with 102 additions and 20 deletions

View File

@ -22,7 +22,7 @@ import supybot
import supybot.world as world
from imp import reload
__version__ = "1.0.0"
__version__ = "1.1.0"
__author__ = supybot.Author("Krytarik Raido", "krytarik", "krytarik@tuxgarage.com")
__contributors__ = {
supybot.Author("Dennis Kaarsemaker", "Seveas", "dennis@kaarsemaker.net"): ['Original Concept'],

View File

@ -35,6 +35,23 @@ def description(pkg):
return pkg['Description'].split('\n')[0]
return None
def apt_cache(aptdir, distro, extra):
return subprocess.check_output(['apt-cache',
'-oDir::State::Lists=%s/%s' % (aptdir, distro),
'-oDir::State::Status=%s/%s.status' % (aptdir, distro),
'-oDir::Etc::SourceList=%s/%s.list' % (aptdir, distro),
'-oDir::Etc::SourceParts=""',
'-oDir::Cache=%s/cache' % aptdir,
'-oAPT::Architecture=i386'] +
extra).decode('utf8')
def apt_file(aptdir, distro, pkg):
return subprocess.check_output(['apt-file',
'-s', '%s/%s.list' % (aptdir, distro),
'-c', '%s/apt-file/%s' % (aptdir, distro),
'-l', '-a', 'i386',
'search', pkg]).decode('utf8')
class Apt:
def __init__(self, plugin):
self.aptdir = plugin.registryValue('aptdir')
@ -44,15 +61,6 @@ class Apt:
os.environ["LANG"] = "C"
if self.aptdir:
self.distros = sorted([x[:-5] for x in os.listdir(self.aptdir) if x.endswith('.list')])
self.aptcommand = """apt-cache\\
-o"Dir::State::Lists=%s/%%s"\\
-o"Dir::etc::sourcelist=%s/%%s.list"\\
-o"Dir::etc::SourceParts=%s/%%s.list.d"\\
-o"Dir::State::status=%s/%%s.status"\\
-o"Dir::Cache=%s/cache"\\
-o"APT::Architecture=i386"\\
%%s %%s""" % tuple([self.aptdir]*5)
self.aptfilecommand = """apt-file -s %s/%%s.list -c %s/apt-file/%%s -l -a i386 search %%s""" % (self.aptdir, self.aptdir)
def find(self, pkg, chkdistro, filelookup=True):
_pkg = ''.join([x for x in pkg.strip().split(None,1)[0] if x.isalnum() or x in '.-_+/'])
@ -65,10 +73,16 @@ class Apt:
return "%s is not a valid distribution: %s" % (distro, ", ".join(self.distros))
pkg = _pkg
data = subprocess.getoutput(self.aptcommand % (distro, distro, distro, distro, 'search -n', pkg))
try:
data = apt_cache(self.aptdir, distro, ['search', '-n', pkg])
except subprocess.CalledProcessError as e:
data = e.output
if not data:
if filelookup:
data = subprocess.getoutput(self.aptfilecommand % (distro, distro, pkg)).split()
try:
data = apt_file(self.aptdir, distro, pkg).split()
except subprocess.CalledProcessError as e:
data = e.output
if data:
if data[0] == 'sh:': # apt-file isn't installed
self.log.error("PackageInfo/packages: apt-file is not installed")
@ -84,13 +98,13 @@ class Apt:
return "File %s found in %s" % (pkg, ', '.join(data))
return 'Package/file %s does not exist in %s' % (pkg, distro)
return "No packages matching '%s' could be found" % pkg
pkgs = [x.split()[0] for x in data.split('\n')]
pkgs = [x.split()[0] for x in data.split('\n') if x]
if len(pkgs) > 10:
return "Found: %s (and %d others) http://packages.ubuntu.com/search?keywords=%s&searchon=names&suite=%s&section=all" % (', '.join(pkgs[:10]), len(pkgs)-10, utils.web.urlquote(pkg), distro)
else:
return "Found: %s" % ', '.join(pkgs[:5])
def info(self, pkg, chkdistro):
def raw_info(self, pkg, chkdistro):
if not pkg.strip():
return ''
_pkg = ''.join([x for x in pkg.strip().split(None,1)[0] if x.isalnum() or x in '.-_+'])
@ -102,8 +116,14 @@ class Apt:
pkg = _pkg
data = subprocess.getoutput(self.aptcommand % (distro, distro, distro, distro, 'show', pkg))
data2 = subprocess.getoutput(self.aptcommand % (distro, distro, distro, distro, 'showsrc', pkg))
try:
data = apt_cache(self.aptdir, distro, ['show', pkg])
except subprocess.CalledProcessError as e:
data = e.output
try:
data2 = apt_cache(self.aptdir, distro, ['showsrc', pkg])
except subprocess.CalledProcessError as e:
data2 = e.output
if not data or 'E: No packages found' in data:
return 'Package %s does not exist in %s' % (pkg, distro)
maxp = {'Version': '0~'}
@ -151,10 +171,24 @@ class Apt:
if archs:
archs = ' (Only available for %s)' % '; '.join(archs)
maxp["Distrobution"] = distro
maxp["Distribution"] = distro
maxp["Architectures"] = archs
return maxp
def info(self, pkg, chkdistro):
maxp = self.raw_info(pkg, chkdistro)
if isinstance(maxp, str):
return maxp
return("%s (source: %s): %s. In component %s, is %s. Version %s (%s), package size %s kB, installed size %s kB%s" %
(maxp['Package'], maxp['Source'] or maxp['Package'], description(maxp), component(maxp['Section']),
maxp['Priority'], maxp['Version'], distro, int(maxp['Size'])/1024, maxp['Installed-Size'], archs))
maxp['Priority'], maxp['Version'], maxp["Distribution"], int(maxp['Size'])/1024, maxp['Installed-Size'], maxp["Architectures"]))
def depends(self, pkg, chkdistro):
maxp = self.raw_info(pkg, chkdistro)
if isinstance(maxp, str):
return maxp
return("%s (version %s in %s) depends on: %s" %
(maxp['Package'], maxp["Version"], maxp["Distribution"], maxp["Depends"]))
# Simple test
if __name__ == "__main__":
@ -189,6 +223,8 @@ if __name__ == "__main__":
aptlookup = Apt(plugin)
if command == "find":
print(aptlookup.find(lookup, dists))
elif command == "depends":
print(aptlookup.depends(lookup, dists))
else:
print(aptlookup.info(lookup, dists))

View File

@ -26,9 +26,7 @@ import supybot.conf as conf
import os
import re
import time
from imp import reload
from . import packages
reload(packages)
def get_user(msg):
try:
@ -155,6 +153,50 @@ class PackageInfo(callbacks.Plugin):
info = wrap(real_info, ['anything', optional('text')])
def real_depends(self, irc, msg, args, package, release):
"""<package> [<release>]
Lookup dependencies for <package>, optionally in <release>
"""
channel = self.__getChannel(msg.args[0])
reply_target = ircutils.replyTo(msg)
(release, rest) = self.__getRelease(irc, release, channel)
if not release:
return
reply = self.Apt.depends(package, release)
if rest:
if rest[0] == '|':
try:
target = rest
while target[0] == '|':
target = target[1:].strip()
if target.lower() == "me":
target = msg.nick
queue(irc, reply_target, "%s: %s" % (target, reply))
return
except Exception as e:
self.log.info("PackageInfo: (depends) Exception in pipe: %r" % e)
pass
elif rest[0] == '>':
try:
while rest[0] == '>':
rest = rest[1:].strip()
targets = [_ for _ in rest.split() if _] # Split and discard empty parts
target = stripNick(targets[0]) # Take the first "nick" and strip off bad chars
if target.lower() == "me":
target = msg.nick # redirect
if not target: # Throw error
raise Exception('No target')
queue(irc, target, "<%s> wants you to know: %s" % (msg.nick, reply))
return
except Exception as e:
self.log.info("PackageInfo: (depends) Exception in redirect: %r" % e)
pass
queue(irc, reply_target, reply)
depends = wrap(real_depends, ['anything', optional('text')])
def real_find(self, irc, msg, args, package, release):
"""<package/filename> [<release>]
@ -216,6 +258,8 @@ class PackageInfo(callbacks.Plugin):
(term, rest) = (rest.split(' ', 1) + [None])[:2]
if cmd == "find":
self.real_find(irc, msg, [], term, rest)
elif cmd == "depends":
self.real_depends(irc, msg, [], term, rest)
else:
self.real_info(irc, msg, [], term, rest)
@ -234,6 +278,8 @@ class PackageInfo(callbacks.Plugin):
(term, rest) = (rest.split(' ', 1) + [None])[:2]
if cmd == "find":
self.real_find(irc, msg, [], term, rest)
elif cmd == "depends":
self.real_depends(irc, msg, [], term, rest)
else:
self.real_info(irc, msg, [], term, rest)