From 2a5d58bce0bad442ffc0833a182f7a01f67fdf7a Mon Sep 17 00:00:00 2001
From: Remi Gacogne <rgacogne-github@coredump.fr>
Date: Wed, 4 Nov 2015 17:36:35 +0100
Subject: [PATCH] Add a separate GnuPG database for database signing

---
 lib/libalpm/alpm.h       | 11 +++++++++++
 lib/libalpm/be_package.c |  2 +-
 lib/libalpm/be_sync.c    |  2 +-
 lib/libalpm/handle.c     | 24 ++++++++++++++++++++++++
 lib/libalpm/handle.h     |  1 +
 lib/libalpm/signing.c    | 35 +++++++++++++++++++++++++++--------
 lib/libalpm/signing.h    |  4 ++--
 src/pacman/Makefile.am   |  2 ++
 src/pacman/conf.c        | 16 ++++++++++++++++
 src/pacman/conf.h        |  4 +++-
 src/pacman/pacman.c      | 33 ++++++++++++++++++++-------------
 src/util/Makefile.am     |  2 ++
 src/util/testpkg.c       |  1 +
 13 files changed, 111 insertions(+), 26 deletions(-)

diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h
index 3049f2f..f87e22d 100644
--- a/lib/libalpm/alpm.h
+++ b/lib/libalpm/alpm.h
@@ -224,6 +224,12 @@ typedef enum _alpm_sigvalidity_t {
 	ALPM_SIGVALIDITY_UNKNOWN
 } alpm_sigvalidity_t;
 
+/** PGP signature types */
+typedef enum _alpm_sigtype_t {
+	ALPM_SIGTYPE_DB,
+	ALPM_SIGTYPE_PKG,
+} alpm_sigtype_t;
+
 /*
  * Structures
  */
@@ -795,6 +801,11 @@ const char *alpm_option_get_gpgdir(alpm_handle_t *handle);
 /** Sets the path to libalpm's GnuPG home directory. */
 int alpm_option_set_gpgdir(alpm_handle_t *handle, const char *gpgdir);
 
+/** Returns the path to libalpm's GnuPG home directory for database. */
+const char *alpm_option_get_gpgdbdir(alpm_handle_t *handle);
+/** Sets the path to libalpm's GnuPG home directory for database. */
+int alpm_option_set_gpgdbdir(alpm_handle_t *handle, const char *gpgdbdir);
+
 /** Returns whether to use syslog (0 is FALSE, TRUE otherwise). */
 int alpm_option_get_usesyslog(alpm_handle_t *handle);
 /** Sets whether to use syslog (0 is FALSE, TRUE otherwise). */
diff --git a/lib/libalpm/be_package.c b/lib/libalpm/be_package.c
index 53399a3..e3fa92c 100644
--- a/lib/libalpm/be_package.c
+++ b/lib/libalpm/be_package.c
@@ -339,7 +339,7 @@ int _alpm_pkg_validate_internal(alpm_handle_t *handle,
 		}
 		if(_alpm_check_pgp_helper(handle, pkgfile, sig,
 					level & ALPM_SIG_PACKAGE_OPTIONAL, level & ALPM_SIG_PACKAGE_MARGINAL_OK,
-					level & ALPM_SIG_PACKAGE_UNKNOWN_OK, sigdata)) {
+					level & ALPM_SIG_PACKAGE_UNKNOWN_OK, ALPM_SIGTYPE_PKG, sigdata)) {
 			handle->pm_errno = ALPM_ERR_PKG_INVALID_SIG;
 			return -1;
 		}
diff --git a/lib/libalpm/be_sync.c b/lib/libalpm/be_sync.c
index b09b060..d8bf809 100644
--- a/lib/libalpm/be_sync.c
+++ b/lib/libalpm/be_sync.c
@@ -113,7 +113,7 @@ static int sync_db_validate(alpm_db_t *db)
 			alpm_siglist_t *siglist;
 			ret = _alpm_check_pgp_helper(db->handle, dbpath, NULL,
 					level & ALPM_SIG_DATABASE_OPTIONAL, level & ALPM_SIG_DATABASE_MARGINAL_OK,
-					level & ALPM_SIG_DATABASE_UNKNOWN_OK, &siglist);
+					level & ALPM_SIG_DATABASE_UNKNOWN_OK, ALPM_SIGTYPE_DB, &siglist);
 			if(ret) {
 				retry = _alpm_process_siglist(db->handle, db->treename, siglist,
 						level & ALPM_SIG_DATABASE_OPTIONAL, level & ALPM_SIG_DATABASE_MARGINAL_OK,
diff --git a/lib/libalpm/handle.c b/lib/libalpm/handle.c
index 98420b0..4e52bee 100644
--- a/lib/libalpm/handle.c
+++ b/lib/libalpm/handle.c
@@ -88,6 +88,7 @@ void _alpm_handle_free(alpm_handle_t *handle)
 	FREE(handle->lockfile);
 	FREE(handle->arch);
 	FREE(handle->gpgdir);
+	FREE(handle->gpgdbdir);
 	FREELIST(handle->noupgrade);
 	FREELIST(handle->noextract);
 	FREELIST(handle->ignorepkg);
@@ -238,6 +239,12 @@ const char SYMEXPORT *alpm_option_get_gpgdir(alpm_handle_t *handle)
 	return handle->gpgdir;
 }
 
+const char SYMEXPORT *alpm_option_get_gpgdbdir(alpm_handle_t *handle)
+{
+	CHECK_HANDLE(handle, return NULL);
+	return handle->gpgdbdir;
+}
+
 int SYMEXPORT alpm_option_get_usesyslog(alpm_handle_t *handle)
 {
 	CHECK_HANDLE(handle, return -1);
@@ -542,6 +549,23 @@ int SYMEXPORT alpm_option_set_gpgdir(alpm_handle_t *handle, const char *gpgdir)
 	return 0;
 }
 
+int SYMEXPORT alpm_option_set_gpgdbdir(alpm_handle_t *handle, const char *gpgdbdir)
+{
+	CHECK_HANDLE(handle, return -1);
+	if(!gpgdbdir) {
+		handle->pm_errno = ALPM_ERR_WRONG_ARGS;
+		return -1;
+	}
+
+	if(handle->gpgdbdir) {
+		FREE(handle->gpgdbdir);
+	}
+	STRDUP(handle->gpgdbdir, gpgdbdir, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
+
+	_alpm_log(handle, ALPM_LOG_DEBUG, "option 'gpgdbdir' = %s\n", handle->gpgdbdir);
+	return 0;
+}
+
 int SYMEXPORT alpm_option_set_usesyslog(alpm_handle_t *handle, int usesyslog)
 {
 	CHECK_HANDLE(handle, return -1);
diff --git a/lib/libalpm/handle.h b/lib/libalpm/handle.h
index e252fbf..9c8a4ab 100644
--- a/lib/libalpm/handle.h
+++ b/lib/libalpm/handle.h
@@ -81,6 +81,7 @@ struct __alpm_handle_t {
 	char *logfile;           /* Name of the log file */
 	char *lockfile;          /* Name of the lock file */
 	char *gpgdir;            /* Directory where GnuPG files are stored */
+	char *gpgdbdir;          /* Directory where GnuPG files for verifying the DB are stored */
 	alpm_list_t *cachedirs;  /* Paths to pacman cache directories */
 	alpm_list_t *hookdirs;   /* Paths to hook directories */
 
diff --git a/lib/libalpm/signing.c b/lib/libalpm/signing.c
index b1c1ee9..49eac7d 100644
--- a/lib/libalpm/signing.c
+++ b/lib/libalpm/signing.c
@@ -152,7 +152,7 @@ static alpm_list_t *list_sigsum(gpgme_sigsum_t sigsum)
 static int init_gpgme(alpm_handle_t *handle)
 {
 	static int init = 0;
-	const char *version, *sigdir;
+	const char *version, *sigdir, *dbsigdir;
 	gpgme_error_t gpg_err;
 	gpgme_engine_info_t enginfo;
 
@@ -162,6 +162,7 @@ static int init_gpgme(alpm_handle_t *handle)
 	}
 
 	sigdir = handle->gpgdir;
+	dbsigdir = handle->gpgdbdir;
 
 	if(_alpm_access(handle, sigdir, "pubring.gpg", R_OK)
 			|| _alpm_access(handle, sigdir, "trustdb.gpg", R_OK)) {
@@ -172,6 +173,15 @@ static int init_gpgme(alpm_handle_t *handle)
 				"pacman-key --init");
 	}
 
+	if(_alpm_access(handle, dbsigdir, "pubring.gpg", R_OK)
+			|| _alpm_access(handle, dbsigdir, "trustdb.gpg", R_OK)) {
+		handle->pm_errno = ALPM_ERR_NOT_A_FILE;
+		_alpm_log(handle, ALPM_LOG_DEBUG, "Database signature verification will fail!\n");
+		_alpm_log(handle, ALPM_LOG_WARNING,
+				_("Database public keyring not found; have you run '%s'?\n"),
+				"pacman-key --init");
+	}
+
 	/* calling gpgme_check_version() returns the current version and runs
 	 * some internal library setup code */
 	version = gpgme_check_version(NULL);
@@ -485,11 +495,13 @@ int _alpm_key_import(alpm_handle_t *handle, const char *fpr)
  * @param handle the context handle
  * @param path the full path to a file
  * @param base64_sig optional PGP signature data in base64 encoding
+ * @param sigtype the signature type, package or database
  * @param siglist a pointer to storage for signature results
  * @return 0 in normal cases, -1 if the something failed in the check process
  */
 int _alpm_gpgme_checksig(alpm_handle_t *handle, const char *path,
-		const char *base64_sig, alpm_siglist_t *siglist)
+			const char *base64_sig, const alpm_sigtype_t sigtype,
+			alpm_siglist_t *siglist)
 {
 	int ret = -1, sigcount;
 	gpgme_error_t gpg_err = 0;
@@ -542,6 +554,11 @@ int _alpm_gpgme_checksig(alpm_handle_t *handle, const char *path,
 	gpg_err = gpgme_new(&ctx);
 	CHECK_ERR();
 
+	if(sigtype == ALPM_SIGTYPE_DB) {
+		gpg_err = gpgme_ctx_set_engine_info(ctx, GPGME_PROTOCOL_OpenPGP, NULL, handle->gpgdbdir);
+		CHECK_ERR();
+	}
+
 	/* create our necessary data objects to verify the signature */
 	gpg_err = gpgme_data_new_from_stream(&filedata, file);
 	CHECK_ERR();
@@ -716,7 +733,8 @@ int _alpm_key_import(alpm_handle_t UNUSED *handle, const char UNUSED *fpr)
 }
 
 int _alpm_gpgme_checksig(alpm_handle_t UNUSED *handle, const char UNUSED *path,
-		const char UNUSED *base64_sig, alpm_siglist_t UNUSED *siglist)
+			const char UNUSED *base64_sig, const alpm_sigtype_t UNUSED sigtype,
+			alpm_siglist_t UNUSED *siglist)
 {
 	return -1;
 }
@@ -754,12 +772,13 @@ char *_alpm_sigpath(alpm_handle_t *handle, const char *path)
  * @param optional whether signatures are optional (e.g., missing OK)
  * @param marginal whether signatures with marginal trust are acceptable
  * @param unknown whether signatures with unknown trust are acceptable
+ * @param sigtype the signature type, package (ALPM_SIGTYPE_PKG) or database (ALPM_SIGTYPE_DB)
  * @param sigdata a pointer to storage for signature results
  * @return 0 on success, -1 on error (consult pm_errno or sigdata)
  */
 int _alpm_check_pgp_helper(alpm_handle_t *handle, const char *path,
-		const char *base64_sig, int optional, int marginal, int unknown,
-		alpm_siglist_t **sigdata)
+			const char *base64_sig, int optional, int marginal, int unknown,
+			const alpm_sigtype_t sigtype, alpm_siglist_t **sigdata)
 {
 	alpm_siglist_t *siglist;
 	int ret;
@@ -767,7 +786,7 @@ int _alpm_check_pgp_helper(alpm_handle_t *handle, const char *path,
 	CALLOC(siglist, 1, sizeof(alpm_siglist_t),
 			RET_ERR(handle, ALPM_ERR_MEMORY, -1));
 
-	ret = _alpm_gpgme_checksig(handle, path, base64_sig, siglist);
+	ret = _alpm_gpgme_checksig(handle, path, base64_sig, sigtype, siglist);
 	if(ret && handle->pm_errno == ALPM_ERR_SIG_MISSING) {
 		if(optional) {
 			_alpm_log(handle, ALPM_LOG_DEBUG, "missing optional signature\n");
@@ -934,7 +953,7 @@ int SYMEXPORT alpm_pkg_check_pgp_signature(alpm_pkg_t *pkg,
 	pkg->handle->pm_errno = 0;
 
 	return _alpm_gpgme_checksig(pkg->handle, pkg->filename,
-			pkg->base64_sig, siglist);
+					pkg->base64_sig, ALPM_SIGTYPE_PKG, siglist);
 }
 
 /**
@@ -950,7 +969,7 @@ int SYMEXPORT alpm_db_check_pgp_signature(alpm_db_t *db,
 	ASSERT(siglist != NULL, RET_ERR(db->handle, ALPM_ERR_WRONG_ARGS, -1));
 	db->handle->pm_errno = 0;
 
-	return _alpm_gpgme_checksig(db->handle, _alpm_db_path(db), NULL, siglist);
+	return _alpm_gpgme_checksig(db->handle, _alpm_db_path(db), NULL, ALPM_SIGTYPE_DB, siglist);
 }
 
 /**
diff --git a/lib/libalpm/signing.h b/lib/libalpm/signing.h
index e5137d7..13fa0c9 100644
--- a/lib/libalpm/signing.h
+++ b/lib/libalpm/signing.h
@@ -23,11 +23,11 @@
 
 char *_alpm_sigpath(alpm_handle_t *handle, const char *path);
 int _alpm_gpgme_checksig(alpm_handle_t *handle, const char *path,
-		const char *base64_sig, alpm_siglist_t *result);
+			const char *base64_sig, alpm_sigtype_t sigtype, alpm_siglist_t *result);
 
 int _alpm_check_pgp_helper(alpm_handle_t *handle, const char *path,
 		const char *base64_sig, int optional, int marginal, int unknown,
-		alpm_siglist_t **sigdata);
+		alpm_sigtype_t sigtype, alpm_siglist_t **sigdata);
 int _alpm_process_siglist(alpm_handle_t *handle, const char *identifier,
 		alpm_siglist_t *siglist, int optional, int marginal, int unknown);
 
diff --git a/src/pacman/Makefile.am b/src/pacman/Makefile.am
index b07c670..cf7a888 100644
--- a/src/pacman/Makefile.am
+++ b/src/pacman/Makefile.am
@@ -4,6 +4,7 @@ SUBDIRS = po
 conffile  = ${sysconfdir}/pacman.conf
 dbpath    = ${localstatedir}/lib/pacman/
 gpgdir    = ${sysconfdir}/pacman.d/gnupg/
+gpgdbdir   = ${sysconfdir}/pacman.d/gnupg-db/
 hookdir   = ${sysconfdir}/pacman.d/hooks/
 cachedir  = ${localstatedir}/cache/pacman/pkg/
 logfile   = ${localstatedir}/log/pacman.log
@@ -17,6 +18,7 @@ AM_CPPFLAGS = \
 	-DCONFFILE=\"$(conffile)\" \
 	-DDBPATH=\"$(dbpath)\" \
 	-DGPGDIR=\"$(gpgdir)\" \
+	-DGPGDBDIR=\"$(gpgdbdir)\" \
 	-DHOOKDIR=\"$(hookdir)\" \
 	-DCACHEDIR=\"$(cachedir)\" \
 	-DLOGFILE=\"$(logfile)\"
diff --git a/src/pacman/conf.c b/src/pacman/conf.c
index f02e361..83b8f19 100644
--- a/src/pacman/conf.c
+++ b/src/pacman/conf.c
@@ -148,6 +148,7 @@ int config_free(config_t *oldconfig)
 	free(oldconfig->dbpath);
 	free(oldconfig->logfile);
 	free(oldconfig->gpgdir);
+	free(oldconfig->gpgdbdir);
 	FREELIST(oldconfig->hookdirs);
 	FREELIST(oldconfig->cachedirs);
 	free(oldconfig->xfercommand);
@@ -561,6 +562,11 @@ static int _parse_options(const char *key, char *value,
 				config->gpgdir = strdup(value);
 				pm_printf(ALPM_LOG_DEBUG, "config: gpgdir: %s\n", value);
 			}
+		} else if(strcmp(key, "GPGDBDir") == 0) {
+			if(!config->gpgdbdir) {
+				config->gpgdbdir = strdup(value);
+				pm_printf(ALPM_LOG_DEBUG, "config: gpgdbdir: %s\n", value);
+			}
 		} else if(strcmp(key, "LogFile") == 0) {
 			if(!config->logfile) {
 				config->logfile = strdup(value);
@@ -756,6 +762,16 @@ static int setup_libalpm(void)
 		return ret;
 	}
 
+        /* Set GnuPG's home directory for database. This is not relative to rootdir, even if
+         * rootdir is defined. Reasoning: gpgdir contains configuration data. */
+	config->gpgdbdir = config->gpgdbdir ? config->gpgdbdir : strdup(GPGDBDIR);
+	ret = alpm_option_set_gpgdbdir(handle, config->gpgdbdir);
+	if(ret != 0) {
+		pm_printf(ALPM_LOG_ERROR, _("problem setting gpgdbdir '%s' (%s)\n"),
+				config->gpgdbdir, alpm_strerror(alpm_errno(handle)));
+		return ret;
+	}
+
 	/* Set user hook directory. This is not relative to rootdir, even if
 	 * rootdir is defined. Reasoning: hookdir contains configuration data. */
 	if(config->hookdirs == NULL) {
diff --git a/src/pacman/conf.h b/src/pacman/conf.h
index 7a7e9cf..eee9a1f 100644
--- a/src/pacman/conf.h
+++ b/src/pacman/conf.h
@@ -66,6 +66,7 @@ typedef struct __config_t {
 	char *dbpath;
 	char *logfile;
 	char *gpgdir;
+	char *gpgdbdir;
 	alpm_list_t *hookdirs;
 	alpm_list_t *cachedirs;
 
@@ -201,7 +202,8 @@ enum {
 	OP_VERBOSE,
 	OP_DOWNLOADONLY,
 	OP_REFRESH,
-	OP_ASSUMEINSTALLED
+	OP_ASSUMEINSTALLED,
+	OP_GPGDBDIR
 };
 
 /* clean method */
diff --git a/src/pacman/pacman.c b/src/pacman/pacman.c
index 94685a7..193ca3e 100644
--- a/src/pacman/pacman.c
+++ b/src/pacman/pacman.c
@@ -208,19 +208,20 @@ static void usage(int op, const char * const myname)
 				break;
 		}
 
-		addlist(_("  -b, --dbpath <path>  set an alternate database location\n"));
-		addlist(_("  -r, --root <path>    set an alternate installation root\n"));
-		addlist(_("  -v, --verbose        be verbose\n"));
-		addlist(_("      --arch <arch>    set an alternate architecture\n"));
-		addlist(_("      --cachedir <dir> set an alternate package cache location\n"));
-		addlist(_("      --hookdir <dir>  set an alternate hook location\n"));
-		addlist(_("      --color <when>   colorize the output\n"));
-		addlist(_("      --config <path>  set an alternate configuration file\n"));
-		addlist(_("      --debug          display debug messages\n"));
-		addlist(_("      --gpgdir <path>  set an alternate home directory for GnuPG\n"));
-		addlist(_("      --logfile <path> set an alternate log file\n"));
-		addlist(_("      --noconfirm      do not ask for any confirmation\n"));
-		addlist(_("      --confirm        always ask for confirmation\n"));
+		addlist(_("  -b, --dbpath <path>   set an alternate database location\n"));
+		addlist(_("  -r, --root <path>     set an alternate installation root\n"));
+		addlist(_("  -v, --verbose         be verbose\n"));
+		addlist(_("      --arch <arch>     set an alternate architecture\n"));
+		addlist(_("      --cachedir <dir>  set an alternate package cache location\n"));
+		addlist(_("      --hookdir <dir>   set an alternate hook location\n"));
+		addlist(_("      --color <when>    colorize the output\n"));
+		addlist(_("      --config <path>   set an alternate configuration file\n"));
+		addlist(_("      --debug           display debug messages\n"));
+		addlist(_("      --gpgdir <path>   set an alternate home directory for GnuPG\n"));
+		addlist(_("      --gpgdbdir <path> set an alternate home directory for database GnuPG\n"));
+		addlist(_("      --logfile <path>  set an alternate log file\n"));
+		addlist(_("      --noconfirm       do not ask for any confirmation\n"));
+		addlist(_("      --confirm         always ask for confirmation\n"));
 	}
 	list = alpm_list_msort(list, alpm_list_count(list), options_cmp);
 	for(i = list; i; i = alpm_list_next(i)) {
@@ -467,6 +468,10 @@ static int parsearg_global(int opt)
 			free(config->gpgdir);
 			config->gpgdir = strdup(optarg);
 			break;
+		case OP_GPGDBDIR:
+			free(config->gpgdbdir);
+			config->gpgdbdir = strdup(optarg);
+			break;
 		case OP_HOOKDIR:
 			config->hookdirs = alpm_list_add(config->hookdirs, strdup(optarg));
 			break;
@@ -979,6 +984,7 @@ static int parseargs(int argc, char *argv[])
 		{"arch",       required_argument, 0, OP_ARCH},
 		{"print-format", required_argument, 0, OP_PRINTFORMAT},
 		{"gpgdir",     required_argument, 0, OP_GPGDIR},
+		{"gpgdbdir",     required_argument, 0, OP_GPGDBDIR},
 		{"dbonly",     no_argument,       0, OP_DBONLY},
 		{"color",      required_argument, 0, OP_COLOR},
 		{0, 0, 0, 0}
@@ -1294,6 +1300,7 @@ int main(int argc, char *argv[])
 		printf("Lock File : %s\n", alpm_option_get_lockfile(config->handle));
 		printf("Log File  : %s\n", alpm_option_get_logfile(config->handle));
 		printf("GPG Dir   : %s\n", alpm_option_get_gpgdir(config->handle));
+		printf("GPG DB Dir: %s\n", alpm_option_get_gpgdbdir(config->handle));
 		list_display("Targets   :", pm_targets, 0);
 	}
 
diff --git a/src/util/Makefile.am b/src/util/Makefile.am
index 6a89ea6..257d20e 100644
--- a/src/util/Makefile.am
+++ b/src/util/Makefile.am
@@ -2,6 +2,7 @@
 conffile  = ${sysconfdir}/pacman.conf
 dbpath    = ${localstatedir}/lib/pacman/
 gpgdir    = ${sysconfdir}/pacman.d/gnupg/
+gpgdbdir  = ${sysconfdir}/pacman.d/gnupg-db/
 cachedir  = ${localstatedir}/cache/pacman/pkg/
 
 bin_PROGRAMS = vercmp testpkg cleanupdelta pacsort pactree
@@ -12,6 +13,7 @@ AM_CPPFLAGS = \
 	-DLOCALEDIR=\"@localedir@\" \
 	-DCONFFILE=\"$(conffile)\" \
 	-DDBPATH=\"$(dbpath)\" \
+	-DGPGDBDIR=\"$(gpgdbdir)\" \
 	-DGPGDIR=\"$(gpgdir)\" \
 	-DCACHEDIR=\"$(cachedir)\"
 
diff --git a/src/util/testpkg.c b/src/util/testpkg.c
index bd95a86..2316319 100644
--- a/src/util/testpkg.c
+++ b/src/util/testpkg.c
@@ -63,6 +63,7 @@ int main(int argc, char *argv[])
 
 	/* set gpgdir to default */
 	alpm_option_set_gpgdir(handle, GPGDIR);
+	alpm_option_set_gpgdbdir(handle, GPGDBDIR);
 
 	if(alpm_pkg_load(handle, argv[1], 1, level, &pkg) == -1
 			|| pkg == NULL) {
-- 
2.6.2


