def check_string(xml_data): if not _find_in_path('gpg'): raise SafeException( "GnuPG is not installed ('gpg' not in $PATH). See http://gnupg.org" ) if xml_data.startswith("<?xml "): return _check_xml_string(xml_data) raise SafeException("Data is not a valid xml document")
def import_key(stream): """Run C{gpg --import} with this stream as stdin.""" errors = tempfile.TemporaryFile() child = os.fork() if child == 0: # We are the child try: try: os.dup2(stream.fileno(), 0) os.dup2(errors.fileno(), 2) os.execlp('gpg', 'gpg', '--no-secmem-warning', '--quiet', '--import') except: traceback.print_exc() finally: os._exit(1) assert False pid, status = os.waitpid(child, 0) assert pid == child errors.seek(0) error_messages = errors.read().strip() errors.close() if error_messages: raise SafeException("Errors from 'gpg --import':\n%s" % error_messages)
def load_from_string(self, xml): try: self.original_xml = xml document = parseString(xml) except Exception, e: raise SafeException("Error recipe could not be loaded does not appear to be valid XML (%s)" % e)
def load_from_file(self, path): try: f = open(path, "r") xml = f.read() f.close() self.load_from_string(xml) except Exception, e: raise SafeException("Error recipe could not be loaded does not appear to be valid XML (%s)" % e)
def _get_sigs_from_gpg_status_stream(status_r, child, errors): """Read messages from status_r and collect signatures from it. When done, reap 'child'. If there are no signatures, throw SafeException (using errors for the error message if non-empty).""" sigs = [] # Should we error out on bad signatures, even if there's a good # signature too? for line in os.fdopen(status_r): assert line.endswith('\n') assert line.startswith('[GNUPG:] ') line = line[9:-1] split_line = line.split(' ') code = split_line[0] args = split_line[1:] if code == 'VALIDSIG': sigs.append(ValidSig(args)) elif code == 'BADSIG': sigs.append(BadSig(args)) elif code == 'ERRSIG': sigs.append(ErrSig(args)) pid, status = os.waitpid(child, 0) assert pid == child errors.seek(0) error_messages = errors.read().strip() errors.close() if not sigs: if error_messages: raise SafeException("No signatures found. Errors from GPG:\n%s" % error_messages) else: raise SafeException( "No signatures found. No error messages from GPG.") return sigs
def get_recipe(self, application_name): # Get information about users system plat = platform.system() + " " + platform.machine() pyvers = "Python/" + platform.python_version() useragent = "klik/%s (%s; %s; %s; %s;) %s (%s)" % ( self.settings.version, self.xdg.get_is_terminal(), plat, self.xdg.get_lang(), self.xdg.get_desktop_enviroment() + "+" + "+".join(self.xdg.get_installed_libs()), pyvers, self.xdg.get_distribution()) # Convert to lower case for comparing application_name = application_name.lower() # Download Recipe if application_name.startswith("http://"): url = application_name else: # Strip klik protocol if application_name.startswith("klik2://"): application_name = application_name[8:] elif application_name.startswith("klik2:"): application_name = application_name[6:] if application_name.startswith("klik://"): application_name = application_name[7:] elif application_name.startswith("klik:"): application_name = application_name[5:] url = self.settings.default_download_url + application_name print "Recipe URL: " + url try: request = urllib2.Request(url) request.add_header('User-Agent', useragent) opener = urllib2.build_opener() data = opener.open(request).read() except: raise SafeException( "Error occurred while attempting to download recipe") # Load Recipe print "" print "Parsing recipe..." recipe = KlikRecipe() recipe.load_from_string(data) self.print_recipe(recipe) return recipe
def _check_xml_string(data_to_check): xml_comment_start = '<!-- Base64 Signature' last_comment = data_to_check.rfind('\n' + xml_comment_start) if last_comment < 0: raise SafeException( "No signature block in XML. Maybe this file isn't signed?") last_comment += 1 # Include new-line in data data = tempfile.TemporaryFile() # Killerkiwi 2007-09-12 - Added rstrip to remove trailing spaces that invalidates signing data.write(data_to_check[:last_comment].rstrip()) data.flush() os.lseek(data.fileno(), 0, 0) errors = tempfile.TemporaryFile() sig_lines = data_to_check[last_comment:].split('\n') if sig_lines[0].strip() != xml_comment_start: raise SafeException('Bad signature block: extra data on comment line') while sig_lines and not sig_lines[-1].strip(): del sig_lines[-1] if sig_lines[-1].strip() != '-->': raise SafeException( 'Bad signature block: last line is not end-of-comment') sig_data = '\n'.join(sig_lines[1:-1]) if re.match('^[ A-Za-z0-9+/=\n]+$', sig_data) is None: raise SafeException( "Invalid characters found in base 64 encoded signature") try: sig_data = base64.decodestring(sig_data) # (b64decode is Python 2.4) except Exception, ex: raise SafeException("Invalid base 64 encoded signature: " + str(ex))
def check_stream(stream): """Pass stream through gpg --decrypt to get the data, the error text, and a list of signatures (good or bad). If stream starts with "<?xml " then get the signature from a comment at the end instead (and the returned data is the original stream). stream must be seekable. @note: Stream returned may or may not be the one passed in. Be careful! @return: (data_stream, [Signatures])""" if not _find_in_path('gpg'): raise SafeException( "GnuPG is not installed ('gpg' not in $PATH). See http://gnupg.org" ) #stream.seek(0) #all = stream.read() stream.seek(0) start = stream.read(6) stream.seek(0) if start == "<?xml ": return _check_xml_stream(stream) else: os.lseek(stream.fileno(), 0, 0) return _check_plain_stream(stream)
def __load_from_document(self, document): interface = self.__getFirstNode(document, "interface") error = self.__getFirstNode(document, "error") if interface != None: self.source_uri = interface.getAttribute("uri") name = self.__getFirstNode(interface, "name") if name != None and name.firstChild != None: self.name = self.__getNodeValue(name, "") # default command is app name self.command = self.name description = self.__getFirstNode(interface, "description") if description != None: self.description = self.__getNodeValue(description, "") summary = self.__getFirstNode(interface, "summary") if summary != None: self.summary = self.__getNodeValue(summary, "") icon = self.__getFirstNode(interface, "icon") if icon != None: self.icon = icon.getAttribute("href") debtags = self.__getFirstNode(interface, "debtags") if debtags != None: nodes = interface.getElementsByTagName("tag") if len(nodes) > 0: self.require_terminal = False for node in nodes: value = self.__getNodeValue(node, None) if value != None: if value == "interface::commandline": self.require_terminal = True value = value.split("::") self.debtags[value[0]] = value[1] group = self.__getFirstNode(interface, "group") if group != None: if group.attributes["main"] != None: self.command = group.getAttribute("main") implementation = self.__getFirstNode( group, "implementation") if implementation != None: self.version = implementation.getAttribute("version") if implementation.attributes["downloadsize"] != None: try: self.size = implementation.getAttribute( "downloadsize") except: pass for node in implementation.getElementsByTagName( "environment"): try: __readEnviroment(node) except: pass for node in implementation.getElementsByTagName("archive"): try: ki = KlikIngredientPackage( node.getAttribute("href"), node.getAttribute("name"), node.getAttribute("md5")) self.packages.append(ki) except: pass preflight = self.__getFirstNode(implementation, "klik-preflight") self.__readActions(preflight, self.preflight) postflight = self.__getFirstNode(implementation, "klik-postflight") self.__readActions(postflight, self.postflight) prerun = self.__getFirstNode(implementation, "klik-prerun") self.__readActions(prerun, self.prerun) postrun = self.__getFirstNode(implementation, "klik-postrun") self.__readActions(postrun, self.postrun) return elif error != None: # hack to trim quotes... why does this happen? raise SafeException(self.__getNodeValue(error, "")) return raise Exception("XML document does not appear to be a valid recipe")
preflight = self.__getFirstNode(implementation, "klik-preflight") self.__readActions( preflight, self.preflight) postflight = self.__getFirstNode(implementation, "klik-postflight") self.__readActions( postflight, self.postflight) prerun = self.__getFirstNode(implementation, "klik-prerun") self.__readActions( prerun, self.prerun) postrun = self.__getFirstNode(implementation, "klik-postrun") self.__readActions( postrun, self.postrun) return elif error != None: # hack to trim quotes... why does this happen? raise SafeException( self.__getNodeValue( error, "") ) return raise Exception("XML document does not appear to be a valid recipe") def __readEnviroment( self, node ): name = node.getAttribute( "name" ) if name != None and name != "": env = Enviroment() env.name = name env.insert = node.getAttribute( "insert" ) env.mode = node.getAttribute( "mode" ) env.default = node.getAttribute( "default" )