????

Your IP : 3.14.130.45


Current Path : /usr/share/yum-plugins/
Upload File :
Current File : //usr/share/yum-plugins/rhnplugin.py

"""
Yum plugin for RHN access.

This plugin provides access to Red Hat Network to yum via up2date modules
and XMLRPC calls.
"""

import os
import sys
import urllib

from yum.plugins import TYPE_CORE
from yum.yumRepo import YumRepository

import yum.Errors

from urlgrabber.grabber import URLGrabber
from urlgrabber.grabber import URLGrabError

try:
    from urlgrabber.grabber import pycurl
except:
    pycurl = None

from iniparse import INIConfig
import gettext
_ = gettext.gettext

# TODO: Get the up2date stuff that we need in a better place,
# so we don't have to do path magic.
sys.path.append("/usr/share/rhn/")

import up2date_client.up2dateAuth as up2dateAuth
from up2date_client import config
from up2date_client import rhnChannel
from up2date_client import rhnPackageInfo
from up2date_client import up2dateErrors
import rhn.transports

__revision__ = "$Rev$"

requires_api_version = '2.5'
plugin_type = TYPE_CORE
pcklAuthFileName = "/var/spool/up2date/loginAuth.pkl"

rhn_enabled = True

COMMUNICATION_ERROR = _("There was an error communicating with CLN.")

from M2Crypto.SSL import SSLError, Connection

def bypass_m2crypto_ssl_connection_check(*args, **kw):
    """This needs to return True, it's used to bypass a check in 
    M2Crypto.SSL.Connection
    """
    return True

def clnreg():
    os.system('/usr/sbin/clnreg_ks')

def init_hook(conduit):
    """ 
    Plugin initialization hook. We setup the RHN channels here. 
    
    We get a list of RHN channels from the server, then make a repo object for
    each one. This list of repos is then added to yum's list of repos via the 
    conduit.
    """

    global rhn_enabled
   
    RHN_DISABLED = _("CLN support will be disabled.")
    
    #####bz 332011 
    #This will bypass a check in M2Crypto.SSL.Connection.
    #The check we are bypassing, was causing an exception on multi-homed machines
    #when the SSL Cert "commonName" did not match the name used to connect to the host.
    #This functionality was different than RHEL4, desire was to bypass the check to 
    #maintain the functionality in RHEL4 
    setattr(Connection, "clientPostConnectionCheck", bypass_m2crypto_ssl_connection_check)

    if not os.geteuid()==0:
        # If non-root notify user RHN repo not accessible
        conduit.error(0, _("*Note* CloudLinux Network repositories are not listed below. You must run this command as root to access CLN repositories."))
        rhn_enabled = False
        return

    clnreg_retry = 0
    up2date_cfg = config.initUp2dateConfig()
    mirrorURL = get_mirror_url(up2date_cfg)
    while clnreg_retry < 2:
        clnreg_retry = clnreg_retry + 1
        try:
            proxy_url = get_proxy_url(up2date_cfg)
            proxy_dict = {}
            if up2date_cfg['useNoSSLForPackages']:
                proxy_dict = {'HTTP' : proxy_url}
            else:
                proxy_dict = {'HTTPS' : proxy_url}
        except BadProxyConfig:
            rhn_enabled = False
            PROXY_ERROR =  _("There was an error parsing the CLN proxy settings.") 
            conduit.error(0, PROXY_ERROR + "\n" + RHN_DISABLED)
            return 

        try:
            login_info = up2dateAuth.getLoginInfo()
        except up2dateErrors.RhnServerException, e:
            if clnreg_retry == 1:
                clnreg()
                continue
            else:
                rewordError(e)
                conduit.error(0, COMMUNICATION_ERROR + "\n" + RHN_DISABLED + "\n" +
                              str(e))
                rhn_enabled = False
                return

        if not login_info:
            if clnreg_retry == 1:
                clnreg()
                continue
            else:
                conduit.error(0, _("This system is not registered with CLN.") + "\n" +
                              RHN_DISABLED)
                rhn_enabled = False
                return 

        CHANNELS_DISABLED = _("CLN channel support will be disabled.")
        try:
            svrChannels = rhnChannel.getChannelDetails()
        except up2dateErrors.NoChannelsError:
            if clnreg_retry == 1:
                clnreg()
            else:
                conduit.error(0, _("This system is not subscribed to any channels.") + 
                              "\n" + CHANNELS_DISABLED)
                return
        except up2dateErrors.NoSystemIdError:
            if clnreg_retry == 1:
                clnreg()
            else:
                conduit.error(0, _("This system may not be a registered to CLN. SystemId could not be acquired.\n") +
                              RHN_DISABLED)
                rhn_enabled = False
                return
        except up2dateErrors.RhnServerException, e:
            if clnreg_retry == 1:
                clnreg()
            else:
                conduit.error(0, COMMUNICATION_ERROR + "\n" + CHANNELS_DISABLED + 
                              "\n" + str(e))
                return

    repos = conduit.getRepos()
    cachedir = conduit.getConf().cachedir
    default_gpgcheck = conduit.getConf().gpgcheck
    gpgcheck = conduit.confBool('main', 'gpgcheck', default_gpgcheck)
    sslcacert = get_ssl_ca_cert(up2date_cfg)
    enablegroups = conduit.getConf().enablegroups

    for channel in svrChannels:
        if channel['version']:
            repo = RhnRepo(channel, mirrorURL, cachedir)
            repo.basecachedir = cachedir
            repo.base_persistdir = cachedir
            repo.gpgcheck = gpgcheck
            repo.proxy = proxy_url
            repo.sslcacert = sslcacert
            repo.enablegroups = enablegroups
            repoOptions = getRHNRepoOptions(conduit, repo.id)
            if repoOptions:
                for o in repoOptions:
                    setattr(repo, o[0], o[1])
                    conduit.info(5, "Repo '%s' setting option '%s' = '%s'" %
                            (repo.id, o[0], o[1]))

            repos.add(repo)

#bz226151,441265
#Allows a "yum clean all" to succeed without communicating
#to backend.  Creating a set of dummy repos which mimic the dirs stored locally
#This gives yum the dir info it needs to peform a clean
#
def formReposForClean(conduit):
    repos = conduit.getRepos()
    cachedir = conduit.getConf().cachedir
    try:
        dir_list = os.listdir(cachedir)
    except Exception, e:
        raise yum.Errors.RepoError(str(e))
    urls = ["http://dummyvalue"]
    for dir in dir_list:
        if dir[0] == ".":
            continue
        if os.path.isdir(os.path.join(cachedir,dir)):
            repo = YumRepository(dir)
            repo.basecachedir = cachedir
            repo.base_persistdir = cachedir
            repo.baseurl = urls 
            repo.urls = repo.baseurl
            repo.enable()
            if not repos.findRepos(repo.id):
                repos.add(repo)
   # cleanup cached login info
    if os.path.exists(pcklAuthFileName):
        os.unlink(pcklAuthFileName)

def posttrans_hook(conduit):
    """ Post rpm transaction hook. We update the RHN profile here. """
    global rhn_enabled
    if rhn_enabled:
        up2date_cfg = config.initUp2dateConfig()
        if up2date_cfg.has_key('writeChangesToLog') and up2date_cfg['writeChangesToLog'] == 1:
            ts_info = conduit.getTsInfo()
            delta = make_package_delta(ts_info)
            rhnPackageInfo.logDeltaPackages(delta)
        try:
            rhnPackageInfo.updatePackageProfile()
        except up2dateErrors.RhnServerException, e:
            conduit.error(0, COMMUNICATION_ERROR + "\n" +
                _("Package profile information could not be sent.") + "\n" + 
                str(e))

def rewordError(e):
    #This is compensating for hosted/satellite returning back an error
    #message instructing RHEL5 clients to run "up2date --register"
    #bz: 438175
    replacedText = _("Error Message:") + "\n\t" + \
        _("Please run rhn_register as root on this client")
    index = e.errmsg.find(": 9\n")
    if index == -1:
        return
    if e.errmsg.find("up2date", 0, index) == -1:
        return
    #Find where the "Error Class Code" text begins, to account
    #for different languages, looking for new line character
    #preceeding the Error Class Code
    indexB = e.errmsg.rfind("\n", 0, index)
    e.errmsg = "\n" + replacedText + e.errmsg[indexB:]
                
            

class RhnRepo(YumRepository):

    """
    Repository object for Red Hat Network.

    This, along with the RhnPackageSack, adapts up2date for use with
    yum.
    """
    rhn_needed_headers = ['X-RHN-Server-Id',
                          'X-RHN-Auth-User-Id',
                          'X-RHN-Auth',
                          'X-RHN-Auth-Server-Time',
                          'X-RHN-Auth-Expire-Offset']
    
    def __init__(self, channel, mirrorURL, cachedir):
        YumRepository.__init__(self, channel['label'])
        self.basecachedir = cachedir
        self.base_persistdir = cachedir
        self.name = channel['name']
        self.label = channel['label']
        self._callbacks_changed = False
        # support failover urls, #232567
        urls = []
        self.mirrorlist = mirrorURL
        
	for url in self.urls:

	    urls.append(url + 'GET-REQ/'+self.id)
        self.baseurl = urls 
        self.urls = self.baseurl
        self.failovermethod = 'priority'
        self.keepalive = True
        self.bandwidth = 0
        self.retries = 10
        self.throttle = 0
        self.timeout = 60.0

        self.http_caching = True

        self.gpgkey = []
        self.gpgcheck = False

        try:
            self.gpgkey = get_gpg_key_urls(channel['gpg_key_url'])
        except InvalidGpgKeyLocation:
            #TODO: Warn about this or log it
            pass

        self.enable()

    def setupRhnHttpHeaders(self):
        """ Set up self.http_headers with needed RHN X-RHN-blah headers """
        
        try:
            li = up2dateAuth.getLoginInfo()
        except up2dateErrors.RhnServerException, e:
            raise yum.Errors.RepoError(str(e))

        # TODO:  do evalution on li auth times to see if we need to obtain a
        # new session...

        for header in RhnRepo.rhn_needed_headers:
            if not li.has_key(header):
                error = _("Missing required login information for CLN: %s") \
                    % header
                raise yum.Errors.RepoError(error)
            
            self.http_headers[header] = li[header]
        # Set the redirect flag
        self.http_headers['X-RHN-Transport-Capability'] = "follow-redirects=3"

    # Override the 'private' __get method so we can do our auth stuff.
    def _getFile(self, url=None, relative=None, local=None,
        start=None, end=None, copy_local=0, checkfunc=None, text=None,
        reget='simple', cache=True, size=None):

        for server in self.urls:
            url = server
            try:
                try:
                    return self._noExceptionWrappingGet(url, relative, local,
                        start, end, copy_local, checkfunc, text, reget, cache, size)
                except URLGrabError, e:
                    if (not self.mirrorlist.count('file:///')):
                        continue
                    else:
                        try:
                            up2dateAuth.updateLoginInfo(mirrorServer=url)
                        except up2dateErrors.RhnServerException, e:
                            raise yum.Errors.RepoError(str(e))

                return self._noExceptionWrappingGet(url, relative, local,
                    start, end, copy_local, checkfunc, text, reget, cache, size)

            except URLGrabError, e:
                raise yum.Errors.RepoError, \
                    "failed to retrieve %s from %s\nerror was %s" % (relative, self.id, e)
            except SSLError, e:
                raise yum.Errors.RepoError(str(e))
            except up2dateErrors.InvalidRedirectionError, e:
                raise up2dateErrors.InvalidRedirectionError(e)
    _YumRepository__get = _getFile

    # This code is copied from yum, we should get the original code to
    # provide more detail in its exception, so we don't have to cut n' paste
    
    def _noExceptionWrappingGet(self, url=None, relative=None, local=None,
        start=None, end=None, copy_local=0, checkfunc=None, text=None,
        reget='simple', cache=True, size=None):
        """retrieve file from the mirrorgroup for the repo
           relative to local, optionally get range from
           start to end, also optionally retrieve from a specific baseurl"""

        # if local or relative is None: raise an exception b/c that shouldn't happen
        # return the path to the local file
       
        self.setupRhnHttpHeaders()
        if pycurl:
            # pycurl/libcurl workaround: in libcurl setting an empty HTTP header means
            # remove that header from the list
            # but we have to send and empty X-RHN-Auth-User-Id ...
            AuthUserH = 'X-RHN-Auth-User-Id'
            if (AuthUserH in self.http_headers and not self.http_headers[AuthUserH]):
                self.http_headers[AuthUserH] = "\r\nX-libcurl-Empty-Header-Workaround: *"

        # Turn our dict into a list of 2-tuples
        headers = YumRepository._YumRepository__headersListFromDict(self)

        # We will always prefer to send no-cache.
        if not (cache or self.http_headers.has_key('Pragma')):
            headers.append(('Pragma', 'no-cache'))

        headers = tuple(headers)

        if local is None or relative is None:
            raise yum.Errors.RepoError, \
                  "get request for Repo %s, gave no source or dest" % self.id

        if self.failure_obj:
            (f_func, f_args, f_kwargs) = self.failure_obj
            self.failure_obj = (f_func, f_args, f_kwargs)

        if self.cache == 1:
            if os.path.exists(local): # FIXME - we should figure out a way
                return local          # to run the checkfunc from here

            else: # ain't there - raise
                raise yum.Errors.RepoError, \
                    "Caching enabled but no local cache of %s from %s" % (local,
                           self)
                           
        if url is not None:
            remote = url + '/' + relative
            result = self.grab.urlgrab(remote, local,
                                      text = text,
                                      range = (start, end),
                                      copy_local=copy_local,
                                      reget = reget,
                                      checkfunc=checkfunc,
                                      http_headers=headers,
                                      ssl_ca_cert = self.sslcacert,
                                      timeout=self.timeout,
                                      size = size
                                      )
            if (result):
                if (self.urls[0] != url):
                    self.urls.pop(self.urls.index(url))
                    self.urls.insert(0,url)
            return result

        result = None
        urlException = None
        for server in self.urls:
            #force to http if configured
            up2date_cfg = config.initUp2dateConfig()
            if up2date_cfg['useNoSSLForPackages'] == 1:
                server = force_http(server)
            #Sanity check the url
            check_url(server)
            # Construct the full url string
            remote = server + '/' + relative
            try:
                result = self.grab.urlgrab(remote, local,
                                          text = text,
                                          range = (start, end),
                                          copy_local=copy_local,
                                          reget = reget,
                                          checkfunc=checkfunc,
                                          http_headers=headers,
                                          ssl_ca_cert = self.sslcacert,
                                          timeout=self.timeout,
                                          size = size
                                          )
                return result
            except URLGrabError, e:
		print e, remote
                urlException = e
                continue
            
        if result == None:
            raise urlException
        return result

    def _setupGrab(self):
        """sets up the grabber functions. We don't want to use mirrors."""

        headers = tuple(YumRepository._YumRepository__headersListFromDict(self))

        self._grabfunc = URLGrabber(keepalive=self.keepalive,
                                   bandwidth=self.bandwidth,
                                   retry=self.retries,
                                   throttle=self.throttle,
                                   progress_obj=self.callback,
                                   proxies = self.proxy_dict,
                                   interrupt_callback=self.interrupt_callback,
                                   timeout=self.timeout,
                                   http_headers=headers,
                                   reget='simple')
        #bz453690 ensure that user-agent header matches for communication from
        #up2date library calls, as well as yum-rhn-plugin calls
        self._grabfunc.opts.user_agent = rhn.transports.Transport.user_agent
        self._grab = self._grabfunc
    setupGrab = _setupGrab

    def _getgrabfunc(self):
        if not self._grabfunc or self._callbacks_changed:
            self._setupGrab()
            self._callbacks_changed = False
        return self._grabfunc
    def _getgrab(self):
        if not self._grab or self._callbacks_changed:
            self._setupGrab()
            self._callbacks_changed = False
        return self._grab

    grabfunc = property(lambda self: self._getgrabfunc())
    grab = property(lambda self: self._getgrab())

    def _setChannelEnable(self, value=1):
        """ Enable or disable channel in file rhnplugin.conf.
            channel is label of channel and value should be 1 or 0.
        """
        cfg = INIConfig(file('/etc/yum/pluginconf.d/rhnplugin.conf'))
        # we cannot use directly cfg[channel].['enabled'], because
        # if that section do not exist it raise error
        func=getattr(cfg, self.label)
        func.enabled=value
        f=open('/etc/yum/pluginconf.d/rhnplugin.conf', 'w')
        print >>f, cfg
        f.close()

    def enablePersistent(self):
        """
        Persistently enable channel in rhnplugin.conf
        """
        self._setChannelEnable(1)
        self.enable()

    def disablePersistent(self):
        """
        Persistently disable channel in rhnplugin.conf
        """
        self._setChannelEnable(0)
        self.disable()

    def _getRepoXML(self):
        import yum.Errors
        try:
            return YumRepository._getRepoXML(self)
        except yum.Errors.RepoError, e:
            # Refresh our loginInfo then try again
            # possibly it's out of date
            up2dateAuth.updateLoginInfo()
            return YumRepository._getRepoXML(self)
            
    def getPackage(self, package, checkfunc=None, text=None, cache=True):
        remote = package.relativepath
        local = package.localPkg()
        basepath = package.basepath

        if self._preload_pkg_from_system_cache(package):
            if package.verifyLocalPkg():
                return local
            misc.unlink_f(local)

        return self._getFile(url=basepath,
                        relative=remote,
                        local=local,
                        checkfunc=checkfunc,
                        text=text,
                        cache=cache,
                        size=package.size,
                        )
    
def make_package_delta(ts_info):
    """
    Construct an RHN style package delta from a yum TransactionData object.

    Return a hash containing two keys: added and removed.
    Each key's value is a list of RHN style package tuples.
    """

    delta = {}
    delta["added"] = []
    delta["removed"] = []

    # Make sure the transaction data has the packages in nice lists.
    ts_info.makelists()

    for ts_member in ts_info.installed:
        package = ts_member.po
        pkgtup = __rhn_pkg_tup_from_po(package)
        delta["added"].append(pkgtup)

    for ts_member in ts_info.depinstalled:
        package = ts_member.po
        pkgtup = __rhn_pkg_tup_from_po(package)
        delta["added"].append(pkgtup)

    for ts_member in ts_info.updated:
        package = ts_member.po
        pkgtup = __rhn_pkg_tup_from_po(package)
        delta["added"].append(pkgtup)

    for ts_member in ts_info.depupdated:
        package = ts_member.po
        pkgtup = __rhn_pkg_tup_from_po(package)
        delta["added"].append(pkgtup)

    for ts_member in ts_info.removed:
        package = ts_member.po
        pkgtup = __rhn_pkg_tup_from_po(package)
        delta["removed"].append(pkgtup)

    for ts_member in ts_info.depremoved:
        package = ts_member.po
        pkgtup = __rhn_pkg_tup_from_po(package)
        delta["removed"].append(pkgtup)

    return delta

def __rhn_pkg_tup_from_po(package):
    """ Construct an rhn-style package tuple from a yum package object. """

    name = package.returnSimple('name')
    epoch = package.returnSimple('epoch')
    version = package.returnSimple('version')
    release = package.returnSimple('release')
    arch = package.returnSimple('arch')

    return (name, version, release, epoch, arch)


class BadConfig(Exception):
    pass

class BadProxyConfig(BadConfig):
    pass

class BadSslCaCertConfig(BadConfig):
    pass

def get_mirror_url(up2date_cfg):
    if not up2date_cfg['mirrorURL']:
	return 'http://repo.cloudlinux.com/cloudlinux/mirrorlists/cln-mirrors'
    else:
	return up2date_cfg['mirrorURL']

def get_proxy_url(up2date_cfg):
    if not up2date_cfg['enableProxy']:
        return None

    proxy_url = ""
    if up2date_cfg['useNoSSLForPackages']:
        proxy_url = 'http://'
    else:
        proxy_url = 'https://'
    if up2date_cfg['enableProxyAuth']:
        if not up2date_cfg.has_key('proxyUser') or \
            up2date_cfg['proxyUser'] == '':
            raise BadProxyConfig
        if not up2date_cfg.has_key('proxyPassword') or \
            up2date_cfg['proxyPassword'] == '':
            raise BadProxyConfig
        proxy_url = proxy_url + up2date_cfg['proxyUser']
        proxy_url = proxy_url + ':'
        proxy_url = proxy_url + urllib.quote(up2date_cfg['proxyPassword'])
        proxy_url = proxy_url + '@'
   
    netloc = up2date_cfg['httpProxy']
    if netloc == '':
        raise BadProxyConfig

    # Check if a protocol is supplied. We'll ignore it.
    proto_split = netloc.split('://')
    if len(proto_split) > 1:
       netloc = proto_split[1]

    return proxy_url + netloc


class InvalidGpgKeyLocation(Exception):
    pass

def is_valid_gpg_key_url(key_url):
    proto_split = key_url.split('://')
    if len(proto_split) != 2:
        return False
    
    proto, path = proto_split
    if proto.lower() != 'file':
        return False

    path = os.path.normpath(path)
    if not path.startswith('/etc/pki/rpm-gpg/'):
        return False
    return True

def get_gpg_key_urls(key_url_string):
    """
    Parse the key urls and validate them.

    key_url_string is a space seperated list of gpg key urls that must be
    located in /etc/pkg/rpm-gpg/.
    Return a list of strings containing the key urls.
    Raises InvalidGpgKeyLocation if any of the key urls are invalid.
    """
    key_urls = key_url_string.split()
    for key_url in key_urls:
        if not is_valid_gpg_key_url(key_url):
            raise InvalidGpgKeyLocation
    return key_urls

def get_ssl_ca_cert(up2date_cfg):
    if not (up2date_cfg.has_key('sslCACert') and up2date_cfg['sslCACert']):
        raise BadSslCaCertConfig

    ca_certs = up2date_cfg['sslCACert']
    if type(ca_certs) == list:
        return ca_certs[0]

    return ca_certs

def check_url(serverurl):
    typ, uri = urllib.splittype(serverurl)
    if typ != None:
        typ = typ.lower()
    up2date_cfg = config.initUp2dateConfig()
    if up2date_cfg['useNoSSLForPackages']:
        if typ.strip() not in ("http"):
            raise up2dateErrors.InvalidProtocolError("You specified an invalid "
                    "protocol.  Option useNoSSLServerForPackages requires http")
    elif typ.strip() not in ("http", "https"):
        raise up2dateErrors.InvalidProtocolError("You specified an invalid "
                                                 "protocol. Only https and "
                                                 "http are allowed.")

def force_http(serverurl):
    """
    Returns a url using http
    """
    httpUrl = serverurl
    typ, uri = urllib.splittype(serverurl)
    if typ != None:
        typ = typ.lower().strip()
    if typ not in ("http"):
        httpUrl = "http:" + uri
    return httpUrl

def getRHNRepoOptions(conduit, repoid):
    from ConfigParser import NoSectionError
    conduit.info(5, "Looking for repo options for [%s]" % (repoid))
    try:
        if conduit:
            if hasattr(conduit, "_conf") and hasattr(conduit._conf, "items"):
                return conduit._conf.items(repoid)
    except NoSectionError, e:
        pass
    return None