Implemented integrity check

This commit is contained in:
Christoph Haas 2016-12-21 20:37:59 +01:00
parent a3b6d5f8a9
commit c770b4f03a
1 changed files with 177 additions and 3 deletions

View File

@ -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)