#  software-properties PPA support
#
#  Copyright (c) 2004-2009 Canonical Ltd.
#
#  Author: Michael Vogt <mvo@debian.org>
#
#  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 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

from threading import Thread
import re
import subprocess
import apt_pkg
import os
import tempfile
import pycurl

def expand_ppa_line(abrev, distro_codename):
    """ Convert an abbreviated ppa name of the form 'ppa:$name' to a
        proper sources.list line of the form 'deb ...' """
    # leave non-ppa: lines unchanged
    if not abrev.startswith("ppa:"):
        return (abrev, None)
    # FIXME: add support for dependency PPAs too (once we can get them
    #        via some sort of API, see LP #385129)
    abrev = abrev.split(":")[1]
    ppa_owner = abrev.split("/")[0]
    try:
        ppa_name = abrev.split("/")[1]
    except IndexError, e:
        ppa_name = "ppa"
    sourceslistd = apt_pkg.Config.FindDir("Dir::Etc::sourceparts")
    line = "deb http://ppa.launchpad.net/%s/%s/ubuntu %s main" % (
        ppa_owner, ppa_name, distro_codename)
    file = "%s/%s-%s-%s.list" % (
        sourceslistd, ppa_owner, ppa_name, distro_codename)
    return (line, file)

class CurlCallback:
    def __init__(self):
        self.contents = ''

    def body_callback(self, buf):
        self.contents = self.contents + buf

class AddPPASigningKeyThread(Thread):
    " thread class for adding the signing key in the background "

    def __init__(self, ppa_path):
        Thread.__init__(self)
        self.ppa_path = ppa_path
        
    def run(self):
        self.add_ppa_signing_key(self.ppa_path)
        
    def add_ppa_signing_key(self, ppa_path):
        """Query and add the corresponding PPA signing key.
        
        The signing key fingerprint is obtained from the Launchpad PPA page,
        via a secure channel, so it can be trusted.
        """
        def cleanup(tmpdir):
            import shutil
            shutil.rmtree(tmp_keyring_dir)
        def fail_with_msg(msg, tmpdir=None):
            print msg
            if tmpdir:
                cleanup(tmpdir)
            return False
        owner_name, ppa_name, distro = ppa_path[1:].split('/')
        lp_url = ('https://launchpad.net/api/1.0/~%s/+archive/%s' % (
            owner_name, ppa_name))
        try:
            # we ask for a JSON structure from lp_page, we could use
            # simplejson, but the format is simple enough for the regexp
            callback = CurlCallback()
            curl = pycurl.Curl()
            curl.setopt(pycurl.SSL_VERIFYPEER, 1)
            curl.setopt(pycurl.SSL_VERIFYHOST, 2)
            curl.setopt(pycurl.WRITEFUNCTION, callback.body_callback)
            curl.setopt(pycurl.URL, str(lp_url))
            curl.setopt(pycurl.HTTPHEADER, ["Accept: application/json"])
            curl.perform()
            curl.close()
            lp_page = callback.contents
            #print lp_page
            signing_key_fingerprint = re.findall(
                '\"signing_key_fingerprint\": \"(\w*)\"', lp_page)
            if not signing_key_fingerprint:
              raise IOError()
        except pycurl.error, e:
            print "Error reading %s: %s" % (lp_url, e[1])
            return False
        except IOError, e:
            print "Error: can't find signing_key_fingerprint at %s" % lp_url
            return False
        signing_key_fingerprint = signing_key_fingerprint[0]
        # double check that the signing key is a v4 fingerprint (160bit)
        if len(signing_key_fingerprint) < 160/8:
            return fail_with_msg(
                "Error: signing key fingerprint '%s' too short" % 
                signing_key_fingerprint)
        # create a temp keyring dir
        tmp_keyring_dir = tempfile.mkdtemp()
        tmp_secret_keyring = os.path.join(tmp_keyring_dir, "secring.gpg")
        tmp_keyring = os.path.join(tmp_keyring_dir, "pubring.gpg")
        # default options for gpg
        gpg_default_options = [
            "gpg", 
            "--no-default-keyring", "--no-options",
            "--homedir", tmp_keyring_dir,
            ]
        # download the key to a temp keyring first
        res = subprocess.call(gpg_default_options + [
            "--secret-keyring", tmp_secret_keyring,
            "--keyring", tmp_keyring,
            "--keyserver", "keyserver.ubuntu.com",
            "--recv", signing_key_fingerprint,
            ])
        if res != 0:
            return fail_with_msg("recv failed", tmp_keyring_dir)
        # now export again using the long key id (to ensure that there is
        # really only this one key in our keyring) and not someone MITM us
        tmp_export_keyring = os.path.join(tmp_keyring_dir, "export-keyring.gpg")
        res = subprocess.call(gpg_default_options + [
            "--keyring", tmp_keyring,
            "--output", tmp_export_keyring,
            "--export", signing_key_fingerprint,
            ])
        if res != 0:
            return fail_with_msg("export failed", tmp_keyring_dir)
        # now verify the fingerprint, this is probably redundant as we
        # exported by the fingerprint in the previous command but its
        # still good paranoia
        output = subprocess.Popen(
            gpg_default_options + [
                "--keyring", tmp_export_keyring,
                "--fingerprint",
                "--batch",
                "--with-colons",
                ], 
            stdout=subprocess.PIPE, 
            universal_newlines=True).communicate()[0]
        got_fingerprint=None
        for line in output.splitlines():
            if line.startswith("fpr:"):
                got_fingerprint = line.split(":")[9]
                # stop after the first to ensure no subkey trickery
                break
        if got_fingerprint != signing_key_fingerprint:
            return fail_with_msg(
                "Fingerprints do not match, not importing: '%s' != '%s'" % (
                    signing_key_fingerprint, got_fingerprint))
        res = subprocess.call(["apt-key", "add", tmp_keyring])
        # cleanup
        cleanup(tmp_keyring_dir)
        return (res == 0)

