#!/usr/bin/python

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# by James Antill

from yum.plugins import TYPE_INTERACTIVE, PluginYumExit
import rpmUtils.transaction

import os
import time
import fnmatch
import yum.pgpmsg
try:
    import gpgme
except:
    gpgme = None

requires_api_version = '2.1'
plugin_type = (TYPE_INTERACTIVE,)

def gpgkey_fingerprint_ascii(gpg_cert, chop=4):
    ''' Given a key_info data from getgpgkeyinfo(), return an ascii
    fingerprint. Chop every 4 ascii values, as that is what GPG does. '''
    fp = yum.pgpmsg.str_to_hex(gpg_cert.public_key.fingerprint())
    if chop:
        fp = [fp[i:i+chop] for i in range(0, len(fp), chop)]
        fp = " ".join(fp)
    return fp

def match_keys(patterns, key, globs=True):
    for pat in patterns:
        if pat == key.keyid:
            return True
        if pat == "%s-%x" % (key.keyid, key.createts):
            return True
        if pat == key.sum_auth:
            return True
        if pat == key.sum_auth_name:
            return True
        if pat == key.sum_auth_email:
            return True
        if globs and fnmatch.fnmatch(key.sum_auth, pat):
            return True
    return False

class Key:

    def __init__(self, keyid, createts, sum_type, sum_auth, data,
                 gpgctx=None, gpgkey=None, gpgsubkey=None, repoid="installed"):
        self.keyid    = keyid
        self.createts = createts
        self.sum_type = sum_type
        self.sum_auth = sum_auth
        self.data     = data
        self.gpgctx   = gpgctx
        self.gpgkey   = gpgkey
        self.gpgsubkey = gpgsubkey
        self.repoid   = repoid

        email_beg = sum_auth.rfind('<')
        if email_beg == -1:
            self.sum_auth_name  = sum_auth
            self.sum_auth_email = ""
        else:
            self.sum_auth_name  = sum_auth[:email_beg]
            if sum_auth[-1] == '>':
                self.sum_auth_email = sum_auth[email_beg+1:-1]
            else:
                self.sum_auth_email = sum_auth[email_beg:]

    def __cmp__(self, other):
        if other is None:
            return 1

        ret = cmp(self.sum_type, other.sum_type)
        if ret: return ret
        ret = cmp(self.sum_auth, other.sum_auth)
        if ret: return ret
        ret = cmp(self.keyid, other.keyid)
        if ret: return ret
        # Never gets here on diff. keys?
        ret = cmp(self.createts, other.createts)
        return ret

class KeysListCommand:

    def __init__(self):
        self.columns = None

    def getNames(self):
        return ["keys", "keys-list"]

    def getUsage(self):
        return "[key-wildcard]"

    def getSummary(self):
        return "Lists keys for signing data"

    def doCheck(self, base, basecmd, extcmds):
        pass

    def show_hdr(self, base):
        columns = zip(("Key owner", "Key email", "Repo", "Key ID"),self.columns)
        print base.fmtColumns(columns)

    def match_key(self, patterns, key):
        return match_keys(patterns, key)

    def show_key(self, base, key):
        columns = zip((key.sum_auth_name, key.sum_auth_email, key.repoid,
                       ("%s-%x" % (key.keyid, key.createts))), self.columns)
        print base.fmtColumns(columns)

    def doCommand(self, base, basecmd, extcmds):
        if hasattr(base, 'run_with_package_names'):
            base.run_with_package_names.add("yum-plugin-keys")

        self.exit_code = 0

        keys = []
        ts = rpmUtils.transaction.TransactionWrapper(base.conf.installroot)
        for hdr in ts.dbMatch('name', 'gpg-pubkey'):
            keyid    = hdr['version']
            createts = int(hdr['release'], 16)

            sum_auth = hdr['summary']
            sum_auth = sum_auth.strip()
            if not sum_auth.startswith('gpg(') or not sum_auth.endswith(')'):
                sum_type = "<?>"
            else:
                sum_auth = sum_auth[4:-1]
                sum_type = "GPG"

            data = hdr['description']

            keys.append(Key(keyid, createts, sum_type, sum_auth, data))
        if gpgme is not None:
            base.repos.doSetup() # Without this repo.cachedir is empty *sigh*
            for repo in base.repos.listEnabled():
              if hasattr(repo, 'gpgcadir'):
                  gpgdirs = [repo.gpgcadir, repo.gpgdir]
              else:
                  gpgdirs = ['%s/gpgdir' % repo.cachedir]

              for gpgdir in gpgdirs:
                if not os.path.exists(gpgdir):
                    continue

                # Borrowed from misc.return_keyids_from_pubring()
                os.environ['GNUPGHOME'] = gpgdir
                ctx = gpgme.Context()
                for k in ctx.keylist():
                    auth  = "%s <%s>" % (k.uids[0].name, k.uids[0].email)
                    for subkey in k.subkeys:
                        if subkey.can_sign:
                            keyid = "%08x" % (int(subkey.keyid,16) & 0xFFFFFFFF)
                            keys.append(Key(keyid, subkey.timestamp,
                                            "GPG", auth, "<not-implemented>",
                                            ctx, k, subkey, repo.id))

        if self.columns is None and not hasattr(base, 'calcColumns'):
            self.columns = (-15, -22, -22, 17)
        elif self.columns is None:
            data = {'owner' : {}, 'email' : {}, 'rid' : {}, 'kid' : {}}
            for key in keys:
                if len(extcmds) and not self.match_key(extcmds, key):
                    continue
                for (d, v) in (("owner", len(key.sum_auth_name)),
                               ("email", len(key.sum_auth_email)),
                               ("rid", len(key.repoid)),
                               ("kid", (len(key.keyid) + 1 +
                                        len("%x" % key.createts)))):
                    data[d].setdefault(v, 0)
                    data[d][v] += 1
            data = [data['owner'], data['email'], data['rid'], data['kid']]
            columns = base.calcColumns(data)
            self.columns = [-columns[0], -columns[1], -columns[2], columns[3]]

        done = False
        for key in sorted(keys):
            if not len(extcmds) or self.match_key(extcmds, key):
                if not done:
                    self.show_hdr(base)
                done = True
                self.show_key(base, key)
        
        return self.exit_code, [basecmd + ' done']

    def needTs(self, base, basecmd, extcmds):
        return False

class KeysInfoCommand(KeysListCommand):

    def __init__(self):
        self.columns = [1] * 4

    def getNames(self):
        return ["keys-info"]

    def getSummary(self):
        return "Full information keys for signing data"

    def show_hdr(self, base):
        pass

    def show_key(self, base, key):
        rpmkeyid = "%s-%x" % (key.keyid, key.createts)
        if key.repoid != "installed":
            print """\
Type       : %s
Rpm Key ID : %s
Key owner  : %s
Key email  : %s
Repo       : %s
Created    : %s
Fingerprint: %x
Key ID     : %x
""" % (key.sum_type, rpmkeyid,
       key.sum_auth_name, key.sum_auth_email, key.repoid,
       time.ctime(key.createts),
       int(key.gpgsubkey.fpr, 16), int(key.gpgsubkey.keyid, 16))
        elif key.sum_type == '<?>':
            print """\
Type      : Unknown
Rpm Key ID: %s
Key owner : %s
Key email : %s
Repo      : installed
Created   : %s
""" % (rpmkeyid,
       key.sum_auth_name, key.sum_auth_email, time.ctime(key.createts))
        else:
            if yum.__version_info__ > (3, 2, 28):
                gpg_certs = yum.pgpmsg.decode_msg(key.data, multi=True)
            else:
                gpg_certs = [yum.pgpmsg.decode_msg(key.data)]
            for gpg_cert in gpg_certs:
                print """\
Type       : %s
Rpm Key ID : %s
Key owner  : %s
Key email  : %s
Repo       : installed
Created    : %s
Version    : PGP Public Key Certificate v%d
Primary ID : %s
Algorithm  : %s
Fingerprint: %s
Key ID     : %s
""" % (key.sum_type, rpmkeyid, key.sum_auth_name, key.sum_auth_email,
       time.ctime(key.createts),
       gpg_cert.version, gpg_cert.user_id,
       yum.pgpmsg.algo_pk_to_str[gpg_cert.public_key.pk_algo],
       gpgkey_fingerprint_ascii(gpg_cert),
       yum.pgpmsg.str_to_hex(gpg_cert.public_key.key_id()))


class KeysDataCommand(KeysListCommand):

    def __init__(self):
        self.columns = [1] * 4

    def getNames(self):
        return ["keys-data"]

    def getSummary(self):
        return "Show public key block information for signing data"

    def show_hdr(self, base):
        pass

    def show_key(self, base, key):
        print """\
Type     : %s
Key owner: %s
Key email: %s
Key ID   : %s
Repo     : %s
Created  : %s
Raw Data :
  %s
""" % (key.sum_type, key.sum_auth_name, key.sum_auth_email,
       key.keyid, key.repoid, time.ctime(key.createts),
       key.data.replace('\n', '\n  '))


class KeysRemoveCommand(KeysListCommand):

    def __init__(self):
        self.columns = [1] * 4

    def getNames(self):
        return ["keys-remove", "keys-erase"]

    def getSummary(self):
        return "Remove a public key block for signing data"

    def show_hdr(self, base):
        pass

    def show_key(self, base, key):
        if key.repoid == "installed":
            release = "%x" % key.createts
            if hasattr(base.rpmdb, 'returnGPGPubkeyPackages'): # New style
                for pkg in base.rpmdb.returnGPGPubkeyPackages():
                    if pkg.version == key.keyid and pkg.release == release:
                        base.remove(po=pkg)
            else:
                base.remove(name='gpg-pubkey',
                            version=key.keyid, release=release)
            self.exit_code = 2
        else:
            print "Delete key %s-%x from %s?" % (key.keyid, key.createts,
                                                 key.repoid)
            if base.userconfirm():
                key.gpgctx.delete(key.gpgkey)

    def match_key(self, patterns, key):
        return match_keys(patterns, key, globs=False)

    def doCheck(self, base, basecmd, extcmds):
        if base.conf.uid:
            raise PluginYumExit('You need to be root to perform this command.')
        if not len(extcmds):
            raise PluginYumExit('You need to specify a key to remove.')
        if hasattr(base, 'run_with_package_names'):
            base.run_with_package_names.add("yum-plugin-keys")


def config_hook(conduit):
    conduit.registerCommand(KeysListCommand())
    conduit.registerCommand(KeysInfoCommand())
    conduit.registerCommand(KeysDataCommand())
    conduit.registerCommand(KeysRemoveCommand())
