#!/usr/bin/python
#
# Python client for checking periodically for posted actions
# on the Red Hat Network servers.
#
# Copyright (C) 2000, Red Hat, Inc. Distributed under GPL.
# Authors: Cristian Gafton <gafton@redhat.com>,
#          Preston Brown <pbrown@redhat.com>
#
# $Id: rhn_check.py,v 1.45 2001/04/06 02:28:45 alikins Exp $

import os
import sys
import socket
import string
import getopt
import rpm
import xmlrpclib
import fnmatch
import traceback

sys.path.append("/usr/share/rhn/up2date")

import up2date

# action version we understand
ACTION_VERSION = 1

argVerbose = 0          # verbose output (might clutter syslog)

def hasSSL():
    return hasattr(socket, "ssl")

# Where do we keep the CA certificate for RHNS?
# The servers we're talking to need to have their certs
# signed by one of these CA.
rhns_ca_cert = "/usr/share/rhn/RHNS-CA-CERT"

###
# MAIN CLIENT CLASS
###
class client:
    def __init__(self):
        self.functions = []
        self.functions.append("refresh")
        self.functions.append("new_systemid")
        self.functions.append("update_packages")
        self.functions.append("up2date")
        self.functions.append("updates_message")

        
    def get_function(self, function):
        if function in self.functions:
            return getattr(self, function)
        return None


    # push again the list of rpm packages to the server
    def refresh_rpmlist(self):
	s = up2date.getServer()
 
        if argVerbose > 1:
            print "Called refresh_rpmlist"
           
        ret = None
        try:
            s.registration.update_packages(up2date.getSystemId(),
                                           up2date.getInstalledPackageList())
        except:
            print "ERROR: refreshing remote package list for System Profile"
            return xmlrpclib.Fault(-1, "Error refreshing package list")
        return 0


    # resync hardware information with the server profile
    def refresh_hardware(self):
        import hardware
        hardware.read_kudzu()
        hardware.read_cpuinfo()
        hardware.read_memory()
        hardware.read_network()
        hardwareList = hardware.Hardware
        
	s = up2date.getServer()
	
        if argVerbose > 1:
            print "Called refresh_hardware"

        try:
            s.registration.refresh_hw_profile(up2date.getSystemId(),
                                              hardwareList)
        except:
            print "ERROR: sending hardware database for System Profile"
            return xmlrpclib.Fault(-1, "Error refreshing system hardware")
        return 0

    
    def refresh(self, database):
        """Refresh the RHN image of what is available on this server"""
        if type(database) != type(""):
            return xmlrpclib.Fault(-8, "Invalid refresh action rquested")
        action = "refresh_%s" % string.lower(database)
        func = None
        if hasattr(self, action):
            func = getattr(self, action)
        if func == None:
            return xmlrpclib.Fault(-9, "Invalid refresh action called")
        return func()


    def new_systemid(self, id):
        """We have been given a new certificate - install it"""

        if argVerbose > 1:
            print "Called new_systemid"

        path = up2date.cfg.readEntry("systemIdPath")
        try:
            os.rename(path, path + ".bak")
        except:
            print "ERROR: could not back up %s" % path
            return xmlrpclib.Fault(-1, "Could not back up old SystemID")

        f = open(path, "w")
        f.write(id)
        f.close()
        try:
            os.chmod(path, 0600)
        except:
            print "ERROR: could not adjust permissions to 0600 on %s" % path
            return xmlrpclib.Fault(-2, "Could not adjust permissions on new SystemID")
        return 0


    def update_packages(self, pkgList):        
        """We have been told that we should retrieve/install packages"""

        def rpmCallback(what, amount, total, hdr, path):
            if what == rpm.RPMCALLBACK_INST_OPEN_FILE:
                global fd
                fileName = "%s/%s-%s-%s.%s.rpm" % (path,
                                                   hdr['name'],
                                                   hdr['version'],
                                                   hdr['release'],
                                                   hdr['arch'])
                try:
                     fd = os.open(fileName, os.O_RDONLY)
                except:
                    print "Error opening %s for reading" % fileName
                    return -1

                return fd
            elif what == rpm.RPMCALLBACK_INST_START:
                fileName = "%s-%s-%s.%s.rpm" % (hdr['name'],
                                                hdr['version'],
                                                hdr['release'],
                                                hdr['arch'])
            elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
                os.close(fd)

            elif what == rpm.RPMCALLBACK_INST_PROGRESS:
                pass
            elif what == rpm.RPMCALLBACK_UNINST_STOP:
                pass

        if type(pkgList) != type([]):
            return xmlrpclib.Fault(-8, "Invalid arguments passed to function")
        
        if argVerbose > 1:
            print "Called update_packages(%s)" % (pkgList,)

        return self.batchRun(0,pkgList)


    def batchRun(self,onlyList,pkgList):
        import wrapper
        pkgNames = map(lambda a:a[0], pkgList)
        wrapper.batchRun(onlyList,pkgNames,
                         1,fromDaemon=1,
                         actionPkgs=pkgList)
        return 0
 
    def updates_message(self, message):
        pass

        
# Exceptions
class UnknownXML:
    def __init__(self, value):
        self.__value = value
        
    def __repr__(self):
        return "Invalid request received (%s)." % self.__value


def selectPackages2(pkgNames):
    selPackages = []
    try:
        allPackages = up2date.getAvailablePackageList()
    except:
        print _("Error getting available package list")
        return selPackages

    for pkg in allPackages:
        if pkg[0] in pkgNames:
            selPackages.append(pkg)

    return selPackages

# Execute the right function in the right class
def do_call(method, params):
    global argVerbose
    
    if argVerbose > 1:
        print "do_call(%s, %s)" % (method, params)
        
    try:
        classname, funcname = string.split(method, '.')
    except:
        raise UnknownXML("method '%s' doesn't have a class and function" % method)
    if not classname or not funcname:
        raise UnknownXML(method)
    if classname == "client":
        c = client()
    else:
        raise UnknownXML("class: %s" % method)
    f = c.get_function(funcname)
    if not f:
        raise UnknownXML("function: %s" % method)
    
    retval = apply(f, params)
    return retval

# submit a response for an action_id
def submit_response(action_id, response):
    # try to submit
    try:
        ret = server.queue.submit(up2date.getSystemId(), action_id, response)
    except xmlrpclib.Fault, f:
        print "Could not submit results to server %s" % server
        print "Error code: %d%s" % (f.faultCode, f.faultString)
        sys.exit(-1)
    except socket.sslerror:
        print "ERROR: SSL handshake to %s failed" % server
        print """
        This could signal that you are *NOT* talking to a server
        whose certificate was signed by a Certificate Authority
        listed in the %s file or that the
        RHNS-CA-CERT file is invalid.""" % rhns_ca_cert
        sys.exit(-1)
    return ret

###
# Functions
###
def check_action(action):
    global argVerbose
    
    if argVerbose > 1:
        print "check_action(%s)" % action
        
    # be very paranoid of what we get back
    if type(action) != type({}):
        print "Got unparseable action response from server"
        if argVerbose > 1:
            print action
        sys.exit(-1)

    for key in ['id', 'version', 'action']:
        if not action.has_key(key):
            print "Got invalid response - missing '%s'" % key
            if argVerbose > 1:
                print action
            sys.exit(-1)
    try:
        ver = int(action['version'])
    except:
        ver = -1
    if ver > ACTION_VERSION or ver < 0:
        print "Got unknown action version %d" % ver
        print action
        # the -99 here is kind of magic
        submit_response(action["id"],
                        xmlrpclib.Fault(-99, "Can not handle this version"))
        return -1
    return 0

# Wrapper handler for the action we're asked to do
def handle_action(action):
    global argVerbose
    global server
    
    if argVerbose > 1:
        print "handle_action(%s)" % action
        
    version = action['version']
    action_id = action['id']
    data = action['action']

    if argVerbose > 0:
        print "handle_action actionid = %s, version = %s" % (action_id, version)
        
    # Decipher the data
    parser, decoder = xmlrpclib.getparser()
    parser.feed(data)
    parser.close()
    params = decoder.close()
    method = decoder.getmethodname()

    try:
        response = do_call(method, params)   
    except (TypeError, ValueError, KeyError, IndexError):
        if argVerbose > 1:
            traceback.print_exc()            
        # wrong number of arguments, wrong type of arguments, etc
        response = xmlrpclib.Fault(-8, "Fatal error occured")   
    except UnknownXML:
        if argVerbose > 1:
            print "Got unknown XML-RPC call %s(%s)" % (method, params)
        # invalid function called
        response = xmlrpclib.Fault(-9, "Invalid function call attempted")   

    if argVerbose > 0:
        print "Sending back response:", response
        
    return submit_response(action_id, response)
    
###
# Init
###
opts, args = getopt.getopt(sys.argv[1:], "v",
                           ["verbose"])
for (optname, optval) in opts:
    if optname == "--verbose" or optname == "-v":
        argVerbose = argVerbose + 1

# retrieve the system_id. This is required.
if not up2date.getSystemId():
    print "ERROR: unable to read system id."
    sys.exit(-1)

# require RHNS-CA-CERT file to be able to authenticate the SSL connections
if not os.access(rhns_ca_cert, os.R_OK):
    if argVerbose > 0:
        print "ERROR: can not find RHNS CA file: %s" % rhns_ca_cert
    sys.exit(-1)
    
# Initialize the server connection...
server = up2date.getServer()
# and force the validation of the SSL connection
server.use_CA_chain(rhns_ca_cert)

###
# Main PROGRAM
###

# Process all the actions we have in the queue
while 1:
    global ACTION_VERSION
    try:
        action = server.queue.get(up2date.getSystemId(), ACTION_VERSION)
    except xmlrpclib.Fault, f:
        print "Could not submit results to server %s" % server
        print "Error code: %d%s" % (f.faultCode, f.faultString)
        sys.exit(-1)
    except socket.sslerror:
        print "ERROR: SSL handshake to %s failed" % server
        print """
        This could signal that you are *NOT* talking to a server
        whose certificate was signed by a Certificate Authority
        listed in the %s file or that the
        RHNS-CA-CERT file is invalid.""" % rhns_ca_cert
        sys.exit(-1)
    except socket.error:
	print "Host not found.  Guess there's no network connection."
	sys.exit(-1)
    if action == "" or action == {}:
        break
    if check_action(action) == 0:
        handle_action(action)

sys.exit(0)
