Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package librepo for openSUSE:Factory checked in at 2022-12-30 11:08:13 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/librepo (Old) and /work/SRC/openSUSE:Factory/.librepo.new.1563 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "librepo" Fri Dec 30 11:08:13 2022 rev:18 rq:1045671 version:1.15.1 Changes: -------- --- /work/SRC/openSUSE:Factory/librepo/librepo.changes 2022-10-17 14:57:19.242049025 +0200 +++ /work/SRC/openSUSE:Factory/.librepo.new.1563/librepo.changes 2022-12-30 11:08:15.589004242 +0100 @@ -1,0 +2,9 @@ +Wed Dec 28 16:50:53 UTC 2022 - Andreas Stieger <andreas.stie...@gmx.de> + +- update to 1.15.1: + * Add API support for waiting on network in an event driven + manner + * OpenPGP API extension and fixes +- lincense updated to LGPL-2.1-or-later + +------------------------------------------------------------------- Old: ---- librepo-1.14.5.tar.gz New: ---- librepo-1.15.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ librepo.spec ++++++ --- /var/tmp/diff_new_pack.X57fFf/_old 2022-12-30 11:08:17.485015576 +0100 +++ /var/tmp/diff_new_pack.X57fFf/_new 2022-12-30 11:08:17.513015744 +0100 @@ -37,10 +37,10 @@ %define devname %{name}-devel Name: librepo -Version: 1.14.5 +Version: 1.15.1 Release: 0 Summary: Repodata downloading library -License: LGPL-2.0-or-later +License: LGPL-2.1-or-later Group: Development/Libraries/C and C++ URL: https://github.com/rpm-software-management/librepo ++++++ librepo-1.14.5.tar.gz -> librepo-1.15.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librepo-1.14.5/CMakeLists.txt new/librepo-1.15.1/CMakeLists.txt --- old/librepo-1.14.5/CMakeLists.txt 2022-09-09 12:32:23.000000000 +0200 +++ new/librepo-1.15.1/CMakeLists.txt 2022-12-09 13:47:26.000000000 +0100 @@ -28,7 +28,7 @@ # Find necessare libraries FIND_PACKAGE(PkgConfig) -PKG_CHECK_MODULES(GLIB2 glib-2.0>=2.28 REQUIRED) +PKG_CHECK_MODULES(GLIB2 glib-2.0>=2.28 gio-2.0 REQUIRED) PKG_SEARCH_MODULE(LIBCRYPTO REQUIRED libcrypto openssl) PKG_CHECK_MODULES(LIBXML2 libxml-2.0 REQUIRED) FIND_PACKAGE(CURL 7.52.0 REQUIRED) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librepo-1.14.5/VERSION.cmake new/librepo-1.15.1/VERSION.cmake --- old/librepo-1.14.5/VERSION.cmake 2022-09-09 12:32:23.000000000 +0200 +++ new/librepo-1.15.1/VERSION.cmake 2022-12-09 13:47:26.000000000 +0100 @@ -1,3 +1,3 @@ SET(LIBREPO_MAJOR "1") -SET(LIBREPO_MINOR "14") -SET(LIBREPO_PATCH "5") +SET(LIBREPO_MINOR "15") +SET(LIBREPO_PATCH "1") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librepo-1.14.5/librepo/gpg.c new/librepo-1.15.1/librepo/gpg.c --- old/librepo-1.14.5/librepo/gpg.c 2022-09-09 12:32:23.000000000 +0200 +++ new/librepo-1.15.1/librepo/gpg.c 2022-12-09 13:47:26.000000000 +0100 @@ -1,5 +1,6 @@ /* librepo - A library providing (libcURL like) API to downloading repository * Copyright (C) 2012 Tomas Mlcoch + * Copyright (C) 2022 Jaroslav Rohel <jro...@redhat.com> * * Licensed under the GNU Lesser General Public License Version 2.1 * @@ -19,12 +20,10 @@ */ #include <assert.h> -#include <glib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> -#include <string.h> #include <gpgme.h> #include <unistd.h> @@ -32,6 +31,21 @@ #include "util.h" #include "gpg.h" +struct tLrGpgSubkey { + gboolean has_next; // FALSE if this is the last subkey in the list + char *id; // subkey id + char *fingerprint; // fingerprint of the subkey in hex digit form + long int timestamp; // creation timestamp, -1 if invalid, 0 if not available + gboolean can_sign; // TRUE if subkey can be used for signing +}; + +struct tLrGpgKey { + gboolean has_next; // FALSE if this is the last subkey in the list + char **uids; // NULL terminated array of user IDs strings + LrGpgSubkey *subkeys; // list of subkeys associated with the key. The first subkey is the primary key + char *raw_key; // key in ACII-Armor format +}; + /* * Creates the '/run/user/$UID' directory if it doesn't exist. If this * directory exists, gpgagent will create its sockets under @@ -52,7 +66,9 @@ * [2] https://bugzilla.redhat.com/show_bug.cgi?id=1769831 * [3] https://github.com/rpm-software-management/microdnf/issues/50 */ -void ensure_socket_dir_exists() { +static void +lr_gpg_ensure_socket_dir_exists() +{ char dirname[32]; snprintf(dirname, sizeof(dirname), "/run/user/%u", getuid()); int res = mkdir(dirname, 0700); @@ -61,22 +77,16 @@ } } -gboolean -lr_gpg_check_signature_fd(int signature_fd, - int data_fd, - const char *home_dir, - GError **err) +static gpgme_ctx_t +lr_gpg_context_init(const char *home_dir, GError **err) { - gpgme_error_t gpgerr; - gpgme_ctx_t context; - gpgme_data_t signature_data; - gpgme_data_t data_data; - gpgme_verify_result_t result; - gpgme_signature_t sig; - assert(!err || *err == NULL); - // Initialization + lr_gpg_ensure_socket_dir_exists(); + + gpgme_ctx_t context; + gpgme_error_t gpgerr; + gpgme_check_version(NULL); gpgerr = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); if (gpgerr != GPG_ERR_NO_ERROR) { @@ -85,7 +95,7 @@ g_set_error(err, LR_GPG_ERROR, LRE_GPGNOTSUPPORTED, "gpgme_engine_check_version() error: %s", gpgme_strerror(gpgerr)); - return FALSE; + return NULL; } gpgerr = gpgme_new(&context); @@ -93,7 +103,7 @@ g_debug("%s: gpgme_new: %s", __func__, gpgme_strerror(gpgerr)); g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, "gpgme_new() error: %s", gpgme_strerror(gpgerr)); - return FALSE; + return NULL; } gpgerr = gpgme_set_protocol(context, GPGME_PROTOCOL_OpenPGP); @@ -102,25 +112,44 @@ g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, "gpgme_set_protocol() error: %s", gpgme_strerror(gpgerr)); gpgme_release(context); - return FALSE; + return NULL; } if (home_dir) { gpgerr = gpgme_ctx_set_engine_info(context, GPGME_PROTOCOL_OpenPGP, NULL, home_dir); if (gpgerr != GPG_ERR_NO_ERROR) { - g_debug("%s: gpgme_ctx_set_engine_info: %s", __func__, - gpgme_strerror(gpgerr)); + g_debug("%s: gpgme_ctx_set_engine_info: %s", __func__, gpgme_strerror(gpgerr)); g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, "gpgme_ctx_set_engine_info() error: %s", gpgme_strerror(gpgerr)); gpgme_release(context); - return FALSE; + return NULL; } } gpgme_set_armor(context, 1); + return context; +} + +gboolean +lr_gpg_check_signature_fd(int signature_fd, + int data_fd, + const char *home_dir, + GError **err) +{ + gpgme_error_t gpgerr; + gpgme_data_t signature_data; + gpgme_data_t data_data; + gpgme_verify_result_t result; + gpgme_signature_t sig; + + gpgme_ctx_t context = lr_gpg_context_init(home_dir, err); + if (!context) { + return FALSE; + } + gpgerr = gpgme_data_new_from_fd(&signature_data, signature_fd); if (gpgerr != GPG_ERR_NO_ERROR) { g_debug("%s: gpgme_data_new_from_fd: %s", @@ -235,73 +264,54 @@ } gboolean -lr_gpg_import_key(const char *key_fn, const char *home_dir, GError **err) +lr_gpg_import_key_from_memory(const char *key, size_t key_len, const char *home_dir, GError **err) { + gpgme_ctx_t context = lr_gpg_context_init(home_dir, err); + if (!context) { + return FALSE; + } + gpgme_error_t gpgerr; - int key_fd; - gpgme_ctx_t context; gpgme_data_t key_data; - assert(!err || *err == NULL); - - ensure_socket_dir_exists(); - - // Initialization - gpgme_check_version(NULL); - gpgerr = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); + gpgerr = gpgme_data_new_from_mem(&key_data, key, key_len, 0); if (gpgerr != GPG_ERR_NO_ERROR) { - g_debug("%s: gpgme_engine_check_version: %s", + g_debug("%s: gpgme_data_new_from_mem: %s", __func__, gpgme_strerror(gpgerr)); - g_set_error(err, LR_GPG_ERROR, LRE_GPGNOTSUPPORTED, - "gpgme_engine_check_version() error: %s", - gpgme_strerror(gpgerr)); - return FALSE; - } - - gpgerr = gpgme_new(&context); - if (gpgerr != GPG_ERR_NO_ERROR) { - g_debug("%s: gpgme_new: %s", __func__, gpgme_strerror(gpgerr)); g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, - "gpgme_new() error: %s", gpgme_strerror(gpgerr)); + "gpgme_data_new_from_mem(_, _, %ld, 0) error: %s", + (unsigned long)key_len, gpgme_strerror(gpgerr)); + gpgme_release(context); return FALSE; } - gpgerr = gpgme_set_protocol(context, GPGME_PROTOCOL_OpenPGP); + gpgerr = gpgme_op_import(context, key_data); if (gpgerr != GPG_ERR_NO_ERROR) { - g_debug("%s: gpgme_set_protocol: %s", __func__, gpgme_strerror(gpgerr)); + g_debug("%s: gpgme_op_import: %s", __func__, gpgme_strerror(gpgerr)); g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, - "gpgme_set_protocol() error: %s", gpgme_strerror(gpgerr)); + "gpgme_op_import() error: %s", gpgme_strerror(gpgerr)); + gpgme_data_release(key_data); gpgme_release(context); return FALSE; } - if (home_dir) { - gpgerr = gpgme_ctx_set_engine_info(context, GPGME_PROTOCOL_OpenPGP, - NULL, home_dir); - if (gpgerr != GPG_ERR_NO_ERROR) { - g_debug("%s: gpgme_ctx_set_engine_info: %s", __func__, gpgme_strerror(gpgerr)); - g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, - "gpgme_ctx_set_engine_info() error: %s", - gpgme_strerror(gpgerr)); - gpgme_release(context); - return FALSE; - } - } - - gpgme_set_armor(context, 1); + gpgme_data_release(key_data); + gpgme_release(context); - // Key import + return TRUE; +} - key_fd = open(key_fn, O_RDONLY); - if (key_fd == -1) { - g_debug("%s: Opening key: %s", __func__, g_strerror(errno)); - g_set_error(err, LR_GPG_ERROR, LRE_IO, - "Error while opening key %s: %s", - key_fn, g_strerror(errno)); - gpgme_release(context); +gboolean +lr_gpg_import_key_from_fd(int key_fd, const char *home_dir, GError **err) +{ + gpgme_ctx_t context = lr_gpg_context_init(home_dir, err); + if (!context) { return FALSE; } + gpgme_error_t gpgerr; + gpgme_data_t key_data; + gpgerr = gpgme_data_new_from_fd(&key_data, key_fd); if (gpgerr != GPG_ERR_NO_ERROR) { g_debug("%s: gpgme_data_new_from_fd: %s", @@ -310,24 +320,267 @@ "gpgme_data_new_from_fd(_, %d) error: %s", key_fd, gpgme_strerror(gpgerr)); gpgme_release(context); - close(key_fd); return FALSE; } gpgerr = gpgme_op_import(context, key_data); - gpgme_data_release(key_data); if (gpgerr != GPG_ERR_NO_ERROR) { g_debug("%s: gpgme_op_import: %s", __func__, gpgme_strerror(gpgerr)); g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, "gpgme_op_import() error: %s", gpgme_strerror(gpgerr)); + gpgme_data_release(key_data); gpgme_release(context); - close(key_fd); return FALSE; } + gpgme_data_release(key_data); + gpgme_release(context); + + return TRUE; +} + +gboolean +lr_gpg_import_key(const char *key_fn, const char *home_dir, GError **err) +{ + assert(!err || *err == NULL); + + int key_fd = open(key_fn, O_RDONLY); + if (key_fd == -1) { + g_debug("%s: Opening key: %s", __func__, g_strerror(errno)); + g_set_error(err, LR_GPG_ERROR, LRE_IO, + "Error while opening key %s: %s", + key_fn, g_strerror(errno)); + return FALSE; + } + + gboolean ret = lr_gpg_import_key_from_fd(key_fd, home_dir, err); + close(key_fd); + return ret; +} + +LrGpgKey * +lr_gpg_list_keys(gboolean export_keys, const char *home_dir, GError **err) +{ + gpgme_error_t gpgerr; + + gpgme_ctx_t context = lr_gpg_context_init(home_dir, err); + if (!context) { + return NULL; + } + + GArray * keys = g_array_new(FALSE, FALSE, sizeof(LrGpgKey)); + + gpgerr = gpgme_op_keylist_start(context, NULL, 0); + while (gpg_err_code(gpgerr) == GPG_ERR_NO_ERROR) { + gpgme_key_t key; + gpgerr = gpgme_op_keylist_next(context, &key); + if (gpgerr) { + break; + } + + GArray * subkeys = g_array_new(FALSE, FALSE, sizeof(LrGpgSubkey)); + gpgme_subkey_t subkey = key->subkeys; + while (subkey) { + LrGpgSubkey lr_subkey; + lr_subkey.has_next = FALSE; + lr_subkey.id = g_strdup(subkey->keyid); + lr_subkey.fingerprint = g_strdup(subkey->fpr); + lr_subkey.timestamp = subkey->timestamp; + lr_subkey.can_sign = subkey->can_sign; + g_array_append_val(subkeys, lr_subkey); + subkey = subkey->next; + } + // All subkeys in the list except the last one are followed by another subkey + if (subkeys->len > 0) { + for (guint i = 0; i < subkeys->len - 1; ++i) { + g_array_index(subkeys, LrGpgSubkey, i).has_next = TRUE; + } + } + + LrGpgKey lr_key; + lr_key.has_next = FALSE; + + GPtrArray * uid_strings = g_ptr_array_new(); + for (gpgme_user_id_t uids = key->uids; uids; uids = uids->next) { + if (!uids->uid) { + continue; + } + g_ptr_array_add(uid_strings, g_strdup(uids->uid)); + } + + gpgme_key_release(key); + + g_ptr_array_add(uid_strings, NULL); // add terminating NULL + lr_key.uids = (char **)g_ptr_array_free(uid_strings, FALSE); + + lr_key.subkeys = (LrGpgSubkey *)(subkeys->len > 0 ? g_array_free(subkeys, FALSE) : g_array_free(subkeys, TRUE)); + lr_key.raw_key = NULL; + g_array_append_val(keys, lr_key); + } + // All keys in the list except the last one are followed by another key + for (guint i = 0; i < keys->len - 1; ++i) { + g_array_index(keys, LrGpgKey, i).has_next = TRUE; + } + + if (gpg_err_code(gpgerr) != GPG_ERR_EOF) { + g_debug("%s: gpgme_op_keylist_: %s", + __func__, gpgme_strerror(gpgerr)); + g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, + "gpgme_op_keylist_ error: %s", + gpgme_strerror(gpgerr)); + lr_gpg_keys_free((LrGpgKey *)g_array_free(keys, FALSE)); + gpgme_release(context); + return NULL; + } + + gpgme_op_keylist_end(context); + + LrGpgKey *lr_keys = (LrGpgKey *)(keys->len > 0 ? g_array_free(keys, FALSE) : g_array_free(keys, TRUE)); + + if (export_keys) { + for (LrGpgKey *lr_key = lr_keys; lr_key; ++lr_key) { + LrGpgSubkey *lr_subkey = lr_key->subkeys; + if (!lr_subkey) { + g_info("%s: Missing data to export key. Damaged key? Skipping the key", __func__); + if (!lr_key->has_next) { + break; + } + continue; + } + + gpgme_data_t key_data; + gpgerr = gpgme_data_new(&key_data); + if (gpgerr != GPG_ERR_NO_ERROR) { + g_debug("%s: gpgme_data_new: %s", __func__, gpgme_strerror(gpgerr)); + g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, + "gpgme_data_new() error: %s", gpgme_strerror(gpgerr)); + lr_gpg_keys_free(lr_keys); + gpgme_release(context); + return NULL; + } + + gpgerr = gpgme_op_export(context, lr_subkey->fingerprint, 0, key_data); + if (gpgerr != GPG_ERR_NO_ERROR) { + g_debug("%s: gpgme_op_export: %s", __func__, gpgme_strerror(gpgerr)); + g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, + "gpgme_op_export() error: %s", gpgme_strerror(gpgerr)); + gpgme_data_release(key_data); + lr_gpg_keys_free(lr_keys); + gpgme_release(context); + return NULL; + } + + off_t key_size = gpgme_data_seek(key_data, 0, SEEK_CUR); + gpgerr = gpgme_data_rewind(key_data); + if (gpgerr != GPG_ERR_NO_ERROR) { + g_debug("%s: gpgme_data_rewind: %s", __func__, gpgme_strerror(gpgerr)); + g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, + "gpgme_data_rewind() error: %s", gpgme_strerror(gpgerr)); + gpgme_data_release(key_data); + lr_gpg_keys_free(lr_keys); + gpgme_release(context); + return NULL; + } + + lr_key->raw_key = g_malloc0(key_size + 1); + ssize_t readed = gpgme_data_read(key_data, lr_key->raw_key, key_size); + if (readed == -1) { + g_debug("%s: gpgme_data_read: %s", __func__, gpgme_strerror(gpgerr)); + g_set_error(err, LR_GPG_ERROR, LRE_GPGERROR, + "gpgme_data_read() error: %s", gpgme_strerror(gpgerr)); + gpgme_data_release(key_data); + lr_gpg_keys_free(lr_keys); + gpgme_release(context); + return NULL; + } + if (readed != key_size) { + g_warning("%s: Error exporting key \"%s\": gpgme_data_read: Key size is %ld but readed %ld. " + "Skipping the key", + __func__, lr_key->subkeys->fingerprint, (long)key_size, (long)readed); + g_free(lr_key->raw_key); + lr_key->raw_key = NULL; + } + + gpgme_data_release(key_data); + + if (!lr_key->has_next) { + break; + } + } + } + gpgme_release(context); + return lr_keys; +} - return TRUE; +const LrGpgKey * +lr_gpg_key_get_next(const LrGpgKey *key) { + return key->has_next ? ++key : NULL; +} + +char * const * +lr_gpg_key_get_userids(const LrGpgKey *key) { + return key->uids; +} + +const char * +lr_gpg_key_get_raw_key(const LrGpgKey *key) { + return key->raw_key; +} + +const LrGpgSubkey * +lr_gpg_key_get_subkeys(const LrGpgKey *key) { + return key->subkeys; +} + +static void +lr_gpg_subkeys_free(LrGpgSubkey *subkeys) { + for (LrGpgSubkey *item = subkeys; item; ++item) { + g_free(item->fingerprint); + g_free(item->id); + if (!item->has_next) { + break; + } + } + g_free(subkeys); +} + +void +lr_gpg_keys_free(LrGpgKey *keys) { + for (LrGpgKey *item = keys; item; ++item) { + g_free(item->raw_key); + lr_gpg_subkeys_free(item->subkeys); + g_strfreev(item->uids); + if (!item->has_next) { + break; + } + } + g_free(keys); +} + +const LrGpgSubkey * +lr_gpg_subkey_get_next(const LrGpgSubkey *subkey) { + return subkey->has_next ? ++subkey : NULL; +} + +const char * +lr_gpg_subkey_get_id(const LrGpgSubkey *subkey) { + return subkey->id; +} + +const char * +lr_gpg_subkey_get_fingerprint(const LrGpgSubkey *subkey) { + return subkey->fingerprint; +} + +long int +lr_gpg_subkey_get_timestamp(const LrGpgSubkey *subkey) { + return subkey->timestamp; +} + +gboolean +lr_gpg_subkey_get_can_sign(const LrGpgSubkey *subkey) { + return subkey->can_sign; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librepo-1.14.5/librepo/gpg.h new/librepo-1.15.1/librepo/gpg.h --- old/librepo-1.14.5/librepo/gpg.h 2022-09-09 12:32:23.000000000 +0200 +++ new/librepo-1.15.1/librepo/gpg.h 2022-12-09 13:47:26.000000000 +0100 @@ -1,5 +1,6 @@ /* librepo - A library providing (libcURL like) API to downloading repository * Copyright (C) 2012 Tomas Mlcoch + * Copyright (C) 2022 Jaroslav Rohel <jro...@redhat.com> * * Licensed under the GNU Lesser General Public License Version 2.1 * @@ -30,6 +31,14 @@ * @{ */ +/** A structure containing information about subkey. + */ +typedef struct tLrGpgSubkey LrGpgSubkey; + +/** A structure containing information about a key with subkeys. + */ +typedef struct tLrGpgKey LrGpgKey; + /** Check detached signature of data. * @param signature_fd File descriptor of signature file. * @param data_fd File descriptor of data to verify. @@ -60,6 +69,35 @@ const char *home_dir, GError **err); + +/** Import key into the keyring. + * @param key Pointer to memory buffer with key. + * @param key_len Length of the key. + * @param home_dir Configuration directory of OpenPGP engine + * (e.g. "/home/user/.gnupg/"), if NULL default + * config directory is used. + * @param err GError ** + * @return returns TRUE if error is not set and FALSE if it is. + */ +gboolean +lr_gpg_import_key_from_memory(const char *key, + size_t key_len, + const char *home_dir, + GError **err); + +/** Import key into the keyring. + * @param key_fd Filedescriptor of key file. + * @param home_dir Configuration directory of OpenPGP engine + * (e.g. "/home/user/.gnupg/"), if NULL default + * config directory is used. + * @param err GError ** + * @return returns TRUE if error is not set and FALSE if it is. + */ +gboolean +lr_gpg_import_key_from_fd(int key_fd, + const char *home_dir, + GError **err); + /** Import key into the keyring. * @param key_fn Filename (path) of key file. * @param home_dir Configuration directory of OpenPGP engine @@ -73,6 +111,90 @@ const char *home_dir, GError **err); + +/** List/export keys (and subkeys) from the keyring. + * @param export_keys If TRUE, the list also contains the exported keys. + * Export is in ASCII-Armor format. + * @param home_dir Configuration directory of OpenPGP engine + * (e.g. "/home/user/.gnupg/"), if NULL default + * config directory is used. + * @param err GError ** + * @return returns list of keys (and subkeys), or NULL if keyring is empty or an error occured. + */ +LrGpgKey * +lr_gpg_list_keys(gboolean export_keys, + const char *home_dir, + GError **err); + +/** Get the next key from the list obtained from lr_gpg_list_keys. + * @param key Input key. + * @return returns next kye. + */ +const LrGpgKey * +lr_gpg_key_get_next(const LrGpgKey *key); + +/** Get NULL terminated array of user IDs strings. + * @param key Input key. + * @return returns NULL terminated array of user IDs strings. + */ +char * const * +lr_gpg_key_get_userids(const LrGpgKey *key); + +/** Get key in ASCII-Armor format (only if `key` was obtained from lr_gpg_list_keys with `export = TRUE`). + * @param key Input key. + * @return returns key in ACII-Armor format. + */ +const char * +lr_gpg_key_get_raw_key(const LrGpgKey *key); + +/** Get a list of subkeys associated with the key. The first subkey is the primary key. + * @param key Input key. + * @return returns list of subkeys associated with the key. + */ +const LrGpgSubkey * +lr_gpg_key_get_subkeys(const LrGpgKey *key); + +/** Release the list of keys obtained from lr_gpg_list_keys. + * @param key Input array of keys. + */ +void +lr_gpg_keys_free(LrGpgKey *key_array); + +/** Get the next subkey from the list obtained from lr_gpg_key_get_subkeys. + * @param key Input subkey. + * @return returns next subkye. + */ +const LrGpgSubkey * +lr_gpg_subkey_get_next(const LrGpgSubkey *subkey); + +/** Get subkey ID. + * @param key Input subkey. + * @return returns key ID. + */ +const char * +lr_gpg_subkey_get_id(const LrGpgSubkey *subkey); + +/** Get fingerprint of the subkey in hex digit form. + * @param key Input subkey. + * @return returns fingerprint of the subkey in hex digit form. + */ +const char * +lr_gpg_subkey_get_fingerprint(const LrGpgSubkey *subkey); + +/** Get the creation timestamp. + * @param key Input subkey. + * @return returns Get the creation timestamp, -1 if invalid, 0 if not available.. + */ +long int +lr_gpg_subkey_get_timestamp(const LrGpgSubkey *subkey); + +/** Get information if the subkey can be used for signing. + * @param key Input subkey. + * @return returns TRUE if subkey can be used for signing. + */ +gboolean +lr_gpg_subkey_get_can_sign(const LrGpgSubkey *subkey); + /** @} */ G_END_DECLS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librepo-1.14.5/librepo/handle.c new/librepo-1.15.1/librepo/handle.c --- old/librepo-1.14.5/librepo/handle.c 2022-09-09 12:32:23.000000000 +0200 +++ new/librepo-1.15.1/librepo/handle.c 2022-12-09 13:47:26.000000000 +0100 @@ -32,6 +32,8 @@ #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> +#include <time.h> +#include <gio/gio.h> #include "handle_internal.h" @@ -264,6 +266,51 @@ } } +struct callback_data { + GMainLoop *loop; + guint64 deadline_millis; + guint timeout_id; + GNetworkMonitor *monitor; + GSocketConnectable *connectable; + GCancellable *cancellable; +}; + +gboolean +timeout_callback(gpointer data) +{ + struct callback_data *dt = (struct callback_data*)data; + + //if past deadline, exit + if (g_get_monotonic_time() >= dt->deadline_millis) { + g_main_loop_quit(dt->loop); + return G_SOURCE_REMOVE; + } + + //if no internet, remove source + if (!g_network_monitor_get_network_available(dt->monitor)) { + return G_SOURCE_REMOVE; + } + + //if url reached, quit loop and remove source + if (g_network_monitor_can_reach (dt->monitor, dt->connectable, dt->cancellable, NULL)) { + g_main_loop_quit(dt->loop); + return G_SOURCE_REMOVE; + } + + //Still waiting on the URL to be availbale, keep polling + return G_SOURCE_CONTINUE; +} + +void +on_network_available(GObject *object, GParamSpec *pspec, gpointer data) +{ + struct callback_data *dt = (struct callback_data*)data; + if (dt->timeout_id == 0 && g_network_monitor_get_network_available(dt->monitor)) { + dt->timeout_id = g_timeout_add(200, timeout_callback, dt); + } + g_main_loop_run(dt->loop); +} + gboolean lr_handle_setopt(LrHandle *handle, GError **err, @@ -841,6 +888,60 @@ return ret; } +gboolean +lr_handle_network_wait(LrHandle *handle, GError **err, guint seconds, GCancellable *cancellable) +{ + assert(!err || *err == NULL); + + if (!handle) { + g_set_error(err, LR_HANDLE_ERROR, LRE_BADFUNCARG, + "No handle specified"); + return FALSE; + } + + GNetworkMonitor *monitor = g_network_monitor_get_default(); + + struct callback_data data_struct; + data_struct.cancellable = cancellable; + data_struct.monitor = monitor; + + const gchar *baseurl; + if (handle->metalinkurl) + baseurl = handle->metalinkurl; + else if (handle->mirrorlisturl) + baseurl = handle->mirrorlisturl; + else if (handle->urls) + baseurl = handle->urls[0]; + assert(baseurl); + + g_autoptr(GUri) uri = g_uri_parse(baseurl, G_URI_FLAGS_NONE, NULL); + if (uri == NULL) { + return FALSE; + } + const gchar* scheme = g_uri_get_scheme(uri); + if (!g_strcmp0(scheme, "file")) { + return TRUE; + } + const gchar* host = g_uri_get_host(uri); + guint16 port = g_uri_get_port(uri); + GSocketConnectable *connectable = g_network_address_new(host, port); + data_struct.connectable = connectable; + data_struct.deadline_millis = g_get_monotonic_time() + seconds * G_USEC_PER_SEC; + g_autoptr(GMainLoop) loop; + loop = g_main_loop_new(NULL, FALSE); + data_struct.loop = loop; + data_struct.timeout_id = 0; + + if (g_network_monitor_get_network_available(data_struct.monitor)) { + data_struct.timeout_id = g_timeout_add(200, timeout_callback, &data_struct); + g_main_loop_run(data_struct.loop); + } + else{ + g_signal_connect(monitor, "notify::network-available", G_CALLBACK(on_network_available), &data_struct); + } + return TRUE; +} + static gboolean lr_handle_prepare_urls(LrHandle *handle, GError **err) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librepo-1.14.5/librepo/handle.h new/librepo-1.15.1/librepo/handle.h --- old/librepo-1.14.5/librepo/handle.h 2022-09-09 12:32:23.000000000 +0200 +++ new/librepo-1.15.1/librepo/handle.h 2022-12-09 13:47:26.000000000 +0100 @@ -22,6 +22,7 @@ #define __LR_HANDLE_H__ #include <glib.h> +#include <gio/gio.h> #include "result.h" @@ -560,6 +561,16 @@ gboolean lr_handle_perform(LrHandle *handle, LrResult *result, GError **err); +/** Handle waiting on network for LRO_URLS. + * @param handle Librepo handle. + * @param seconds Network timeout seconds + * @param err GError ** + * @param cancellable GCancellable * + * @return TRUE if everything is ok, FALSE if err is set. + */ +gboolean +lr_handle_network_wait(LrHandle *handle, GError **err, guint seconds, GCancellable *cancellable); + /** @} */ G_END_DECLS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librepo-1.14.5/librepo.spec new/librepo-1.15.1/librepo.spec --- old/librepo-1.14.5/librepo.spec 2022-09-09 12:32:23.000000000 +0200 +++ new/librepo-1.15.1/librepo.spec 2022-12-09 13:47:26.000000000 +0100 @@ -11,11 +11,11 @@ %global dnf_conflict 2.8.8 Name: librepo -Version: 1.14.5 +Version: 1.15.1 Release: 1%{?dist} Summary: Repodata downloading library -License: LGPLv2+ +License: LGPL-2.1-or-later URL: https://github.com/rpm-software-management/librepo Source0: %{url}/archive/%{version}/%{name}-%{version}.tar.gz diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/librepo-1.14.5/tests/test_gpg.c new/librepo-1.15.1/tests/test_gpg.c --- old/librepo-1.14.5/tests/test_gpg.c 2022-09-09 12:32:23.000000000 +0200 +++ new/librepo-1.15.1/tests/test_gpg.c 2022-12-09 13:47:26.000000000 +0100 @@ -38,6 +38,7 @@ _signature_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml_bad.asc", NULL); + // Import the first key directly from the file ret = lr_gpg_import_key(key_path, tmp_home_path, &tmp_err); ck_assert(ret); ck_assert_ptr_null(tmp_err); @@ -57,8 +58,7 @@ &tmp_err); ck_assert(!ret); ck_assert_ptr_nonnull(tmp_err); - g_error_free(tmp_err); - tmp_err = NULL; + g_clear_error(&tmp_err); // Bad data ret = lr_gpg_check_signature(signature_path, @@ -67,13 +67,19 @@ &tmp_err); ck_assert(!ret); ck_assert_ptr_nonnull(tmp_err); - g_error_free(tmp_err); - tmp_err = NULL; + g_clear_error(&tmp_err); + + // Load the second key into memory and import it from memory + gchar *contents; + gsize length; + ret = g_file_get_contents(_key_path, &contents, &length, &tmp_err); + ck_assert(ret); + ck_assert_ptr_null(tmp_err); - // Import the 2nd key - ret = lr_gpg_import_key(_key_path, tmp_home_path, &tmp_err); + ret = lr_gpg_import_key_from_memory(contents, length, tmp_home_path, &tmp_err); ck_assert(ret); ck_assert_ptr_null(tmp_err); + g_free(contents); // Valid key and data ret = lr_gpg_check_signature(_signature_path, @@ -90,7 +96,7 @@ &tmp_err); ck_assert(!ret); ck_assert_ptr_nonnull(tmp_err); - g_error_free(tmp_err); + g_clear_error(&tmp_err); tmp_err = NULL; // Bad data 2 @@ -100,7 +106,7 @@ &tmp_err); ck_assert(!ret); ck_assert_ptr_nonnull(tmp_err); - g_error_free(tmp_err); + g_clear_error(&tmp_err); tmp_err = NULL; lr_remove_dir(tmp_home_path); @@ -114,12 +120,117 @@ } END_TEST +START_TEST(test_gpg_check_key_export) +{ + gboolean ret; + char *key_path; + char *tmp_home_path; + GError *tmp_err = NULL; + + tmp_home_path = lr_gettmpdir(); + key_path = lr_pathconcat(test_globals.testdata_dir, + "repo_yum_01/repodata/repomd.xml.key", NULL); + + // Import the key from file descriptor + int key_fd = open(key_path, O_RDONLY); + ck_assert(key_fd != -1); + ret = lr_gpg_import_key_from_fd(key_fd, tmp_home_path, &tmp_err); + ck_assert(ret); + ck_assert_ptr_null(tmp_err); + ck_assert(close(key_fd) != -1); + + // Export the keys + LrGpgKey *keys = lr_gpg_list_keys(TRUE, tmp_home_path, &tmp_err); + ck_assert_ptr_nonnull(keys); + ck_assert_ptr_null(tmp_err); + + // Test key user ids + char * const *uids = lr_gpg_key_get_userids(keys); + ck_assert_ptr_nonnull(uids); + ck_assert(g_strcmp0(uids[0], "Tomas Mlcoch (test key) <tmlc...@redhat.com>") == 0); + ck_assert_ptr_null(uids[1]); + + // Get subkeys + const LrGpgSubkey *subkeys = lr_gpg_key_get_subkeys(keys); + ck_assert_ptr_nonnull(subkeys); + + // Test first subkey + const char *id = lr_gpg_subkey_get_id(subkeys); + ck_assert(g_strcmp0(id, "46AF958A22F2C4E9") == 0); + const char *fingerprint = lr_gpg_subkey_get_fingerprint(subkeys); + ck_assert(g_strcmp0(fingerprint, "55B80C4944D8938E94980B6D46AF958A22F2C4E9") == 0); + long timestamp = lr_gpg_subkey_get_timestamp(subkeys); + ck_assert(timestamp == 1347882156); + gboolean can_sign = lr_gpg_subkey_get_can_sign(subkeys); + ck_assert(can_sign); + + // Get second subkey + subkeys = lr_gpg_subkey_get_next(subkeys); + ck_assert_ptr_nonnull(subkeys); + + // Test second subkey + id = lr_gpg_subkey_get_id(subkeys); + ck_assert(g_strcmp0(id, "7AE6F6EF026AF38A") == 0); + fingerprint = lr_gpg_subkey_get_fingerprint(subkeys); + ck_assert(g_strcmp0(fingerprint, "D855DBAA43DB343EC4F214EB7AE6F6EF026AF38A") == 0); + timestamp = lr_gpg_subkey_get_timestamp(subkeys); + ck_assert(timestamp == 1347882156); + can_sign = lr_gpg_subkey_get_can_sign(subkeys); + ck_assert(!can_sign); + + // There are no other subkeys for the key + subkeys = lr_gpg_subkey_get_next(subkeys); + ck_assert_ptr_null(subkeys); + + // Test exported raw key + const char *raw_key = lr_gpg_key_get_raw_key(keys); + ck_assert_ptr_nonnull(raw_key); + ck_assert(strstr(raw_key, + "mQENBFBXDKwBCADA5jpCwpb/JKOG8mcFyIanNojDwpHwKoyjGNpZNPNUDJguvkRa\n" + "IO3NdoyXYd5QVTOsnyKBaRaiLLJWI/VJxTOT3fwOPprrzUlkHwoWl+sYuSdXHASu\n" + "m4lkBiXHsa5oiXPdrY6hoh5vsF8ASwCHXOwpR9yyvGEaUUMBl2GpJAX/cGVcL4Dy\n" + "Z0pyJMLO4qrIPoX+wd1ZSFSc8JcAC4UtA82HCGTmesgialpwKdoQyt+em94oIM1f\n" + "D6v7zzcRX/zLKKEzpFnU458WBA+JACkde3ohX//0fDCeaLqMzs++FCgwm/HMCszw\n" + "RnINr+K8ENfMYBoeM7a7tnhiae+rkxWmvWz/ABEBAAG0LFRvbWFzIE1sY29jaCAo\n" + "dGVzdCBrZXkpIDx0bWxjb2NoQHJlZGhhdC5jb20+iQE4BBMBAgAiBQJQVwysAhsD\n" + "BgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBGr5WKIvLE6fdyB/9OzDczaqGy\n" + "1rzk7Wp2C9S5QatFUFNWt6FIFPITbixT4jrDo/LyUJVWLw4ng7ldg79vmrzhpP8h\n" + "yFVvuvGvSEMn5sgnZ83SEd4vRJ2O8K5RuVs5Kcj7ayLlxPpqbYOYmrmTaLwYTwdv\n" + "0wDnNU9IkkMSK752RQes4J+4XGikd8CNm5lw8cRQ7bcQd8s2rnCoiyGt7PIdl13z\n" + "8hO9KA52iUP06AbbIusbQ1jzsViEny+xQH7SZ53Ga4eRr0mW2iA20Mkp4Ieb+dNo\n" + "47Q8aHUqI9O4HTF/3Fzt7KmNxXCpCOhxTWx0IkqPGoZ0W63Aut/CVh0LXsBF2TUD\n" + "Ym9P/IjRgJLhuQENBFBXDKwBCACvhlMcgjLJ4PtMTtauF1OXVTfODQSHo+qwKt4S\n" + "GyDlTGayQ76pOqYkkzIRqmNYl1ThmcfzpmJ3O+hFQQ7OdguYcfkbfgIMjEJEbKG3\n" + "wsR5pm9zjwStzYHedwkct1nyROgBz70o16FfdiWOguw58jQZOSO/I2S3JpLsLgI8\n" + "KqdIk/0WuoOfzt+KcvL52lX94O2hBpRI0v6lDgSm7KkPGQrVFnSIUR5r73ceageL\n" + "5LmGm1TlEjWHwA9iYIvBcjnE26/l8u58IYQ/sUmn0u4jBcBNc7iqdWvlSLZLlMmi\n" + "qnzDNhUup9neKGxgr4hGAblxiSxXlmOoFv0jEW81b4VximSJABEBAAGJAR8EGAEC\n" + "AAkFAlBXDKwCGwwACgkQRq+ViiLyxOnf6Qf+PIG//12qp3hXZsvB7JQuQ4nUNwp6\n" + "Ufm6W9pFm3DOqnI9H9ZNzGbkoS5WwRp0B1NLfNKipQVORnDs6qve298ReRrmLKnk\n" + "BPZqxFpPqLQ6X83Or2bqKiJS1axonIgqkImFLfxxqKoukvhn328Z2FVlrvkKSMU8\n" + "eHi/iDF/TCHoPE9WtnVSzsNU9i+9j8j//GO+bMC5AGNOxcBKlChFpLYpE/pfITL/\n" + "icS7wB9MrMLNvjlN1EKszQFxJrFVBGTt8hUqRH3CCUFRwbpE1QJ1WAzJ0Vzk5nWR\n" + "rVZQiiLe03B8hC7/qRiB4bya5nbWcwe9ltPFja4/tTe92ivScFfCLyALVQ==\n" + "=G300") != NULL); + + // Only one key is in keyring + ck_assert_ptr_null(lr_gpg_key_get_next(keys)); + + lr_gpg_keys_free(keys); + lr_remove_dir(tmp_home_path); + lr_free(key_path); + g_free(tmp_home_path); +} +END_TEST + + Suite * gpg_suite(void) { Suite *s = suite_create("gpg"); TCase *tc = tcase_create("Main"); tcase_add_test(tc, test_gpg_check_signature); + tcase_add_test(tc, test_gpg_check_key_export); suite_add_tcase(s, tc); return s; }