diff --git a/TODO b/TODO index 00e51964f..1297b1364 100644 --- a/TODO +++ b/TODO @@ -2,6 +2,7 @@ TODO list: - glsa utility (*) - migrate server code to ServerInterface [] write a tool that helps keeping packages updated (also supporting injected ones) + - packages.sabayonlinux.org interactivity (comments + images upload + connection to phpbb user db) - Community repositories - split RDEPEND and PDEPEND - find a way to better handle real smartapps deps (need split PDEPEND?) diff --git a/client/equo b/client/equo index 029d478cd..f18ef298c 100644 --- a/client/equo +++ b/client/equo @@ -53,6 +53,9 @@ def print_help(): print_info(" \t\t"+red("--upgrade")+"\t\t upgrade "+etpConst['systemname']+" to the specified release (3.5,3.6...)") print_info(" \t\t"+red("--nochecksum")+"\t\t disable package integrity check") + print_info(" \t"+blue("security")+brown("\t\t security infrastructure tools")) + print_info(" \t\t"+darkgreen("update")+red("\t\t\t download the latest Security Advisories")) + print_info(" \t"+blue("install")+brown("\t\t install one or more packages or .tbz2")) print_info(" \t\t"+red("--ask")+"\t\t\t ask before making any changes") print_info(" \t\t"+red("--pretend")+"\t\t just show what would be done") @@ -252,6 +255,10 @@ try: rc = text_ui.package(options) load_conf_cache() + elif options[0] == "security": + import text_security + rc = text_security.security(options[1:]) + elif (options[0] == "query"): import text_query rc = text_query.query(options[1:]) diff --git a/conf/repositories.conf b/conf/repositories.conf index 7a4d17e6e..c4ec0f47d 100644 --- a/conf/repositories.conf +++ b/conf/repositories.conf @@ -71,3 +71,14 @@ conntestlink|http://svn.sabayonlinux.org # downloadspeedlimit|30 # # downloadspeedlimit| + +# +# syntax for securityurl: +# +# securityurl: Entropy compatible Security Advisories (GLSAs) package URL +# securityurl| +# +# example: +# securityurl|http://packages.sabayonlinux.org/security/security-advisories.tar.bz2 +# +# securityurl| \ No newline at end of file diff --git a/libraries/entropy.py b/libraries/entropy.py index 0b3ea7b0c..c177a92fa 100644 --- a/libraries/entropy.py +++ b/libraries/entropy.py @@ -93,6 +93,9 @@ class EquoInterface(TextInterface): self.FileUpdates = self.__FileUpdates() self.repoDbCache = {} + # security interface + self.Security = SecurityInterface(self) + def switchChroot(self, chroot = ""): # clean caches self.purge_cache() @@ -3586,7 +3589,7 @@ class RepoInterface: # Test network connectivity conntest = self.Entropy.entropyTools.get_remote_data(etpConst['conntestlink']) if not conntest: - raise exceptionTools.OnlineMirrorError("OnlineMirrorError: Cannot connect to ") + raise exceptionTools.OnlineMirrorError("OnlineMirrorError: Cannot connect to %s" % (etpConst['conntestlink'],)) if not self.reponames: for x in etpRepositories: @@ -3923,6 +3926,8 @@ class RepoInterface: # clean caches if self.dbupdated: self.Entropy.generate_cache(depcache = True, configcache = False) + # update Security Advisories + self.Entropy.Security.fetch_advisories() if self.syncErrors: self.Entropy.updateProgress( red("Something bad happened. Please have a look."), @@ -6135,3 +6140,253 @@ class ErrorReportInterface: else: raise exceptionTools.PermissionDenied("PermissionDenied: not yet prepared") + +''' + I wrote this after having drunk a Red Bull, + so I think the code will look nice and sharp + ~~ GIVES YOU WINGS ~~ +''' +class SecurityInterface: + + def __init__(self, EquoInstance): + self.Entropy = EquoInstance + try: + self.Entropy.instanceTest() + except: + raise exceptionTools.IncorrectParameter("IncorrectParameter: a valid Entropy Instance is needed") + self.lastfetch = None + self.previous_checksum = "0" + self.advisories_changed = None + + self.unpackdir = os.path.join(etpConst['entropyunpackdir'],"security-"+str(self.Entropy.entropyTools.getRandomNumber())) + self.security_url = etpConst['securityurl'] + self.unpacked_package = os.path.join(self.unpackdir,"glsa_package") + self.security_url_checksum = etpConst['securityurl']+etpConst['packageshashfileext'] + self.download_package = os.path.join(self.unpackdir,os.path.basename(etpConst['securityurl'])) + self.download_package_checksum = self.download_package+etpConst['packageshashfileext'] + + self.security_package = os.path.join(etpConst['securitydir'],os.path.basename(etpConst['securityurl'])) + self.security_package_checksum = self.security_package+etpConst['packageshashfileext'] + + if os.path.isfile(etpConst['securitydir']) or os.path.islink(etpConst['securitydir']): + os.remove(etpConst['securitydir']) + if not os.path.isdir(etpConst['securitydir']): + os.makedirs(etpConst['securitydir']) + elif os.path.isfile(self.security_package_checksum): + try: + f = open(self.security_package_checksum) + self.previous_checksum = f.readline().strip().split()[0] + f.close() + except: # FIXME really horrible exception trapping + try: + f.close() + except: + pass + + def __prepare_unpack(self): + + if os.path.isfile(self.unpackdir) or os.path.islink(self.unpackdir): + os.remove(self.unpackdir) + if os.path.isdir(self.unpackdir): + shutil.rmtree(self.unpackdir,True) + try: + os.rmdir(self.unpackdir) + except OSError: + pass + os.makedirs(self.unpackdir) + + def __download_glsa_package(self): + return self.__generic_download(self.security_url, self.download_package) + + def __download_glsa_package_checksum(self): + return self.__generic_download(self.security_url_checksum, self.download_package_checksum, showSpeed = False) + + def __generic_download(self, url, save_to, showSpeed = True): + fetchConn = self.Entropy.urlFetcher(url, save_to, resume = False, showSpeed = showSpeed) + fetchConn.progress = self.Entropy.progress + rc = fetchConn.download() + del fetchConn + status = True + if rc in ("-1","-2","-3"): + status = False + return status + + def __verify_checksum(self): + + # read checksum + if not os.path.isfile(self.download_package_checksum) or not os.access(self.download_package_checksum,os.R_OK): + return 1 + + f = open(self.download_package_checksum) + try: + checksum = f.readline().strip().split()[0] + f.close() + except: + return 2 + + if checksum == self.previous_checksum: + self.advisories_changed = False + else: + self.advisories_changed = True + md5res = self.Entropy.entropyTools.compareMd5(self.download_package,checksum) + if not md5res: + return 3 + return 0 + + def __unpack_advisories(self): + rc = self.Entropy.entropyTools.uncompressTarBz2( + self.download_package, + self.unpacked_package, + catchEmpty = True + ) + return rc + + def __clear_previous_advisories(self): + if os.listdir(etpConst['securitydir']): + shutil.rmtree(etpConst['securitydir'],True) + if not os.path.isdir(etpConst['securitydir']): + os.makedirs(etpConst['securitydir']) + + def __put_advisories_in_place(self): + for advfile in os.listdir(self.unpacked_package): + from_file = os.path.join(self.unpacked_package,advfile) + to_file = os.path.join(etpConst['securitydir'],advfile) + shutil.move(from_file,to_file) + + def __cleanup_garbage(self): + shutil.rmtree(self.unpackdir,True) + + def fetch_advisories(self): + + self.Entropy.updateProgress( + blue("Testing ")+bold("Security Advisories")+blue(" service connection"), + importance = 2, + type = "info", + header = red("@@ "), + footer = red(" ...") + ) + + # Test network connectivity + conntest = self.Entropy.entropyTools.get_remote_data(etpConst['conntestlink']) + if not conntest: + raise exceptionTools.OnlineMirrorError("OnlineMirrorError: Cannot connect to %s" % (etpConst['conntestlink'],)) + + self.Entropy.updateProgress( + blue("Getting the latest ")+bold("Security Advisories")+darkgreen(" (GLSAs)"), + importance = 2, + type = "info", + header = red("@@ "), + footer = red(" ...") + ) + + # prepare directories + self.__prepare_unpack() + + # download package + status = self.__download_glsa_package() + self.lastfetch = status + if not status: + self.Entropy.updateProgress( + blue("Security Advisories: unable to download package, sorry."), + importance = 2, + type = "error", + header = red(" ## ") + ) + return 1 + + self.Entropy.updateProgress( + blue("Verifying checksum"), + importance = 1, + type = "info", + header = red(" # "), + footer = red(" ..."), + back = True + ) + + # download digest + status = self.__download_glsa_package_checksum() + if not status: + self.Entropy.updateProgress( + blue("Security Advisories: cannot download checksum, sorry."), + importance = 2, + type = "error", + header = red(" ## ") + ) + return 2 + + # verify digest + status = self.__verify_checksum() + + if status == 1: + self.Entropy.updateProgress( + blue("Security Advisories: cannot open packages, sorry."), + importance = 2, + type = "error", + header = red(" ## ") + ) + return 3 + elif status == 2: + self.Entropy.updateProgress( + blue("Security Advisories: cannot read checksum, sorry."), + importance = 2, + type = "error", + header = red(" ## ") + ) + return 4 + elif status == 3: + self.Entropy.updateProgress( + blue("Security Advisories: digest verification failed, sorry."), + importance = 2, + type = "error", + header = red(" ## ") + ) + return 5 + elif status == 0: + self.Entropy.updateProgress( + darkgreen("Verification Successful"), + importance = 1, + type = "info", + header = red(" # ") + ) + else: + raise exceptionTools.InvalidData("InvalidData: return status not valid.") + + # now unpack in place + status = self.__unpack_advisories() + if status != 0: + self.Entropy.updateProgress( + blue("Security Advisories: digest verification failed, try again later."), + importance = 2, + type = "error", + header = red(" ## ") + ) + return 6 + + self.Entropy.updateProgress( + darkgreen("Installing Security Advisories"), + importance = 1, + type = "info", + header = red(" # "), + footer = red(" ...") + ) + + # clear previous + self.__clear_previous_advisories() + # copy over + self.__put_advisories_in_place() + # remove temp stuff + self.__cleanup_garbage() + + if self.advisories_changed: + advtext = darkgreen("Security Advisories: updated successfully") + else: + advtext = darkred("Security Advisories: already up to date") + + self.Entropy.updateProgress( + advtext, + importance = 2, + type = "info", + header = red(" @@ ") + ) + + return 0 diff --git a/libraries/entropyConstants.py b/libraries/entropyConstants.py index 7bde7697e..04b3252f3 100644 --- a/libraries/entropyConstants.py +++ b/libraries/entropyConstants.py @@ -517,6 +517,7 @@ def initConfig_entropyConstants(rootdir): ETP_SMARTAPPSDIR = "/smartapps/"+ETP_ARCH_CONST ETP_SMARTPACKAGESDIR = "/smartpackages/"+ETP_ARCH_CONST ETP_CACHESDIR = "/caches/" + ETP_SECURITYDIR = "/glsa/" ETP_LOG_DIR = ETP_DIR+"/"+"logs" ETP_CONF_DIR = rootdir+"/etc/entropy" ETP_SYSLOG_DIR = rootdir+"/var/log/entropy/" @@ -651,6 +652,8 @@ def initConfig_entropyConstants(rootdir): 'downloadspeedlimit': None, # equo packages download speed limit (in kb/sec) 'dumpstoragedir': ETP_DIR+ETP_CACHESDIR, # data storage directory, useful to speed up equo across multiple issued commands + 'securitydir': ETP_DIR+ETP_SECURITYDIR, # where GLSAs are stored + 'securityurl': "http://packages.sabayonlinux.org/security/security-advisories.tar.bz2", # packages keywords/mask/unmask settings 'packagemasking': {}, # package masking information dictionary filled by maskingparser.py @@ -871,6 +874,12 @@ def initConfig_entropyConstants(rootdir): except: etpConst['downloadspeedlimit'] = None + elif (line.find("securityurl|") != -1) and (not line.startswith("#")) and (len(line.split("|")) == 2): + try: + url = line.split("|")[1] + etpConst['securityurl'] = url + except: + pass # align etpConst['binaryurirelativepath'] and etpConst['etpurirelativepath'] with etpConst['product'] etpConst['binaryurirelativepath'] = etpConst['product']+"/"+etpConst['binaryurirelativepath']