Implemented integrity check
This commit is contained in:
parent
a3b6d5f8a9
commit
c770b4f03a
180
checkjoomla.py
180
checkjoomla.py
@ -3,6 +3,15 @@
|
||||
from pathlib import Path
|
||||
import re
|
||||
import urllib.request
|
||||
from urllib.request import urlretrieve
|
||||
import zipfile
|
||||
import shutil
|
||||
import stat
|
||||
import os
|
||||
import os.path
|
||||
import hashlib
|
||||
import sys
|
||||
import argparse
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
##############################################################
|
||||
@ -15,6 +24,9 @@ base_path = '/var/www/vhosts'
|
||||
# CSV FILENAME
|
||||
csv_file_name = 'joomla_status.csv'
|
||||
|
||||
# TMP DOWNLOAD DIR
|
||||
tmp_dl_dir = '/tmp'
|
||||
|
||||
#############################################################
|
||||
|
||||
def get_joomla_version(filepath):
|
||||
@ -58,6 +70,120 @@ def get_newest_version():
|
||||
|
||||
return highest_version
|
||||
|
||||
def download_joomla_version(version):
|
||||
version_match = re.search("""(\d+)\.(\d+)(\.(\d+))?([ab](\d+))?""", version)
|
||||
|
||||
dst_path = ""
|
||||
if version_match is not None:
|
||||
if version_match.group(1) == 2:
|
||||
version_path = "joomla25"
|
||||
else:
|
||||
version_path = "joomla3"
|
||||
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 + "/" + 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"
|
||||
urllib.request.urlretrieve (url, dst_path)
|
||||
|
||||
return dst_path
|
||||
|
||||
def extract_downloaded_joomla_version(version, path):
|
||||
dst_path = tmp_dl_dir + "/" + version
|
||||
|
||||
# extract a fresh copy...
|
||||
shutil.rmtree(dst_path, onerror=remove_readonly)
|
||||
|
||||
with zipfile.ZipFile(path, "r") as zip_ref:
|
||||
zip_ref.extractall(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()
|
||||
for dirpath, dirnames, filenames in os.walk(dir_root, topdown=True):
|
||||
|
||||
dirnames.sort(key=os.path.normcase)
|
||||
filenames.sort(key=os.path.normcase)
|
||||
|
||||
dirnames[:] = [d for d in dirnames if d not in exclude_dirs]
|
||||
|
||||
for filename in filenames:
|
||||
filepath = os.path.join(dirpath, filename)
|
||||
|
||||
# If some metadata is required, add it to the checksum
|
||||
|
||||
# 1) filename (good idea)
|
||||
# hash.update(os.path.normcase(os.path.relpath(filepath, dir_root))
|
||||
|
||||
# 2) mtime (possibly a bad idea)
|
||||
# st = os.stat(filepath)
|
||||
# hash.update(struct.pack('d', st.st_mtime))
|
||||
|
||||
# 3) size (good idea perhaps)
|
||||
# hash.update(bytes(st.st_size))
|
||||
|
||||
f = open(filepath, 'rb')
|
||||
for chunk in iter(lambda: f.read(65536), b''):
|
||||
hash.update(chunk)
|
||||
|
||||
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):
|
||||
|
||||
dirnames.sort(key=os.path.normcase)
|
||||
filenames.sort(key=os.path.normcase)
|
||||
|
||||
dirnames[:] = [d for d in dirnames if d not in exclude_dirs]
|
||||
|
||||
for filename in filenames:
|
||||
relative_path = os.path.relpath(dirpath, 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):
|
||||
hash_orig = hashlib.md5()
|
||||
f = open(orig_filepath, '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')
|
||||
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())
|
||||
pass
|
||||
else:
|
||||
#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!")
|
||||
pass
|
||||
|
||||
return check_failures
|
||||
|
||||
class bcolors:
|
||||
HEADER = '\033[95m'
|
||||
OKBLUE = '\033[94m'
|
||||
@ -71,12 +197,17 @@ class bcolors:
|
||||
# MAIN
|
||||
###########
|
||||
|
||||
parser = argparse.ArgumentParser(description='Check joomla installation state.')
|
||||
parser.add_argument('-v', '--verbose', action='store_true', help='Print verbose output')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
newest_version = get_newest_version()
|
||||
|
||||
print(bcolors.HEADER + "Newest Joomla Version: ", bcolors.OKBLUE, newest_version, bcolors.ENDC, "\n")
|
||||
|
||||
fobj = open(csv_file_name, "w")
|
||||
fobj.write("Status;Actual Version;Newest Version;Domain;Path\n")
|
||||
fobj.write("Status;Integrity;Actual Version;Newest Version;Domain;Path\n")
|
||||
|
||||
for file_path in Path(base_path).glob('**/version.php'):
|
||||
newest_version = get_newest_version()
|
||||
@ -88,12 +219,55 @@ for file_path in Path(base_path).glob('**/version.php'):
|
||||
domain = match.group(1)
|
||||
|
||||
if version:
|
||||
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)
|
||||
fobj.write("WARN;" + version + ";" + newest_version + ";" + domain + ";" + str(file_path) + "\n")
|
||||
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)
|
||||
fobj.write("OKOK;" + version + ";" + newest_version + ";" + domain + ";" + str(file_path) + "\n")
|
||||
version_status = "OKOK"
|
||||
|
||||
print(bcolors.HEADER, " -> Checking file integrity: ", bcolors.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)
|
||||
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
|
||||
|
||||
if not orig_root:
|
||||
print(bcolors.FAIL, "Failed to extract joomla source!", bcolors.ENDC)
|
||||
else:
|
||||
result_list = cmp_joomla_directories(orig_root, cms_root)
|
||||
|
||||
if len(result_list) == 0:
|
||||
print(bcolors.OKGREEN, "OK", bcolors.ENDC)
|
||||
integrity_status = "OKOK"
|
||||
else:
|
||||
# check if only image files differ... if so ignore it.
|
||||
real_fail_count = 0
|
||||
|
||||
for fail_path in result_list:
|
||||
if fail_path.lower().endswith(".jpg") or fail_path.lower().endswith(".png"):
|
||||
pass
|
||||
else:
|
||||
real_fail_count = real_fail_count + 1
|
||||
|
||||
if real_fail_count == 0:
|
||||
print(bcolors.WARNING, "OK", bcolors.ENDC, "Use -v to get details!")
|
||||
integrity_status = "WARN"
|
||||
else:
|
||||
print(bcolors.FAIL, "FAIL", bcolors.ENDC, "Use -v to get details!")
|
||||
integrity_status = "FAIL"
|
||||
|
||||
if args.verbose:
|
||||
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.close()
|
||||
print("\n" + bcolors.HEADER + "All versions written to: ", bcolors.OKBLUE, csv_file_name, bcolors.ENDC)
|
||||
|
Loading…
Reference in New Issue
Block a user