From a405edc8ce6428a3526be759395bd421197cd7da Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Sat, 27 May 2017 12:19:40 +0200 Subject: [PATCH] Update for Joomla 3.7.2 --- checkjoomla.py | 205 ++++++++++++++++++++++++++++++------------------- 1 file changed, 125 insertions(+), 80 deletions(-) diff --git a/checkjoomla.py b/checkjoomla.py index b139575..02fbdf7 100644 --- a/checkjoomla.py +++ b/checkjoomla.py @@ -1,21 +1,22 @@ #!/usr/bin/env python3 -from pathlib import Path -import re -import urllib.request -from urllib.request import urlretrieve -import zipfile -import shutil -import stat +import argparse +import hashlib import os import os.path -import hashlib +import re +import shutil +import stat import sys -import argparse +import socket +import urllib.request +import urllib.error +import zipfile from distutils.version import StrictVersion +from pathlib import Path ############################################################## -## CONFIG +# CONFIG ############################################################## # BASE BASE @@ -27,52 +28,57 @@ csv_file_name = 'joomla_status.csv' # TMP DOWNLOAD DIR tmp_dl_dir = '/tmp' + ############################################################# -def get_joomla_version(filepath): - fobj = open(filepath, "r", -1, None, 'replace') - version = -1 +def get_joomla_version(version_file_path): + version_file = open(version_file_path, "r", -1, None, 'replace') + main_version = -1 dev_version = -1 - for line in fobj: - if version != -1 and dev_version != -1: + for line in version_file: + if main_version != -1 and dev_version != -1: break - match = re.search("""RELEASE[\s]*=[\s]*'?"?([0-9\.]+)'?"?""", line) - if match is not None: - #print(match.group(1)) - version = match.group(1) - match = re.search("""DEV_LEVEL[\s]*=[\s]*'?"?([0-9]+)'?"?""", line) - if match is not None: - #print(match.group(1)) - dev_version = match.group(1) - fobj.close() + version_match = re.search("""RELEASE[\s]*=[\s]*'?"?([0-9.]+)'?"?""", line) + if version_match is not None: + # print(version_match.group(1)) + main_version = version_match.group(1) - if version != -1 and dev_version != -1: - return str(version + "." + dev_version) + version_match = re.search("""DEV_LEVEL[\s]*=[\s]*'?"?([0-9]+)'?"?""", line) + if version_match is not None: + # print(version_match.group(1)) + dev_version = version_match.group(1) + version_file.close() + + if main_version != -1 and dev_version != -1: + return str(main_version + "." + dev_version) else: return "" -def check_version(version, newest_version): - if StrictVersion(newest_version) > StrictVersion(version): + +def check_version(current_version, latest_version): + if StrictVersion(latest_version) > StrictVersion(current_version): return False else: return True + def get_newest_version(): response = urllib.request.urlopen("http://update.joomla.org/core/list.xml") highest_version = "0.0" for line in response: - match = re.search("""[\s]version="?'?([0-9\.]+)"?'?""", str(line)) - if match is not None: - if StrictVersion(match.group(1)) > StrictVersion(highest_version): - highest_version = match.group(1) + version_match = re.search("""[\s]version="?'?([0-9.]+)"?'?""", str(line)) + if version_match is not None: + if StrictVersion(version_match.group(1)) > StrictVersion(highest_version): + highest_version = version_match.group(1) return highest_version -def download_joomla_version(version): - version_match = re.search("""(\d+)\.(\d+)(\.(\d+))?([ab](\d+))?""", version) - + +def download_joomla_version(joomla_version): + version_match = re.search("""(\d+)\.(\d+)(\.(\d+))?([ab](\d+))?""", joomla_version) + dst_path = "" if version_match is not None: if version_match.group(1) == "2": @@ -82,40 +88,56 @@ def download_joomla_version(version): version_string = version_match.group(1) + "-" + version_match.group(2) + "-" + version_match.group(4) # check if file has already been downloaded... - dst_path = tmp_dl_dir + "/orig_joomla_" + version + ".zip" + dst_path = tmp_dl_dir + "/orig_joomla_" + joomla_version + ".zip" dst_file = Path(dst_path) if not dst_file.is_file(): - url = "https://downloads.joomla.org/cms/" + version_path + "/" + version_string + "/joomla_" + version_string + "-stable-full_package-zip?format=zip" + if StrictVersion(joomla_version) > StrictVersion('3.7.1'): + version_string2 = ".".join(version_string.rsplit('-', 1)) + + url = "https://downloads.joomla.org/cms/" + \ + version_path + "/" + version_string + \ + "/Joomla_" + version_string2 + \ + "-Stable-Full_Package.zip?format=zip" + else: + url = "https://downloads.joomla.org/cms/" + \ + version_path + "/" + version_string + \ + "/joomla_" + version_string + \ + "-stable-full_package-zip?format=zip" + try: - urllib.request.urlretrieve (url, dst_path) - except: + urllib.request.urlretrieve(url, dst_path) + except (urllib.error.URLError, urllib.error.HTTPError, socket.error): + # print("Download of", url, "failed!") dst_path = "" return dst_path -def extract_downloaded_joomla_version(version, path): - dst_path = tmp_dl_dir + "/orig_joomla_" + version + +def extract_downloaded_joomla_version(joomla_version, zip_file_path): + dst_path = tmp_dl_dir + "/orig_joomla_" + joomla_version # extract a fresh copy... shutil.rmtree(dst_path, onerror=remove_readonly) try: - with zipfile.ZipFile(path, "r") as zip_ref: + with zipfile.ZipFile(zip_file_path, "r") as zip_ref: zip_ref.extractall(dst_path) - except: + except (urllib.error.URLError, urllib.error.HTTPError, socket.error): dst_path = "" return dst_path + def remove_readonly(func, path, excinfo): if os.path.isdir(path): os.chmod(path, stat.S_IWRITE) func(path) + def get_dir_md5(dir_root): exclude_dirs = {"installation", "tmp"} - hash = hashlib.md5() + md5_hash = hashlib.md5() for dirpath, dirnames, filenames in os.walk(dir_root, topdown=True): dirnames.sort(key=os.path.normcase) @@ -140,57 +162,69 @@ def get_dir_md5(dir_root): f = open(filepath, 'rb') for chunk in iter(lambda: f.read(65536), b''): - hash.update(chunk) + md5_hash.update(chunk) + + return md5_hash.hexdigest() - return hash.hexdigest() def cmp_joomla_directories(original_root, installation_root): exclude_dirs = {"installation", "tmp", "logs"} - check_failures = [] - - for dirpath, dirnames, filenames in os.walk(original_root, topdown=True): + check_failures = [] - dirnames.sort(key=os.path.normcase) - filenames.sort(key=os.path.normcase) + for dir_path, dir_names, file_names in os.walk(original_root, topdown=True): - dirnames[:] = [d for d in dirnames if d not in exclude_dirs] + dir_names.sort(key=os.path.normcase) + file_names.sort(key=os.path.normcase) - for filename in filenames: - relative_path = os.path.relpath(dirpath, original_root) + dir_names[:] = [d for d in dir_names if d not in exclude_dirs] + + for filename in file_names: + relative_path = os.path.relpath(dir_path, original_root) if relative_path == ".": relative_path = "" - orig_filepath = os.path.join(dirpath, filename) - inst_filepath = os.path.join(installation_root, os.path.join(relative_path, filename)) - - if os.path.isfile(inst_filepath): + orig_file_path = os.path.join(dir_path, filename) + inst_file_path = os.path.join(installation_root, os.path.join(relative_path, filename)) + + if os.path.isfile(inst_file_path): hash_orig = hashlib.md5() - f = open(orig_filepath, 'rb') + f = open(orig_file_path, 'rb') for chunk in iter(lambda: f.read(65536), b''): hash_orig.update(chunk) f.close() - + hash_inst = hashlib.md5() - f = open(inst_filepath, 'rb') + f = open(inst_file_path, 'rb') for chunk in iter(lambda: f.read(65536), b''): hash_inst.update(chunk) f.close() if hash_orig.hexdigest() == hash_inst.hexdigest(): - #print("file ok", os.path.join(relative_path, filename), hash_orig.hexdigest(), hash_inst.hexdigest()) + """ + print("file ok", + os.path.join(relative_path, filename), + hash_orig.hexdigest(), + hash_inst.hexdigest()) + """ pass else: - #print("file NOT OK!!!!!!!!!!!!!!!!!!!!!!!", os.path.join(relative_path, filename), hash_orig.hexdigest(), hash_inst.hexdigest()) + """ + print("file NOT OK!", + os.path.join(relative_path, filename), + hash_orig.hexdigest(), + hash_inst.hexdigest()) + """ check_failures.append(os.path.join(relative_path, filename)) else: - #print("File", os.path.join(relative_path, filename), "is missing!") + # print("File", os.path.join(relative_path, filename), "is missing!") pass return check_failures -class bcolors: + +class ConsoleColors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKGREEN = '\033[92m' @@ -200,6 +234,7 @@ class bcolors: BOLD = '\033[1m' UNDERLINE = '\033[4m' + # MAIN ########### @@ -211,7 +246,7 @@ args = parser.parse_args() newest_version = get_newest_version() -print(bcolors.HEADER + "Newest Joomla Version: ", bcolors.OKBLUE, newest_version, bcolors.ENDC, "\n") +print(ConsoleColors.HEADER + "Newest Joomla Version: ", ConsoleColors.OKBLUE, newest_version, ConsoleColors.ENDC, "\n") fobj = open(csv_file_name, "w") fobj.write("Status;Integrity;Actual Version;Newest Version;Domain;Path\n") @@ -229,30 +264,37 @@ for file_path in Path(base_path).glob('**/version.php'): version_status = "UNKN" integrity_status = "UNKN" if not check_version(version, newest_version): - print(bcolors.FAIL, "[WARNING]", bcolors.ENDC, "Outdated Joomla version found!\t[", bcolors.FAIL + version + bcolors.ENDC, "] [", bcolors.WARNING + domain + bcolors.ENDC, "] \tin ", file_path) + print(ConsoleColors.FAIL, "[WARNING]", ConsoleColors.ENDC, "Outdated Joomla version found!\t[", + ConsoleColors.FAIL + version + ConsoleColors.ENDC, "] [", + ConsoleColors.WARNING + domain + ConsoleColors.ENDC, "] \tin ", + file_path) version_status = "WARN" else: - print(bcolors.OKGREEN, "[OK] ", bcolors.ENDC, "Up to date Joomla version found!\t[", bcolors.OKGREEN + version + bcolors.ENDC, "] [", bcolors.WARNING + domain + bcolors.ENDC, "] \tin ", file_path) + print(ConsoleColors.OKGREEN, "[OK] ", ConsoleColors.ENDC, "Up to date Joomla version found!\t[", + ConsoleColors.OKGREEN + version + ConsoleColors.ENDC, "] [", + ConsoleColors.WARNING + domain + ConsoleColors.ENDC, "] \tin ", + file_path) version_status = "OKOK" if not args.nointegrity: - print(bcolors.HEADER, " -> Checking file integrity: ", bcolors.ENDC, end=" ") + print(ConsoleColors.HEADER, " -> Checking file integrity: ", ConsoleColors.ENDC, end=" ") sys.stdout.flush() dl_path = download_joomla_version(version) if not dl_path: - print(bcolors.FAIL, "Failed to download joomla source!", bcolors.ENDC) + print(ConsoleColors.FAIL, "Failed to download joomla source!", ConsoleColors.ENDC) else: orig_root = extract_downloaded_joomla_version(version, dl_path) - cms_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(str(file_path))))) # strip "libraries/cms/version/version.php" from filename + cms_root = os.path.dirname(os.path.dirname(os.path.dirname( + os.path.dirname(str(file_path))))) # strip "libraries/cms/version/version.php" from filename if not orig_root: - print(bcolors.FAIL, "Failed to extract joomla source!", bcolors.ENDC) + print(ConsoleColors.FAIL, "Failed to extract joomla source!", ConsoleColors.ENDC) else: result_list = cmp_joomla_directories(orig_root, cms_root) if len(result_list) == 0: - print(bcolors.OKGREEN, "OK", bcolors.ENDC) + print(ConsoleColors.OKGREEN, "OK", ConsoleColors.ENDC) integrity_status = "OKOK" else: # check if only image files differ... if so ignore it. @@ -265,18 +307,21 @@ for file_path in Path(base_path).glob('**/version.php'): real_fail_count = real_fail_count + 1 if real_fail_count == 0: - print(bcolors.WARNING, "OK", bcolors.ENDC, "Use -v to get details!") + print(ConsoleColors.WARNING, "OK", ConsoleColors.ENDC, "Use -v to get details!") integrity_status = "WARN" else: - print(bcolors.FAIL, "FAIL", bcolors.ENDC, "Use -v to get details!") + print(ConsoleColors.FAIL, "FAIL", ConsoleColors.ENDC, "Use -v to get details!") integrity_status = "FAIL" - + if args.verbose: if len(result_list) > 0: print('\tMissmatch: %s' % '\n\tMissmatch: '.join(map(str, result_list))) - - fobj.write(version_status + ";" + integrity_status + ";" + version + ";" + newest_version + ";" + domain + ";" + str(file_path) + "\n") - print("") # empty last line + + fobj.write( + version_status + ";" + integrity_status + ";" + version + ";" + newest_version + ";" + domain + ";" + str( + file_path) + "\n") + print("") # empty last line fobj.close() -print("\n" + bcolors.HEADER + "All versions written to: ", bcolors.OKBLUE, csv_file_name, bcolors.ENDC) +print("\n" + ConsoleColors.HEADER + "All versions written to: ", + ConsoleColors.OKBLUE, csv_file_name, ConsoleColors.ENDC)