aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/ChangeFile.py109
-rw-r--r--lib/DebianSigVerifier.py35
-rw-r--r--lib/Dnotify.py199
-rwxr-xr-xlib/DpkgControl.py156
-rw-r--r--lib/DpkgDatalist.py81
-rw-r--r--lib/GPGSigVerifier.py78
-rw-r--r--lib/OrderedDict.py76
-rwxr-xr-xlib/SafeWriteFile.py81
-rwxr-xr-xlib/SignedFile.py107
-rw-r--r--lib/__init__.py1
-rw-r--r--lib/misc.py36
-rw-r--r--lib/version.py1
12 files changed, 960 insertions, 0 deletions
diff --git a/lib/ChangeFile.py b/lib/ChangeFile.py
new file mode 100644
index 0000000..b74e623
--- /dev/null
+++ b/lib/ChangeFile.py
@@ -0,0 +1,109 @@
+# ChangeFile
+
+# A class which represents a Debian change file.
+
+# Copyright 2002 Colin Walters <walters@gnu.org>
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import os, re, sys, string, stat, popen2
+import threading, Queue
+import logging
+from minidinstall import DpkgControl, SignedFile
+
+class ChangeFileException(Exception):
+ def __init__(self, value):
+ self._value = value
+ def __str__(self):
+ return `self._value`
+
+class ChangeFile(DpkgControl.DpkgParagraph):
+ def __init__(self):
+ DpkgControl.DpkgParagraph.__init__(self)
+ self._logger = logging.getLogger("mini-dinstall")
+
+ def load_from_file(self, filename):
+ f = SignedFile.SignedFile(open(filename))
+ self.load(f)
+ f.close()
+
+ def getFiles(self):
+ out = []
+ try:
+ files = self['files']
+ except KeyError:
+ return []
+ lineregexp = re.compile("^([0-9a-f]{32})[ \t]+(\d+)[ \t]+([-/a-zA-Z0-9]+)[ \t]+([-a-zA-Z0-9]+)[ \t]+([0-9a-zA-Z][-+:.,=~0-9a-zA-Z_]+)$")
+ for line in files:
+ if line == '':
+ continue
+ match = lineregexp.match(line)
+ if (match is None):
+ raise ChangeFileException("Couldn't parse file entry \"%s\" in Files field of .changes" % (line,))
+ out.append((match.group(1), match.group(2), match.group(3), match.group(4), match.group(5)))
+ return out
+
+ def verify(self, sourcedir):
+ for (md5sum, size, section, prioriy, filename) in self.getFiles():
+ self._verify_file_integrity(os.path.join(sourcedir, filename), int(size), md5sum)
+
+ def _verify_file_integrity(self, filename, expected_size, expected_md5sum):
+ self._logger.debug('Checking integrity of %s' % (filename,))
+ try:
+ statbuf = os.stat(filename)
+ if not stat.S_ISREG(statbuf[stat.ST_MODE]):
+ raise ChangeFileException("%s is not a regular file" % (filename,))
+ size = statbuf[stat.ST_SIZE]
+ except OSError, e:
+ raise ChangeFileException("Can't stat %s: %s" % (filename,e.strerror))
+ if size != expected_size:
+ raise ChangeFileException("File size for %s does not match that specified in .dsc" % (filename,))
+ if (self._get_file_md5sum(filename) != expected_md5sum):
+ raise ChangeFileException("md5sum for %s does not match that specified in .dsc" % (filename,))
+ self._logger.debug('Verified md5sum %s and size %s for %s' % (expected_md5sum, expected_size, filename))
+
+ def _get_file_md5sum(self, filename):
+ if os.access('/usr/bin/md5sum', os.X_OK):
+ cmd = '/usr/bin/md5sum %s' % (filename,)
+ self._logger.debug("Running: %s" % (cmd,))
+ child = popen2.Popen3(cmd, 1)
+ child.tochild.close()
+ erroutput = child.childerr.read()
+ child.childerr.close()
+ if erroutput != '':
+ child.fromchild.close()
+ raise ChangeFileException("md5sum returned error output \"%s\"" % (erroutput,))
+ (md5sum, filename) = string.split(child.fromchild.read(), None, 1)
+ child.fromchild.close()
+ status = child.wait()
+ if not (status is None or (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0)):
+ if os.WIFEXITED(status):
+ msg = "md5sum exited with error code %d" % (os.WEXITSTATUS(status),)
+ elif os.WIFSTOPPED(status):
+ msg = "md5sum stopped unexpectedly with signal %d" % (os.WSTOPSIG(status),)
+ elif os.WIFSIGNALED(status):
+ msg = "md5sum died with signal %d" % (os.WTERMSIG(status),)
+ raise ChangeFileException(msg)
+ return md5sum.strip()
+ import md5
+ f = open(filename)
+ md5sum = md5.new()
+ buf = f.read(8192)
+ while buf != '':
+ md5sum.update(buf)
+ buf = f.read(8192)
+ return md5sum.hexdigest()
+
+# vim:ts=4:sw=4:et:
diff --git a/lib/DebianSigVerifier.py b/lib/DebianSigVerifier.py
new file mode 100644
index 0000000..d441a58
--- /dev/null
+++ b/lib/DebianSigVerifier.py
@@ -0,0 +1,35 @@
+# DebianSigVerifier -*- mode: python; coding: utf-8 -*-
+
+# A class for verifying signed files, using Debian keys
+
+# Copyright © 2002 Colin Walters <walters@gnu.org>
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import os, re, sys, string, stat, logging
+from minidinstall.GPGSigVerifier import GPGSigVerifier
+
+class DebianSigVerifier(GPGSigVerifier):
+ _dpkg_ring = '/etc/dpkg/local-keyring.gpg'
+ def __init__(self, keyrings=None, extra_keyrings=None):
+ if keyrings is None:
+ keyrings = ['/usr/share/keyrings/debian-keyring.gpg', '/usr/share/keyrings/debian-keyring.pgp']
+ if os.access(self._dpkg_ring, os.R_OK):
+ keyrings.append(self._dpkg_ring)
+ if not extra_keyrings is None:
+ keyrings += extra_keyrings
+ GPGSigVerifier.__init__(self, keyrings)
+
+# vim:ts=4:sw=4:et:
diff --git a/lib/Dnotify.py b/lib/Dnotify.py
new file mode 100644
index 0000000..122e03c
--- /dev/null
+++ b/lib/Dnotify.py
@@ -0,0 +1,199 @@
+# Dnotify -*- mode: python; coding: utf-8 -*-
+
+# A simple FAM-like beast in Python
+
+# Copyright © 2002 Colin Walters <walters@gnu.org>
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import os, re, sys, string, stat, threading, Queue, time
+import logging
+from minidinstall import misc
+
+class DnotifyException(Exception):
+ def __init__(self, value):
+ self._value = value
+ def __str__(self):
+ return `self._value`
+
+class DirectoryNotifierFactory:
+ def create(self, dirs, use_dnotify=1, poll_time=30, logger=None, cancel_event=None):
+ if use_dnotify and os.access('/usr/bin/dnotify', os.X_OK):
+ if logger:
+ logger.debug("Using dnotify directory notifier")
+ return DnotifyDirectoryNotifier(dirs, logger)
+ else:
+ if logger:
+ logger.debug("Using mtime-polling directory notifier")
+ return MtimeDirectoryNotifier(dirs, poll_time, logger, cancel_event=cancel_event)
+
+class DnotifyNullLoggingFilter(logging.Filter):
+ def filter(self, record):
+ return 0
+
+class DirectoryNotifier:
+ def __init__(self, dirs, logger, cancel_event=None):
+ self._cwd = os.getcwd()
+ self._dirs = dirs
+ if cancel_event is None:
+ self._cancel_event = threading.Event()
+ else:
+ self._cancel_event = cancel_event
+ if logger is None:
+ self._logger = logging.getLogger("Dnotify")
+ self._logger.addFilter(DnotifyNullLoggingFilter())
+ else:
+ self._logger = logger
+
+ def cancelled(self):
+ return self._cancel_event.isSet()
+
+class DirectoryNotifierAsyncWrapper(threading.Thread):
+ def __init__(self, dnotify, queue, logger=None, name=None):
+ if not name is None:
+ threading.Thread.__init__(self, name=name)
+ else:
+ threading.Thread.__init__(self)
+ self._eventqueue = queue
+ self._dnotify = dnotify
+ if logger is None:
+ self._logger = logging.getLogger("Dnotify")
+ self._logger.addFilter(DnotifyNullLoggingFilter())
+ else:
+ self._logger = logger
+
+ def cancel(self):
+ self._cancel_event.set()
+
+ def run(self):
+ self._logger.info('Created new thread (%s) for async directory notification' % (self.getName()))
+ while not self._dnotify.cancelled():
+ dir = self._dnotify.poll()
+ self._eventqueue.put(dir)
+ self._logger.info('Caught cancel event; async dnotify thread exiting')
+
+class MtimeDirectoryNotifier(DirectoryNotifier):
+ def __init__(self, dirs, poll_time, logger, cancel_event=None):
+ DirectoryNotifier.__init__(self, dirs, logger, cancel_event=cancel_event)
+ self._changed = []
+ self._dirmap = {}
+ self._polltime = poll_time
+ for dir in dirs:
+ self._dirmap[dir] = os.stat(os.path.join(self._cwd, dir))[stat.ST_MTIME]
+
+ def poll(self, timeout=None):
+ timeout_time = None
+ if timeout:
+ timeout_time = time.time() + timeout
+ while self._changed == []:
+ if timeout_time and time.time() > timeout_time:
+ return None
+ self._logger.debug('Polling...')
+ for dir in self._dirmap.keys():
+ oldtime = self._dirmap[dir]
+ mtime = os.stat(os.path.join(self._cwd, dir))[stat.ST_MTIME]
+ if oldtime < mtime:
+ self._logger.debug('Directory "%s" has changed' % (dir,))
+ self._changed.append(dir)
+ self._dirmap[dir] = mtime
+ if self._changed == []:
+ for x in range(self._polltime):
+ if self._cancel_event.isSet():
+ return None
+ time.sleep(1)
+ ret = self._changed[0]
+ self._changed = self._changed[1:]
+ return ret
+
+class DnotifyDirectoryNotifier(DirectoryNotifier):
+ def __init__(self, dirs, logger):
+ DirectoryNotifier.__init__(self, dirs, logger)
+ self._queue = Queue.Queue()
+ dnotify = DnotifyThread(self._queue, self._dirs, self._logger)
+ dnotify.start()
+
+ def poll(self, timeout=None):
+ # delete duplicates
+ i = self._queue.qsize()
+ self._logger.debug('Queue size: %d', (i,))
+ set = {}
+ while i > 0:
+ dir = self._queue_get(timeout)
+ if dir is None:
+ # We shouldn't have to do this; no one else is reading
+ # from the queue. But we do it just to be safe.
+ for key in set.keys():
+ self._queue.put(key)
+ return None
+ set[dir] = 1
+ i -= 1
+ for key in set.keys():
+ self._queue.put(key)
+ i = self._queue.qsize()
+ self._logger.debug('Queue size (after duplicate filter): %d', (i,))
+ return self._queue_get(timeout)
+
+ def _queue_get(self, timeout):
+ if timeout is None:
+ return self._queue.get()
+ timeout_time = time.time() + timeout
+ while 1:
+ try:
+ self._queue.get(0)
+ except Queue.Empty:
+ if time.time() > timeout_time:
+ return None
+ else:
+ time.sleep(15)
+
+class DnotifyThread(threading.Thread):
+ def __init__(self, queue, dirs, logger):
+ threading.Thread.__init__(self)
+ self._queue = queue
+ self._dirs = dirs
+ self._logger = logger
+
+ def run(self):
+ self._logger.debug('Starting dnotify reading thread')
+ (infd, outfd) = os.pipe()
+ pid = os.fork()
+ if pid == 0:
+ os.close(infd)
+ misc.dup2(outfd, 1)
+ args = ['dnotify', '-m', '-c', '-d', '-a', '-r'] + list(self._dirs) + ['-e', 'printf', '"{}\\0"']
+ os.execv('/usr/bin/dnotify', args)
+ os.exit(1)
+
+ os.close(outfd)
+ stdout = os.fdopen(infd)
+ c = 'x'
+ while c != '':
+ curline = ''
+ c = stdout.read(1)
+ while c != '' and c != '\0':
+ curline += c
+ c = stdout.read(1)
+ if c == '':
+ break
+ self._logger.debug('Directory "%s" changed' % (curline,))
+ self._queue.put(curline)
+ (pid, status) = os.waitpid(pid, 0)
+ if status is None:
+ ecode = 0
+ else:
+ ecode = os.WEXITSTATUS(status)
+ raise DnotifyException("dnotify exited with code %s" % (ecode,))
+
+# vim:ts=4:sw=4:et:
diff --git a/lib/DpkgControl.py b/lib/DpkgControl.py
new file mode 100755
index 0000000..38147c7
--- /dev/null
+++ b/lib/DpkgControl.py
@@ -0,0 +1,156 @@
+# DpkgControl.py
+#
+# This module implements control file parsing.
+#
+# DpkgParagraph is a low-level class, that reads/parses a single paragraph
+# from a file object.
+#
+# DpkgControl uses DpkgParagraph in a loop, pulling out the value of a
+# defined key(package), and using that as a key in it's internal
+# dictionary.
+#
+# DpkgSourceControl grabs the first paragraph from the file object, stores
+# it in object.source, then passes control to DpkgControl.load, to parse
+# the rest of the file.
+#
+# To test this, pass it a filetype char, a filename, then, optionally,
+# the key to a paragraph to display, and if a fourth arg is given, only
+# show that field.
+#
+# Copyright 2001 Adam Heath <doogie@debian.org>
+#
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import re, string
+from DpkgDatalist import *
+from minidinstall.SignedFile import *
+from types import ListType
+
+class DpkgParagraph(DpkgOrderedDatalist):
+ caseSensitive = 0
+ trueFieldCasing = {}
+
+ def setCaseSensitive( self, value ): self.caseSensitive = value
+
+ def load( self, f ):
+ "Paragraph data from a file object."
+ key = None
+ value = None
+ while 1:
+ line = f.readline()
+ if not line:
+ return
+ # skip blank lines until we reach a paragraph
+ if line == '\n':
+ if not self:
+ continue
+ else:
+ return
+ line = line[ :-1 ]
+ if line[ 0 ] != ' ':
+ key, value = string.split( line, ":", 1 )
+ if value: value = value[ 1: ]
+ if not self.caseSensitive:
+ newkey = string.lower( key )
+ if not self.trueFieldCasing.has_key( key ):
+ self.trueFieldCasing[ newkey ] = key
+ key = newkey
+ else:
+ if isinstance( value, ListType ):
+ value.append( line[ 1: ] )
+ else:
+ value = [ value, line[ 1: ] ]
+ self[ key ] = value
+
+ def _storeField( self, f, value, lead = " " ):
+ if isinstance( value, ListType ):
+ value = string.join( map( lambda v, lead = lead: v and ( lead + v ) or v, value ), "\n" )
+ else:
+ if value: value = lead + value
+ f.write( "%s\n" % ( value ) )
+
+ def _store( self, f ):
+ "Write our paragraph data to a file object"
+ for key in self.keys():
+ value = self[ key ]
+ if self.trueFieldCasing.has_key( key ):
+ key = self.trueFieldCasing[ key ]
+ f.write( "%s:" % key )
+ self._storeField( f, value )
+
+class DpkgControl(DpkgOrderedDatalist):
+
+ key = "package"
+ caseSensitive = 0
+
+ def setkey( self, key ): self.key = key
+ def setCaseSensitive( self, value ): self.caseSensitive = value
+
+ def _load_one( self, f ):
+ p = DpkgParagraph( None )
+ p.setCaseSensitive( self.caseSensitive )
+ p.load( f )
+ return p
+
+ def load( self, f ):
+ while 1:
+ p = self._load_one( f )
+ if not p: break
+ self[ p[ self.key ] ] = p
+
+ def _store( self, f ):
+ "Write our control data to a file object"
+
+ for key in self.keys():
+ self[ key ]._store( f )
+ f.write( "\n" )
+
+class DpkgSourceControl( DpkgControl ):
+ source = None
+
+ def load( self, f ):
+ f = SignedFile(f)
+ self.source = self._load_one( f )
+ DpkgControl.load( self, f )
+
+ def __repr__( self ):
+ return self.source.__repr__() + "\n" + DpkgControl.__repr__( self )
+
+ def _store( self, f ):
+ "Write our control data to a file object"
+ self.source._store( f )
+ f.write( "\n" )
+ DpkgControl._store( self, f )
+
+if __name__ == "__main__":
+ import sys
+ types = { 'p' : DpkgParagraph, 'c' : DpkgControl, 's' : DpkgSourceControl }
+ type = sys.argv[ 1 ]
+ if not types.has_key( type ):
+ print "Unknown type `%s'!" % type
+ sys.exit( 1 )
+ file = open( sys.argv[ 2 ], "r" )
+ data = types[ type ]()
+ data.load( file )
+ if len( sys.argv ) > 3:
+ para = data[ sys.argv[ 3 ] ]
+ if len( sys.argv ) > 4:
+ para._storeField( sys.stdout, para[ sys.argv[ 4 ] ], "" )
+ else:
+ para._store( sys.stdout )
+ else:
+ data._store( sys.stdout )
+
+# vim:ts=4:sw=4:et:
diff --git a/lib/DpkgDatalist.py b/lib/DpkgDatalist.py
new file mode 100644
index 0000000..0c11612
--- /dev/null
+++ b/lib/DpkgDatalist.py
@@ -0,0 +1,81 @@
+# DpkgDatalist.py
+#
+# This module implements DpkgDatalist, an abstract class for storing
+# a list of objects in a file. Children of this class have to implement
+# the load and _store methods.
+#
+# Copyright 2001 Wichert Akkerman <wichert@linux.com>
+#
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import os, sys
+from UserDict import UserDict
+from OrderedDict import OrderedDict
+from minidinstall.SafeWriteFile import SafeWriteFile
+from types import StringType
+
+class DpkgDatalistException(Exception):
+ UNKNOWN = 0
+ SYNTAXERROR = 1
+
+ def __init__(self, message="", reason=UNKNOWN, file=None, line=None):
+ self.message=message
+ self.reason=reason
+ self.filename=file
+ self.line=line
+
+class _DpkgDatalist:
+ def __init__(self, fn=""):
+ '''Initialize a DpkgDatalist object. An optional argument is a
+ file from which we load values.'''
+
+ self.filename=fn
+ if self.filename:
+ self.load(self.filename)
+
+ def store(self, fn=None):
+ "Store variable data in a file."
+
+ if fn==None:
+ fn=self.filename
+ # Special case for writing to stdout
+ if not fn:
+ self._store(sys.stdout)
+ return
+
+ # Write to a temporary file first
+ if type(fn) == StringType:
+ vf=SafeWriteFile(fn+".new", fn, "w")
+ else:
+ vf=fn
+ try:
+ self._store(vf)
+ finally:
+ if type(fn) == StringType:
+ vf.close()
+
+
+class DpkgDatalist(UserDict, _DpkgDatalist):
+ def __init__(self, fn=""):
+ UserDict.__init__(self)
+ _DpkgDatalist.__init__(self, fn)
+
+
+class DpkgOrderedDatalist(OrderedDict, _DpkgDatalist):
+ def __init__(self, fn=""):
+ OrderedDict.__init__(self)
+ _DpkgDatalist.__init__(self, fn)
+
+# vim:ts=4:sw=4:et:
diff --git a/lib/GPGSigVerifier.py b/lib/GPGSigVerifier.py
new file mode 100644
index 0000000..78aeebb
--- /dev/null
+++ b/lib/GPGSigVerifier.py
@@ -0,0 +1,78 @@
+# GPGSigVerifier -*- mode: python; coding: utf-8 -*-
+
+# A class for verifying signed files
+
+# Copyright © 2002 Colin Walters <walters@gnu.org>
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import os, re, sys, string, stat
+from minidinstall import misc
+
+class GPGSigVerifierException(Exception):
+ def __init__(self, value):
+ self._value = value
+ def __str__(self):
+ return `self._value`
+
+class GPGSigVerificationFailure(Exception):
+ def __init__(self, value, output):
+ self._value = value
+ self._output = output
+ def __str__(self):
+ return `self._value`
+
+ def getOutput(self):
+ return self._output
+
+class GPGSigVerifier:
+ def __init__(self, keyrings, gpgv=None):
+ self._keyrings = keyrings
+ if gpgv is None:
+ gpgv = '/usr/bin/gpgv'
+ if not os.access(gpgv, os.X_OK):
+ raise GPGSigVerifierException("Couldn't execute \"%s\"" % (gpgv,))
+ self._gpgv = gpgv
+
+ def verify(self, filename, sigfilename=None):
+ (stdin, stdout) = os.pipe()
+ pid = os.fork()
+ if pid == 0:
+ os.close(stdin)
+ misc.dup2(stdout, 1)
+ misc.dup2(stdout, 2)
+ args = []
+ for keyring in self._keyrings:
+ args.append('--keyring')
+ args.append(keyring)
+ if sigfilename:
+ args.append(sigfilename)
+ args = [self._gpgv] + args + [filename]
+ os.execv(self._gpgv, args)
+ os.exit(1)
+ os.close(stdout)
+ output = os.fdopen(stdin).readlines()
+ (pid, status) = os.waitpid(pid, 0)
+ if not (status is None or (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0)):
+ if os.WIFEXITED(status):
+ msg = "gpgv exited with error code %d" % (os.WEXITSTATUS(status),)
+ elif os.WIFSTOPPED(status):
+ msg = "gpgv stopped unexpectedly with signal %d" % (os.WSTOPSIG(status),)
+ elif os.WIFSIGNALED(status):
+ msg = "gpgv died with signal %d" % (os.WTERMSIG(status),)
+ raise GPGSigVerificationFailure(msg, output)
+ return output
+
+# vim:ts=4:sw=4:et:
diff --git a/lib/OrderedDict.py b/lib/OrderedDict.py
new file mode 100644
index 0000000..fa3f276
--- /dev/null
+++ b/lib/OrderedDict.py
@@ -0,0 +1,76 @@
+# OrderedDict.py
+#
+# This class functions almost exactly like UserDict. However, when using
+# the sequence methods, it returns items in the same order in which they
+# were added, instead of some random order.
+#
+# Copyright 2001 Adam Heath <doogie@debian.org>
+#
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+from UserDict import UserDict
+
+class OrderedDict(UserDict):
+ __order=[]
+
+ def __init__(self, dict=None):
+ UserDict.__init__(self)
+ self.__order=[]
+ if dict is not None and dict.__class__ is not None:
+ self.update(dict)
+
+ def __cmp__(self, dict):
+ if isinstance(dict, OrderedDict):
+ ret=cmp(self.__order, dict.__order)
+ if not ret:
+ ret=UserDict.__cmp__(self, dict)
+ return ret
+ else:
+ return UserDict.__cmp__(self, dict)
+
+ def __setitem__(self, key, value):
+ if not self.has_key(key):
+ self.__order.append(key)
+ UserDict.__setitem__(self, key, value)
+
+ def __delitem__(self, key):
+ if self.has_key(key):
+ del self.__order[self.__order.index(key)]
+ UserDict.__delitem__(self, key)
+
+ def clear(self):
+ self.__order=[]
+ UserDict.clear(self)
+
+ def copy(self):
+ if self.__class__ is OrderedDict:
+ return OrderedDict(self)
+ import copy
+ return copy.copy(self)
+
+ def keys(self):
+ return self.__order
+
+ def items(self):
+ return map(lambda x, self=self: (x, self.__getitem__(x)), self.__order)
+
+ def values(self):
+ return map(lambda x, self=self: self.__getitem__(x), self.__order)
+
+ def update(self, dict):
+ for k, v in dict.items():
+ self.__setitem__(k, v)
+
+# vim:ts=4:sw=4:et:
diff --git a/lib/SafeWriteFile.py b/lib/SafeWriteFile.py
new file mode 100755
index 0000000..1777d36
--- /dev/null
+++ b/lib/SafeWriteFile.py
@@ -0,0 +1,81 @@
+# SafeWriteFile.py
+#
+# This file is a writable file object. It writes to a specified newname,
+# and when closed, renames the file to the realname. If the object is
+# deleted, without being closed, this rename isn't done. If abort() is
+# called, it also disables the rename.
+#
+# Copyright 2001 Adam Heath <doogie@debian.org>
+#
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+from types import StringType
+from shutil import copy2
+from string import find
+from os import rename
+
+class ObjectNotAllowed(Exception):
+ pass
+
+
+class InvalidMode(Exception):
+ pass
+
+
+class SafeWriteFile:
+ def __init__(self, newname, realname, mode="w", bufsize=-1):
+
+ if type(newname)!=StringType:
+ raise ObjectNotAllowed(newname)
+ if type(realname)!=StringType:
+ raise ObjectNotAllowed(realname)
+
+ if find(mode, "r")>=0:
+ raise InvalidMode(mode)
+ if find(mode, "a")>=0 or find(mode, "+") >= 0:
+ copy2(realname, newname)
+ self.fobj=open(newname, mode, bufsize)
+ self.newname=newname
+ self.realname=realname
+ self.__abort=0
+
+ def close(self):
+ self.fobj.close()
+ if not (self.closed and self.__abort):
+ rename(self.newname, self.realname)
+
+ def abort(self):
+ self.__abort=1
+
+ def __del__(self):
+ self.abort()
+ del self.fobj
+
+ def __getattr__(self, attr):
+ try:
+ return self.__dict__[attr]
+ except:
+ return eval("self.fobj." + attr)
+
+
+if __name__ == "__main__":
+ import time
+ f=SafeWriteFile("sf.new", "sf.data")
+ f.write("test\n")
+ f.flush()
+ time.sleep(1)
+ f.close()
+
+# vim:ts=4:sw=4:et:
diff --git a/lib/SignedFile.py b/lib/SignedFile.py
new file mode 100755
index 0000000..648186e
--- /dev/null
+++ b/lib/SignedFile.py
@@ -0,0 +1,107 @@
+# SignedFile -*- mode: python; coding: utf-8 -*-
+
+# SignedFile offers a subset of file object operations, and is
+# designed to transparently handle files with PGP signatures.
+
+# Copyright © 2002 Colin Walters <walters@gnu.org>
+#
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import re,string
+
+class SignedFile:
+ _stream = None
+ _eof = 0
+ _signed = 0
+ _signature = None
+ _signatureversion = None
+ _initline = None
+ def __init__(self, stream):
+ self._stream = stream
+ line = stream.readline()
+ if (line == "-----BEGIN PGP SIGNED MESSAGE-----\n"):
+ self._signed = 1
+ while (1):
+ line = stream.readline()
+ if (len(line) == 0 or line == '\n'):
+ break
+ else:
+ self._initline = line
+
+ def readline(self):
+ if self._eof:
+ return ''
+ if self._initline:
+ line = self._initline
+ self._initline = None
+ else:
+ line = self._stream.readline()
+ if not self._signed:
+ return line
+ elif line == "-----BEGIN PGP SIGNATURE-----\n":
+ self._eof = 1
+ self._signature = []
+ self._signatureversion = self._stream.readline()
+ self._stream.readline() # skip blank line
+ while 1:
+ line = self._stream.readline()
+ if len(line) == 0 or line == "-----END PGP SIGNATURE-----\n":
+ break
+ self._signature.append(line)
+ self._signature = string.join
+ return ''
+ return line
+
+ def readlines(self):
+ ret = []
+ while 1:
+ line = self.readline()
+ if (line != ''):
+ ret.append(line)
+ else:
+ break
+ return ret
+
+ def close(self):
+ self._stream.close()
+
+ def getSigned(self):
+ return self._signed
+
+ def getSignature(self):
+ return self._signature
+
+ def getSignatureVersion(self):
+ return self._signatureversion
+
+if __name__=="__main__":
+ import sys
+ if len(sys.argv) == 0:
+ print "Need one file as an argument"
+ sys.exit(1)
+ filename = sys.argv[1]
+ f=SignedFile(open(filename))
+ if f.getSigned():
+ print "**** SIGNED ****"
+ else:
+ print "**** NOT SIGNED ****"
+ lines=f.readlines()
+ print lines
+ if not f.getSigned():
+ assert(len(lines) == len(actuallines))
+ else:
+ print "Signature: %s" % (f.getSignature())
+
+# vim:ts=4:sw=4:et:
diff --git a/lib/__init__.py b/lib/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/lib/__init__.py
@@ -0,0 +1 @@
+
diff --git a/lib/misc.py b/lib/misc.py
new file mode 100644
index 0000000..eb52d00
--- /dev/null
+++ b/lib/misc.py
@@ -0,0 +1,36 @@
+# misc -*- mode: python; coding: utf-8 -*-
+
+# misc tools for mini-dinstall
+
+# Copyright © 2004 Thomas Viehmann <tv@beamnet.de>
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import os, errno, time
+
+def dup2(fd,fd2):
+ # dup2 with EBUSY retries (cf. dup2(2) and Debian bug #265513)
+ success = 0
+ tries = 0
+ while (not success):
+ try:
+ os.dup2(fd,fd2)
+ success = 1
+ except OSError, e:
+ if (e.errno != errno.EBUSY) or (tries >= 3):
+ raise
+ # wait 0-2 seconds befor next try
+ time.sleep(tries)
+ tries += 1
diff --git a/lib/version.py b/lib/version.py
new file mode 100644
index 0000000..316c536
--- /dev/null
+++ b/lib/version.py
@@ -0,0 +1 @@
+pkg_version = "0.6.21-0.1"