/*
    Copyright (C) 2010  ABRT team
    Copyright (C) 2010  RedHat Inc

    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.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "libabrt.h"
#include "rpm.h"

#ifdef HAVE_LIBRPM
#include <rpm/rpmts.h>
#include <rpm/rpmcli.h>
#include <rpm/rpmdb.h>
#include <rpm/rpmpgp.h>
#endif

/**
* A set, which contains finger prints.
*/

static GList *list_fingerprints = NULL;

/* cuts the name from the NVR format: foo-1.2.3-1.el6
   returns a newly allocated string
*/
char* get_package_name_from_NVR_or_NULL(const char* packageNVR)
{
    char* package_name = NULL;
    if (packageNVR != NULL)
    {
        log_notice("packageNVR %s", packageNVR);
        package_name = xstrdup(packageNVR);
        char *pos = strrchr(package_name, '-');
        if (pos != NULL)
        {
            *pos = '\0';
            pos = strrchr(package_name, '-');
            if (pos != NULL)
            {
                *pos = '\0';
            }
        }
    }
    return package_name;
}

void rpm_init()
{
#ifdef HAVE_LIBRPM
    if (rpmReadConfigFiles(NULL, NULL) != 0)
        error_msg("Can't read RPM rc files");
#endif

    list_free_with_free(list_fingerprints); /* paranoia */
    /* Huh? Why do we start the list with an element with NULL string? */
    list_fingerprints = g_list_alloc();
}

void rpm_destroy()
{
#ifdef HAVE_LIBRPM
    /* Mirroring the order of deinit calls in rpm-4.11.1/lib/poptALL.c::rpmcliFini() */
    rpmFreeCrypto();
    rpmFreeMacros(NULL);
    rpmFreeRpmrc();

/* rpm >= 4.14 handles this automatically on exit */
#if 0
    /* RPM doc says "clean up any open iterators and databases".
     * Observed to eliminate these Berkeley DB warnings:
     * "BDB2053 Freeing read locks for locker 0x1e0: 28718/139661746636736"
     */
    rpmdbCheckTerminate(1);
#endif
#endif

    list_free_with_free(list_fingerprints);
    list_fingerprints = NULL;
}

void rpm_load_gpgkey(const char* filename)
{
#ifdef HAVE_LIBRPM
    uint8_t *pkt = NULL;
    size_t pklen;
    if (pgpReadPkts(filename, &pkt, &pklen) != PGPARMOR_PUBKEY)
    {
        free(pkt);
        error_msg("Can't load public GPG key %s", filename);
        return;
    }

    uint8_t keyID[8];
#if 0
    if (pgpPubkeyFingerprint(pkt, pklen, keyID) == 0)
#else
    if (pgpPubkeyKeyID(pkt, pklen, keyID) == 0)
#endif
    {
        char *fingerprint = pgpHexStr(keyID, sizeof(keyID));
        if (fingerprint != NULL)
            list_fingerprints = g_list_append(list_fingerprints, fingerprint);
    }
    free(pkt);
#else
    return;
#endif
}

int rpm_chk_fingerprint(const char* pkg)
{
    char *fingerprint = rpm_get_fingerprint(pkg);
    int res = 0;
    if (fingerprint)
        res = rpm_fingerprint_is_imported(fingerprint);
    free(fingerprint);
    return res;
}

int rpm_fingerprint_is_imported(const char* fingerprint)
{
    return !!g_list_find_custom(list_fingerprints, fingerprint, (GCompareFunc)g_strcmp0);
}

char *rpm_get_fingerprint(const char *pkg)
{
#ifdef HAVE_LIBRPM
    char *fingerprint = NULL;
    char *pgpsig = NULL;
    const char *errmsg = NULL;

    rpmts ts = rpmtsCreate();
    rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_NAME, pkg, 0);
    Header header = rpmdbNextIterator(iter);

    if (!header)
        goto error;

    pgpsig = headerFormat(header, "%|SIGGPG?{%{SIGGPG:pgpsig}}:{%{SIGPGP:pgpsig}}|", &errmsg);
    if (!pgpsig && errmsg)
    {
        log_notice("cannot get siggpg:pgpsig. reason: %s", errmsg);
        goto error;
    }

    char *pgpsig_tmp = strstr(pgpsig, " Key ID ");
    if (pgpsig_tmp)
        fingerprint = xstrdup(pgpsig_tmp + sizeof(" Key ID ") - 1);

error:
    free(pgpsig);
    rpmdbFreeIterator(iter);
    rpmtsFree(ts);
    return fingerprint;
#else
    return NULL;
#endif
}

/*
  Checking the MD5 sum requires to run prelink to "un-prelink" the
  binaries - this is considered potential security risk so we don't
  use it, until we find some non-intrusive way
*/

/*
 * Not woking, need to be rewriten
 *
bool CheckHash(const char* pPackage, const char* pPath)
{
    bool ret = true;
    rpmts ts = rpmtsCreate();
    rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_NAME, pPackage, 0);
    Header header = rpmdbNextIterator(iter);
    if (header == NULL)
        goto error;

    rpmfi fi = rpmfiNew(ts, header, RPMTAG_BASENAMES, RPMFI_NOHEADER);
    std::string headerHash;
    char computedHash[1024] = "";

    while (rpmfiNext(fi) != -1)
    {
        if (strcmp(pPath, rpmfiFN(fi)) == 0)
        {
            headerHash = rpmfiFDigestHex(fi, NULL);
            rpmDoDigest(rpmfiDigestAlgo(fi), pPath, 1, (unsigned char*) computedHash, NULL);
            ret = (headerHash != "" && headerHash == computedHash);
            break;
        }
    }
    rpmfiFree(fi);
error:
    rpmdbFreeIterator(iter);
    rpmtsFree(ts);
    return ret;
}
*/

#ifdef HAVE_LIBRPM
static int rpm_query_file(rpmts *ts, rpmdbMatchIterator *iter, Header *header,
        const char *filename, const char *rootdir_or_NULL)
{
    const char *queryname = filename;

    *ts = rpmtsCreate();
    if (rootdir_or_NULL)
    {
        if (rpmtsSetRootDir(*ts, rootdir_or_NULL) != 0)
        {
            rpmtsFree(*ts);
            return -1;
        }

        unsigned len = strlen(rootdir_or_NULL);
        /* remove 'chroot' prefix */
        if (strncmp(filename, rootdir_or_NULL, len) == 0 && filename[len] == '/')
            queryname += len;
    }

    *iter = rpmtsInitIterator(*ts, RPMTAG_BASENAMES, queryname, 0);
    *header = rpmdbNextIterator(*iter);

    if (!(*header) && rootdir_or_NULL)
    {
        rpmdbFreeIterator(*iter);
        rpmtsFree(*ts);

        return rpm_query_file(ts, iter, header, filename, NULL);
    }

    return 0;
}
#endif

char* rpm_get_component(const char *filename, const char *rootdir_or_NULL)
{
#ifdef HAVE_LIBRPM
    char *ret = NULL;
    char *srpm = NULL;
    rpmts ts;
    rpmdbMatchIterator iter;
    Header header;

    if (rpm_query_file(&ts, &iter, &header, filename, rootdir_or_NULL) < 0)
        return NULL;

    if (!header)
        goto error;

    const char *errmsg = NULL;
    srpm = headerFormat(header, "%{SOURCERPM}", &errmsg);
    if (!srpm && errmsg)
    {
        error_msg("cannot get srpm. reason: %s", errmsg);
        goto error;
    }

    ret = get_package_name_from_NVR_or_NULL(srpm);
    free(srpm);

 error:
    rpmdbFreeIterator(iter);
    rpmtsFree(ts);
    return ret;
#else
    return NULL;
#endif
}

#ifdef HAVE_LIBRPM
#define pkg_add_id(name)                                                \
    static inline int pkg_add_##name(Header header, struct pkg_envra *p) \
    {                                                                   \
        const char *errmsg = NULL;                                      \
        p->p_##name = headerFormat(header, "%{"#name"}", &errmsg);      \
        if (p->p_##name || !errmsg)                                     \
            return 0;                                                   \
                                                                        \
        error_msg("cannot get "#name": %s", errmsg);                    \
                                                                        \
        return -1;                                                      \
    }                                                                   \

pkg_add_id(epoch);
pkg_add_id(name);
pkg_add_id(version);
pkg_add_id(release);
pkg_add_id(arch);
pkg_add_id(vendor);
#endif

// caller is responsible to free returned value
struct pkg_envra *rpm_get_package_nvr(const char *filename, const char *rootdir_or_NULL)
{
#ifdef HAVE_LIBRPM
    rpmts ts;
    rpmdbMatchIterator iter;
    Header header;

    struct pkg_envra *p = NULL;

    if (rpm_query_file(&ts, &iter, &header, filename, rootdir_or_NULL) < 0)
        return NULL;

    if (!header)
        goto error;

    p = xzalloc(sizeof(*p));
    int r;
    r = pkg_add_epoch(header, p);
    if (r)
        goto error;
   /*
    * <npajkovs> hello, what's the difference between epoch '0' and  '(none)'?
    * <Panu> nothing really, a missing epoch is considered equal to zero epoch
    */
    if (!strncmp(p->p_epoch, "(none)", strlen("(none)")))
    {
        free(p->p_epoch);
        p->p_epoch = xstrdup("0");
    }

    r = pkg_add_name(header, p);
    if (r)
        goto error;

    r = pkg_add_version(header, p);
    if (r)
        goto error;

    r = pkg_add_release(header, p);
    if (r)
        goto error;

    r = pkg_add_arch(header, p);
    if (r)
        goto error;

    r = pkg_add_vendor(header, p);
    if (r)
        goto error;

    if (strcmp(p->p_epoch, "0") == 0)
        p->p_nvr = xasprintf("%s-%s-%s", p->p_name, p->p_version, p->p_release);
    else
        p->p_nvr = xasprintf("%s:%s-%s-%s", p->p_epoch, p->p_name, p->p_version, p->p_release);

    rpmdbFreeIterator(iter);
    rpmtsFree(ts);
    return p;

 error:
    free_pkg_envra(p);

    rpmdbFreeIterator(iter);
    rpmtsFree(ts);
    return NULL;
#else
    return NULL;
#endif
}

void free_pkg_envra(struct pkg_envra *p)
{
    if (!p)
        return;

    free(p->p_vendor);
    free(p->p_epoch);
    free(p->p_name);
    free(p->p_version);
    free(p->p_release);
    free(p->p_arch);
    free(p->p_nvr);
    free(p);
}
