#!/usr/bin/python
# -*- Mode: Python; python-indent: 8; indent-tabs-mode: t -*-

import sys, os #, errno
#import datetime
import subprocess
import re

from preupg.script_api import *


#END GENERATED SECTION
# exit functions are exit_{pass,not_applicable, fixed, fail, etc.}
# logging functions are log_{error, warning, info, etc.}
# for logging in-place risk use functions log_{extreme, high, medium, slight}_risk

def splitFilename(filename):
    """ 
    Pass in a standard style rpm fullname 
    
    Return a name, version, release, epoch, arch, e.g.::
        foo-1.0-1.i386.rpm returns foo, 1.0, 1, i386
        1:bar-9-123a.ia64.rpm returns bar, 9, 123a, 1, ia64

    This function is copied from rpmutils.
    Dead code right now as rpm -qa --filesbypkg writes name without version, --queryformat is safer and simpler.
    """

    if filename[-4:] == '.rpm':
        filename = filename[:-4]
    
    archIndex = filename.rfind('.')
    arch = filename[archIndex+1:]

    relIndex = filename[:archIndex].rfind('-')
    rel = filename[relIndex+1:archIndex]

    verIndex = filename[:relIndex].rfind('-')
    ver = filename[verIndex+1:relIndex]

    epochIndex = filename.find(':')
    if epochIndex == -1: 
        epoch = ''
    else:
        epoch = filename[:epochIndex]
    
    name = filename[epochIndex + 1:verIndex]
    return name, ver, rel, epoch, arch

def main():
	"""Read VALUE_EXECUTABLES, ignore those from Red Hat, and check if they are linked against hal-libs."""
	"""Check if any package depends on hal, and again ignore Red Hat packages."""

	def is_elf(file_path):
		"""check for elf magic number, true for match"""
		try:
			f = open(file_path, "rb")
		except IOError:
			#solution_file("can not open " + file_path + "\n")
			return False
		pattern = "\x7FELF"
		try:
			char = f.read(4)
			if char != pattern:
				#solution_file("Not matched " + file_path+ "\n")
				return False
		except IOError:
			#solution_file("can not read " + file_path+ "\n")
			return False
		finally:
			f.close()
		#solution_file("matched " + file_path + "\n")
		return True

	def run_command(cmd):
		"""Run external command specified as a list of argv, return status code, and output as a list of lines."""
		output = os.tmpfile()
        	try:
        		return_code = subprocess.Popen(cmd,
							stdout=output,
							stderr=subprocess.STDOUT,
							close_fds=True,
							shell=False,
							creationflags=0).wait()
		except:
			output.seek(0)
			log_error(output.read())
			log_error("Could not invoke the external command " + str(cmd))
			exit_error()
		output.seek(0)
		return (return_code, output.read())

	def get_package(file_path):
		"""Ask rpm if the file belongs to a package, and return its name."""
		cmd = ["rpm", "-qf", file_path]
		(return_code, output) = run_command(cmd)
		if return_code !=  0:
			return None
		return output.strip()

	def is_redhat(file_path, redhat_list, filetopkgdict):
		"""Check if the executable is from a Red Hat signed package, those are covered elsewhere."""
		"""expects redhat_list - list of Red Hat package names"""
		"""expects filetopkgdict - dict of file to package mapping"""
		try:
			return filetopkgdict[file_path] in redhat_list
		except KeyError:
			return False
		#return pkg in redhat_list
		
		#pkg = get_package(file_path)
		#if not pkg:
		#	return False
		#(name, ver, rel, epoch, arch) = splitFilename(pkg))

		#for rhlist_item in redhat_list:
		#	if name == rhlist_item:
		#		return True
		#return  False
		#if pkg:
		#	log_error(file_path + "  " + pkg)
		#else:
		#	log_error(file_path + "    SSSSSSSSSSSSSSSSS")
		#return 0

	def debug_write_list(list):
		"""for debugging purposes only, dead code"""
		for item in list:
			solution_file(item.strip()+"\n")

	def ldd(file_path):
		"""run ldd and return output as a list of words if successful"""
        	cmd = ["ldd", file_path]
		(return_code, output) = run_command(cmd)
		if return_code !=  0:
			log_warning("Could not run ldd on " + file_path + "\n")
			return ""
		return output.split()

	def filesbypkg():
		"""run 'rpm -qa --filesbypkg' and make a dict file->package"""
		cmd = ["rpm", "-qa", "--filesbypkg"]
		(return_code, output) = run_command(cmd)
		if return_code != 0:
			log_error(output)
			log_error("Error while invoking rpm -qa --filesbypkg")
			exit_error()
		output = output.split("\n")
		filetopkgdict = {}
		for line in output:
			tmp = line.split()
			if len(tmp) != 2:
				#log_error("Error while parsing output from 'rpm -qa --filesbypkg'")
				#log_error("line is:"+line+":\n")
				#exit_error()
				continue
			filetopkgdict[tmp[1]] = tmp[0]
		#log_error(str(filetopkgdict))
		#exit_error()
		return filetopkgdict

	failed = False
	#create a list of executables
	with open(VALUE_EXECUTABLES, "r") as f:
		filelist = f.readlines()
	filelist = tuple(filelist)
	#strip newlines
	#filelist = map(lambda x: x[:-1], filelist)
	filelist = map(lambda x: x.strip(), filelist)
	#ignore symlinks
	filelist = filter(lambda x: not os.path.islink(x), filelist)
	#ignore pipes and other weirdness
	filelist = filter(os.path.isfile, filelist)
	#take only elf
	filelist = filter(is_elf, filelist)
	#ignore red hat packages, first create dict of all files to packages
	filetopkgdict = filesbypkg()
	#list of redhat packages
	#we need only package names in this list
	#much faster to find members in set than in list
	redhat_list = set(get_dist_native_list())
	filelist = filter(lambda x: not is_redhat(x, redhat_list, filetopkgdict), filelist)
	
	hal_lib_list = ["/usr/lib64/libhal-storage.so.1", "/usr/lib64/libhal-storage.so.1.0.0", "/usr/lib64/libhal.so.1", "/usr/lib64/libhal.so.1.0.0"]
	for file in filelist:
		libs = ldd(file)
		for hal_lib in hal_lib_list:
			if hal_lib in libs:
				log_high_risk(file + " is linked against " + hal_lib)
				failed = True
				break

	#check for rpm packages dependent on hal, hal-libs
	(return_code, output) = run_command(["rpm", "-qa", "--whatrequires", "hal", "hal-libs", "--queryformat", "%{NAME}\n"])
	#log_error(output)
	if return_code == 0:
		for pkg in output.split("\n"):
			pkgstrip = pkg.strip()
			if pkgstrip and (pkgstrip not in redhat_list):
				log_high_risk("Found an RPM package not signed by Red Hat dependent on HAL:" + pkg)
				failed = True
	
	return failed

if __name__ == "__main__":
	if os.geteuid() != 0:
		sys.stdout.write("Needs to be root.\n")
		log_slight_risk("The script needs to be run under root.")
		exit_error()
	if main():
		exit_fail()
	else:
		exit_pass()
