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

import os
import sys
import socket
import string
import getopt
import rpm
import fnmatch
import traceback
sys.path.append("/usr/share/rhn/")

from up2date_client import wrapper
from up2date_client import getMethod
from up2date_client.translate import _, N_, cat
from up2date_client import up2date
try:
    from rhn import rpclib
except ImportError:
    rpclib = __import__("xmlrpclib")

# action version we understand
ACTION_VERSION = 2 

# lock file to check if we're disabled at the server's request
DISABLE_FILE = "/etc/sysconfig/rhn/disable"

# Command line flags
argVerbose = 0          # verbose output (might clutter syslog)

# 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.
if up2date.cfg.readEntry("sslCACert"):
    rhns_ca_cert = up2date.cfg.readEntry("sslCACert")
else:
    rhns_ca_cert = "/usr/share/rhn/RHNS-CA-CERT"
    
# 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

def do_call(method, params):
    global argVerbose

    if argVerbose > 1:
        print "do_call(%s, %s)" % (method, params)

    method = getMethod.getMethod(method, "/usr/share/rhn/", "actions")
    retval = apply(method, params)
    return retval


# submit a response for an action_id
def submit_response(action_id, status, message, data):
    global server
    # try to submit
    try:
        ret = server.queue.submit(up2date.getSystemId(), action_id, status, message, data)
    except rpclib.Fault, f:
        print "Could not submit results to server %s" % server
        print "Error code: %d%s" % (f.faultCode, f.faultString)
        sys.exit(-1)
    # XXX: what if no SSL in socket?
    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 "Could not submit to %s.\nPossible networking problem?" % str(server)
        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"],
                        rpclib.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 = rpclib.getparser()
    parser.feed(data)
    parser.close()
    params = decoder.close()
    method = decoder.getmethodname()
    data = {}

    try:
        (status, message, data) = do_call(method, params)   
    except (TypeError, ValueError, KeyError, IndexError):
        if argVerbose > 1:
            traceback.print_exc()            
        # wrong number of arguments, wrong type of arguments, etc
        status = 6,
        message = "Fatal error in Python code occured"
    except UnknownXML:
        if argVerbose > 1:
            print "Got unknown XML-RPC call %s(%s)" % (method, params)
        # invalid function called
        status = 6
        message = "Invalid function call attempted"   

    if argVerbose > 0:
        print "Sending back response:", (status, message, data)

    return submit_response(action_id, status, message, data)
    
###
# Init
###
opts, args = getopt.getopt(sys.argv[1:], "v",
                           ["verbose"])
for (optname, optval) in opts:
    if optname == "--verbose" or optname == "-v":
        argVerbose = argVerbose + 1

# if we're disabled, go down (almost) quietly
if os.path.exists(DISABLE_FILE):
    print "RHN service is disabled. Check %s" % DISABLE_FILE
    sys.exit(0)
    
# 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)

try:
    up2date.updateLoginInfo()
except up2date.CommunicationError, e:
    print e
    sys.exit(1)
###
# Main PROGRAM
###

# Build a status report
Status = {}
Status["uname"] = os.uname()
if os.access("/proc/uptime", os.R_OK):
    uptime = string.split(open("/proc/uptime", "r").read())
    try:
        Status["uptime"] = map(int, map(float, uptime))
    except TypeError, ValueError:
        Status["uptime"] = map(lambda a: a[:-3], uptime)
    except:
        pass

# Process all the actions we have in the queue
while 1:
    global ACTION_VERSION
    global Status
    try:
        action = server.queue.get(up2date.getSystemId(), ACTION_VERSION, Status)
    except rpclib.Fault, f:
        print "Could not retrieve action item from server %s" % server
        print "Error code: %d%s" % (f.faultCode, f.faultString)
        sys.exit(-1)
    # XXX: what if no SSL in socket?
    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 "Could not retrieve action from %s.\nPossible networking problem?" % str(server)
	sys.exit(-1)
    if action == "" or action == {}:
        break
    if check_action(action) == 0:
        handle_action(action)

sys.exit(0)
