/*
 * zkey - Generate, re-encipher, and validate secure keys
 *
 * Copyright IBM Corp. 2021
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <dlfcn.h>

#include <openssl/crypto.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509.h>

#include "lib/zt_common.h"
#include "lib/util_base.h"
#include "lib/util_panic.h"
#include "lib/util_libc.h"
#include "lib/util_path.h"
#include "lib/util_rec.h"

#include "plugin-utils.h"
#include "properties.h"
#include "utils.h"

#ifndef OPENSSL_VERSION_PREREQ
	#if defined(OPENSSL_VERSION_MAJOR) && defined(OPENSSL_VERSION_MINOR)
		#define OPENSSL_VERSION_PREREQ(maj, min)		\
			((OPENSSL_VERSION_MAJOR << 16) +		\
			OPENSSL_VERSION_MINOR >= ((maj) << 16) + (min))
	#else
		#define OPENSSL_VERSION_PREREQ(maj, min)		\
			(OPENSSL_VERSION_NUMBER >= (((maj) << 28) |	\
			((min) << 20)))
	#endif
#endif

/**
 * Initializes the plugin and setup the plugin data structure
 *
 * @param pd                address of the plugin data structure
 * @param plugin_name       name of the plugin (used for printing messages)
 * @param config_path       name of a directory where the KMS plugin can store
 *                          its configuration and other files it needs to store
 * @param config_file       name of the config file of the plugin
 * @param verbose           if true, the plugin should write verbose or debug
 *                          messages to stderr during further processing.
 *
 *  @returns 0 on success, a negative errno in case of an error.
 */
int plugin_init(struct plugin_data *pd, const char *plugin_name,
		const char *config_path, const char *config_file,
		bool verbose)
{
	struct stat sb;
	int rc;

	util_assert(pd != NULL, "Internal error: pd is NULL");
	util_assert(plugin_name != NULL, "Internal error: plugin_name is NULL");
	util_assert(config_path != NULL, "Internal error: config_path is NULL");
	util_assert(config_file != NULL, "Internal error: config_file is NULL");

	memset(pd, 0, sizeof(struct plugin_data));

	pd->plugin_name = util_strdup(plugin_name);
	pd->config_path = util_strdup(config_path);
	pd->config_file = util_strdup(config_file);
	pd->verbose = verbose;

	pr_verbose(pd, "Plugin initializing, config_path: '%s'", config_path);

	if (lstat(config_path, &sb) != 0) {
		rc = -errno;
		warnx("Can not access '%s': %s", config_path, strerror(-rc));
		goto error;
	}
	if (!S_ISDIR(sb.st_mode)) {
		warnx("'%s' is not a directory", config_path);
		rc = -EIO;
		goto error;
	}
	if (!util_path_is_readable(config_path) ||
	    !util_path_is_writable(config_path)) {
		warnx("Permission denied for '%s'", config_path);
		rc = -EACCES;
		goto error;
	}
	if (sb.st_mode & S_IWOTH) {
		warnx("Directory '%s' is writable for others, this is not "
		      "accepted", config_path);
		rc = -EIO;
		goto error;
	}

	pd->config_path_owner = sb.st_gid;
	pd->config_path_mode = sb.st_mode & (S_IRUSR | S_IWUSR |
					     S_IRGRP  | S_IWGRP |
					     S_IROTH);

	pd->properties = properties_new();
	rc = plugin_load_config(pd);
	if (rc != 0 && rc != -EIO) {
		warnx("Failed to load plugin config file: %s", strerror(-rc));
		goto error;
	}

	return 0;

error:
	plugin_term(pd);
	return rc;
}

/**
 * Terminate the plugin and cleanup the plugin data structure
 *
 * @param pd                address of the plugin data structure
 */
void plugin_term(struct plugin_data *pd)
{
	util_assert(pd != NULL, "Internal error: pd is NULL");

	pr_verbose(pd, "Plugin terminated");

	if (pd->plugin_name != NULL)
		free((void *)pd->plugin_name);
	if (pd->config_path != NULL)
		free((void *)pd->config_path);
	if (pd->config_file != NULL)
		free((void *)pd->config_file);
	if (pd->properties != NULL)
		properties_free(pd->properties);
}

/**
 * Clears the error message in the plugin data
 *
 * @param pd                the plugin data
 */
void plugin_clear_error(struct plugin_data *pd)
{
	memset(pd->error_msg, 0, sizeof(pd->error_msg));
}

/**
 * Sets the error message in the plugin data
 *
 * @param pd                the plugin data
 * @param fmt               the format string for sprintf
 */
void plugin_set_error(struct plugin_data *pd, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(pd->error_msg, sizeof(pd->error_msg), fmt, ap);
	va_end(ap);
}

/**
 * Load the plugin config file
 *
 * @param pd                the plugin data
 * @param config_file       the name of the config file
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
int plugin_load_config(struct plugin_data *pd)
{
	char *file_name = NULL;
	int rc;

	util_asprintf(&file_name, "%s/%s", pd->config_path, pd->config_file);

	rc = properties_load(pd->properties, file_name, true);
	if (rc != 0)
		pr_verbose(pd, "Failed to load plugin config file '%s': %s",
			   file_name, strerror(-rc));
	else
		pr_verbose(pd, "Config file '%s' loaded", file_name);

	free(file_name);
	return rc;
}

/**
 * Save the plugin config file
 *
 * @param pd                the plugin data
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
int plugin_save_config(struct plugin_data *pd)
{
	char *file_name = NULL;
	int rc;

	util_asprintf(&file_name, "%s/%s", pd->config_path, pd->config_file);

	pr_verbose(pd, "Saving '%s'", file_name);

	rc = properties_save(pd->properties, file_name, true);
	if (rc != 0) {
		plugin_set_error(pd, "Failed to save plugin config file '%s': "
				 "%s", file_name, strerror(-rc));
		goto out;
	}

	rc = plugin_set_file_permission(pd, file_name);
	if (rc != 0)
		goto out;

out:
	free(file_name);
	return rc;
}


/**
 * Sets the file permissions of the file to the permissions and the group
 * of configuration directory
 *
 * @param pd                 the plugin data
 * @param filename           the name of the file to set permissions for
 *
 * @returns 0 on success, or a negative errno value on failure
 */
int plugin_set_file_permission(struct plugin_data *pd, const char *filename)
{
	int fd, rc = 0;

	fd = open(filename, O_RDONLY | O_NOFOLLOW);
	if (fd < 0) {
		rc = -errno;
		plugin_set_error(pd, "Failed to open '%s': %s", filename,
				 strerror(-rc));
		return rc;
	}

	if (fchmod(fd, pd->config_path_mode) != 0) {
		rc = -errno;
		plugin_set_error(pd, "chmod failed on file '%s': %s", filename,
				 strerror(-rc));
		goto out;
	}

	if (fchown(fd, geteuid(), pd->config_path_owner) != 0) {
		rc = -errno;
		plugin_set_error(pd, "chown failed on file '%s': %s", filename,
				 strerror(-rc));
		goto out;
	}

out:
	close(fd);
	return rc;
}

/**
 * Checks if a plugin config property is set and not empty
 *
 * @param pd                the plugin data
 * @param name              the name of the property
 *
 * @returns true if the property is set and is not empty, false otherwise
 */
bool plugin_check_property(struct plugin_data *pd, const char *name)
{
	bool ok = true;
	char *value;

	value = properties_get(pd->properties, name);
	pr_verbose(pd, "Property '%s': %s", name,
		   value != NULL ? value : "(missing)");

	ok &= (value != NULL && strlen(value) > 0);

	if (value != NULL)
		free(value);

	return ok;
}

/**
 * Sets or removes a property. If value is NULL it is removed, otherwise it
 * is set.
 *
 * @param pd                the plugin data
 * @param name              the name of the property
 * @param value             the value of the property or NULL
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
int plugin_set_or_remove_property(struct plugin_data *pd, const char *name,
				  const char *value)
{
	int rc = 0;

	if (value != NULL) {
		rc = properties_set(pd->properties, name, value);
		if (rc != 0) {
			plugin_set_error(pd, "Failed to set property '%s': %s",
					 name, strerror(-rc));
			goto out;
		}
	} else {
		rc = properties_remove(pd->properties, name);
		if (rc != 0 && rc != -ENOENT) {
			plugin_set_error(pd, "Failed to remove property '%s': "
					 "%s", name, strerror(-rc));
			goto out;
		}
		rc = 0;
	}

out:
	return rc;
}


/**
 * Makes a temporary file an active file, by first removing the current active
 * file (if existent), and then renaming the temporary file to the active file.
 * The active file permissions are also set to the permissions and the group of
 * configuration directory.
 *
 * @param pd                the plugin data
 * @param temp_file         the name of the temporary file
 * @param active_file       the name of the active file
 *
 * @returns 0 on success, or a negative errno value on failure
 */
int plugin_activate_temp_file(struct plugin_data *pd, const char *temp_file,
			      const char *active_file)
{
	int rc;

	if (util_path_exists(active_file)) {
		rc = remove(active_file);
		if (rc != 0) {
			rc = -errno;
			plugin_set_error(pd, "remove failed on file '%s': %s",
					 active_file, strerror(-rc));
			return rc;
		}
	}

	rc = rename(temp_file, active_file);
	if (rc != 0) {
		rc = -errno;
		plugin_set_error(pd, "rename failed on file '%s': %s",
				 temp_file, strerror(-rc));
		return rc;
	}

	return plugin_set_file_permission(pd, active_file);
}

/**
 * Check if the certificate is a self signed certificate, and if it is expired
 * or not yet valid.
 *
 * @param pd                the plugin data
 * @param cert_file         the file name of the PEM file containing the cert
 * @param self_signed       on return: true if the cetr is a self signed cert
 * @param valid             on return: false if the cert is expired or not yet
 *                          valid
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
int plugin_check_certificate(struct plugin_data *pd, const char *cert_file,
			     bool *self_signed, bool *valid)
{
	X509 *cert;
	FILE *fp;
	int rc;

	fp = fopen(cert_file, "r");
	if (fp == NULL) {
		rc = -errno;
		pr_verbose(pd, "Failed to open certificate PEM file '%s': "
			   "%s", cert_file, strerror(-rc));
		return rc;
	}

	cert = PEM_read_X509(fp, NULL, NULL, NULL);
	fclose(fp);

	if (cert == NULL) {
		pr_verbose(pd, "Failed to read certificate PEM file '%s'",
			   cert_file);
		return -EIO;
	}

	*self_signed = (X509_NAME_cmp(X509_get_subject_name(cert),
				      X509_get_issuer_name(cert)) == 0);

#if OPENSSL_VERSION_PREREQ(4, 0)
	*valid = X509_check_certificate_times(NULL, cert, NULL);
#else
	*valid = (X509_cmp_current_time(X509_get0_notBefore(cert)) < 0 &&
		  X509_cmp_current_time(X509_get0_notAfter(cert)) > 0);
#endif

	X509_free(cert);

	return 0;
}

/**
 * Print the certificate(s) contained in the specified PEM file.
 *
 * @param pd                the plugin data
 * @param cert_pem          the file name of the PEM file to print
 *
 * @returns -EIO if the file could not be opened. -ENOENT if the PEM file
 *          does not contain any certificates. 0 if success.
 */
int plugin_print_certificates(struct plugin_data *pd, const char *cert_pem)
{
	int rc = -ENOENT;
	X509 *cert;
	FILE *fp;

	if (cert_pem == NULL)
		return -EINVAL;

	fp = fopen(cert_pem, "r");
	if (fp == NULL) {
		pr_verbose(pd, "File '%s': %s", cert_pem, strerror(errno));
		return -EIO;
	}

	while (1) {
		cert = PEM_read_X509(fp, NULL, NULL, NULL);
		if (cert == NULL)
			break;

		X509_print_ex_fp(stdout, cert, 0, X509_FLAG_NO_EXTENSIONS);

		X509_free(cert);
		rc = 0;
	}

	fclose(fp);
	return rc;
}

/**
 * Queries the APKA master key states and verification patterns of the current
 * CCA adapter
 *
 * @param pd                the plugin data
 * @param cca               the CCA library structure
 * @param apka_mk_info      the master key info of the APKA master key

 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int get_cca_apka_mk_info(struct plugin_data *pd, struct cca_lib *cca,
				struct mk_info *apka_mk_info)
{
	long exit_data_len = 0, rule_array_count, verb_data_length = 0;
	unsigned char rule_array[16 * 8] = { 0, };
	unsigned char exit_data[4] = { 0, };
	long return_code, reason_code;
	struct cca_staticsb {
		u16	sym_old_mk_mdc4_len;
		u16	sym_old_mk_mdc4_id;
		u8	sym_old_mk_mdc4_hp[16];
		u16	sym_cur_mk_mdc4_len;
		u16	sym_cur_mk_mdc4_id;
		u8	sym_cur_mk_mdc4_hp[16];
		u16	sym_new_mk_mdc4_len;
		u16	sym_new_mk_mdc4_id;
		u8	sym_new_mk_mdc4_hp[16];
		u16	asym_old_mk_mdc4_len;
		u16	asym_old_mk_mdc4_id;
		u8	asym_old_mk_mdc4_hp[16];
		u16	asym_cur_mk_mdc4_len;
		u16	asym_cur_mk_mdc4_id;
		u8	asym_cur_mk_mdc4_hp[16];
		u16	asym_new_mk_mdc4_len;
		u16	asym_new_mk_mdc4_id;
		u8	asym_new_mk_mdc4_hp[16];
		u16	sym_old_mk_vp_len;
		u16	sym_old_mk_vp_id;
		u8	sym_old_mk_vp[8];
		u16	sym_cur_mk_vp_len;
		u16	sym_cur_mk_vp_id;
		u8	sym_cur_mk_vp[8];
		u16	sym_new_mk_vp_len;
		u16	sym_new_mk_vp_id;
		u8	sym_new_mk_vp[8];
		u16	sym_new_mk_mkap_len;
		u16	sym_new_mk_mkap_id;
		u8	sym_new_mk_mkap[8];
		u16	aes_old_mk_vp_len;
		u16	aes_old_mk_vp_id;
		u8	aes_old_mk_vp[8];
		u16	aes_cur_mk_vp_len;
		u16	aes_cur_mk_vp_id;
		u8	aes_cur_mk_vp[8];
		u16	aes_new_mk_vp_len;
		u16	aes_new_mk_vp_id;
		u8	aes_new_mk_vp[8];
		u16	apka_old_mk_vp_len;
		u16	apka_old_mk_vp_id;
		u8	apka_old_mk_vp[8];
		u16	apka_cur_mk_vp_len;
		u16	apka_cur_mk_vp_id;
		u8	apka_cur_mk_vp[8];
		u16	apka_new_mk_vp_len;
		u16	apka_new_mk_vp_id;
		u8	apka_new_mk_vp[8];
	} statis_csb = { 0 };

	util_assert(cca != NULL, "Internal error: cca is NULL");
	util_assert(apka_mk_info != NULL,
		    "Internal error: apka_mk_info is NULL");

	memset(apka_mk_info, 0, sizeof(struct mk_info));

	memset(rule_array, 0, sizeof(rule_array));
	memcpy(rule_array, "STATICSB", 8);
	rule_array_count = 1;

	verb_data_length = sizeof(statis_csb);

	cca->dll_CSUACFQ(&return_code, &reason_code,
			 &exit_data_len, exit_data,
			 &rule_array_count, rule_array,
			 &verb_data_length, (unsigned char *)&statis_csb);

	pr_verbose(pd, "CSUACFQ (Cryptographic Facility Query) returned: "
		   "return_code: %ld, reason_code: %ld", return_code,
		   reason_code);
	if (return_code != 0)
		return -EIO;

	switch (rule_array[10 * 8]) {
	case '3':
		apka_mk_info->new_mk.mk_state = MK_STATE_FULL;
		break;
	case '2':
		apka_mk_info->new_mk.mk_state = MK_STATE_PARTIAL;
		break;
	case '1':
	default:
		apka_mk_info->new_mk.mk_state = MK_STATE_EMPTY;
		break;
	}
	memcpy(apka_mk_info->new_mk.mkvp, statis_csb.apka_new_mk_vp,
	       sizeof(statis_csb.apka_new_mk_vp));

	switch (rule_array[11 * 8]) {
	case '2':
		apka_mk_info->cur_mk.mk_state = MK_STATE_VALID;
		break;
	case '1':
	default:
		apka_mk_info->cur_mk.mk_state = MK_STATE_INVALID;
		break;
	}
	memcpy(apka_mk_info->cur_mk.mkvp, statis_csb.apka_cur_mk_vp,
	       sizeof(statis_csb.apka_cur_mk_vp));

	switch (rule_array[12 * 8]) {
	case '2':
		apka_mk_info->old_mk.mk_state = MK_STATE_VALID;
		break;
	case '1':
	default:
		apka_mk_info->old_mk.mk_state = MK_STATE_INVALID;
		break;
	}
	memcpy(apka_mk_info->old_mk.mkvp, statis_csb.apka_old_mk_vp,
	       sizeof(statis_csb.apka_old_mk_vp));

	return 0;
}


/**
 * Print the APKA master key infos of the selected APQNs
 *
 * @param pd                the plugin data
 * @param cca               CCA library structure
 * @param apqns             a list of APQNs
 * @param num_apqns         number of APQNs in above array
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int print_cca_apka_mks(struct plugin_data *pd, struct cca_lib *cca,
			      const struct kms_apqn *apqns, size_t num_apqns)
{
	struct mk_info mk_info;
	struct util_rec *rec;
	enum card_type type;
	int rc = 0, level;
	size_t i;

	rec = util_rec_new_wide("-");
	util_rec_def(rec, "APQN", UTIL_REC_ALIGN_LEFT, 11, "CARD.DOMAIN");
	util_rec_def(rec, "NEW", UTIL_REC_ALIGN_LEFT, 16, "NEW APKA MK");
	util_rec_def(rec, "CUR", UTIL_REC_ALIGN_LEFT, 16, "CURRENT APKA MK");
	util_rec_def(rec, "OLD", UTIL_REC_ALIGN_LEFT, 16, "OLD APKA MK");
	util_rec_def(rec, "TYPE", UTIL_REC_ALIGN_LEFT, 6, "TYPE");
	util_rec_print_hdr(rec);

	for (i = 0; i < num_apqns; i++) {
		rc = select_cca_adapter(cca, apqns[i].card, apqns[i].domain,
					pd->verbose);
		if (rc != 0) {
			pr_verbose(pd, "Failed to select APQN %02x.%04x: "
				   "%s", apqns[i].card, apqns[i].domain,
				   strerror(-rc));
			goto out;
		}

		rc = get_cca_apka_mk_info(pd, cca, &mk_info);
		if (rc != 0) {
			pr_verbose(pd, "Failed to get the APKA master key "
				   "infos for APQN %02x.%04x: %s",
				   apqns[i].card, apqns[i].domain,
				   strerror(-rc));
			goto out;
		}

		level = sysfs_get_card_level(apqns[i].card);
		type = sysfs_get_card_type(apqns[i].card);

		util_rec_set(rec, "APQN", "%02x.%04x", apqns[i].card,
			     apqns[i].domain);

		if (mk_info.new_mk.mk_state == MK_STATE_FULL ||
		    mk_info.new_mk.mk_state == MK_STATE_COMMITTED)
			util_rec_set(rec, "NEW", "%s",
				     printable_mkvp(type, mk_info.new_mk.mkvp));
		else if (mk_info.new_mk.mk_state == MK_STATE_PARTIAL)
			util_rec_set(rec, "NEW", "partially loaded");
		else if (mk_info.new_mk.mk_state == MK_STATE_UNCOMMITTED)
			util_rec_set(rec, "NEW", "uncommitted");
		else
			util_rec_set(rec, "NEW", "-");

		if (mk_info.cur_mk.mk_state ==  MK_STATE_VALID)
			util_rec_set(rec, "CUR", "%s",
				     printable_mkvp(type, mk_info.cur_mk.mkvp));
		else
			util_rec_set(rec, "CUR", "-");

		if (mk_info.old_mk.mk_state ==  MK_STATE_VALID)
			util_rec_set(rec, "OLD", "%s",
				     printable_mkvp(type, mk_info.old_mk.mkvp));
		else
			util_rec_set(rec, "OLD", "-");

		if (level > 0 && type != CARD_TYPE_ANY)
			util_rec_set(rec, "TYPE", "CEX%d%c", level,
				     type == CARD_TYPE_CCA ? 'C' : 'P');
		else
			util_rec_set(rec, "TYPE", "?");

		util_rec_print(rec);
	}

out:
	util_rec_free(rec);

	return rc;
}

/**
 * Cross checks the list of APQNs specified if the CCA master keys of all APQNs
 * for the APKA master key are the same.
 *
 * @param pd                the plugin data
 * @param apqns             a list of APQNs
 * @param num_apqns         number of APQNs in above array
 *
 * @returns 0 on success, a negative errno in case of an error.
 * -ENODEV is returned if at least one APQN has a mismatching master key.
 */
int cross_check_cca_apka_apqns(struct plugin_data *pd,
			       const struct kms_apqn *apqns, size_t num_apqns)
{
	u8 new_mkvp[MKVP_LENGTH] = { 0, };
	u8 mkvp[MKVP_LENGTH] = { 0, };
	struct cca_lib cca = { 0 };
	struct mk_info mk_info;
	bool mismatch = false;
	bool print = false;
	int rc = -ENODEV;
	char temp[200];
	size_t i;

	for (i = 0; i < num_apqns; i++) {
		rc = select_cca_adapter(&cca, apqns[i].card, apqns[i].domain,
					pd->verbose);
		if (rc != 0) {
			pr_verbose(pd, "Failed to select APQN %02x.%04x: "
				   "%s", apqns[i].card, apqns[i].domain,
				   strerror(-rc));
			goto out;
		}

		rc = get_cca_apka_mk_info(pd, &cca, &mk_info);
		if (rc != 0) {
			pr_verbose(pd, "Failed to get the APKA master key "
				   "infos for APQN %02x.%04x: %s",
				   apqns[i].card, apqns[i].domain,
				   strerror(-rc));
			goto out;
		}

		if (mk_info.new_mk.mk_state == MK_STATE_PARTIAL) {
			print = true;
			sprintf(temp, "INFO: APQN %02x.%04x: The NEW APKA "
				"master key register is only partially loaded.",
				apqns[i].card, apqns[i].domain);
			util_print_indented(temp, 0);
		}

		if (MKVP_ZERO(new_mkvp) &&
		    mk_info.new_mk.mk_state == MK_STATE_FULL)
			memcpy(new_mkvp, mk_info.new_mk.mkvp, sizeof(new_mkvp));

		if (mk_info.new_mk.mk_state == MK_STATE_FULL &&
		    !MKVP_EQ(mk_info.new_mk.mkvp, new_mkvp)) {
			print = true;
			sprintf(temp, "WARNING: APQN %02x.%04x: The NEW APKA "
				"master key register contains a different "
				"master key than the NEW APKA register of "
				"other APQNs.", apqns[i].card, apqns[i].domain);
			util_print_indented(temp, 0);
		}

		if (mk_info.cur_mk.mk_state != MK_STATE_VALID) {
			mismatch = true;
			print = true;
			printf("WARNING: APQN %02x.%04x: No APKA master key is "
			       "set.\n", apqns[i].card, apqns[i].domain);
			continue;
		}

		if (mk_info.old_mk.mk_state == MK_STATE_VALID &&
		    MKVP_EQ(mk_info.old_mk.mkvp, mk_info.cur_mk.mkvp)) {
			print = true;
			sprintf(temp, "INFO: APQN %02x.%04x: The OLD APKA "
				"master key register contains the same master "
				"key as the CURRENT APKA master key register.",
				apqns[i].card, apqns[i].domain);
			util_print_indented(temp, 0);
		}

		if (mk_info.new_mk.mk_state == MK_STATE_FULL &&
		    MKVP_EQ(mk_info.new_mk.mkvp, mk_info.cur_mk.mkvp)) {
			print = true;
			sprintf(temp, "INFO: APQN %02x.%04x: The NEW APKA "
				"master key register contains the same master "
				"key as the CURRENT APKA master key register.",
				apqns[i].card, apqns[i].domain);
			util_print_indented(temp, 0);
		}

		if (mk_info.new_mk.mk_state == MK_STATE_FULL &&
		    mk_info.old_mk.mk_state == MK_STATE_VALID &&
		    MKVP_EQ(mk_info.new_mk.mkvp, mk_info.old_mk.mkvp)) {
			print = true;
			sprintf(temp, "INFO: APQN %02x.%04x: The NEW APKA "
				"master key register contains the same master "
				"key as the OLD APKA master key register.",
				apqns[i].card, apqns[i].domain);
			util_print_indented(temp, 0);
		}

		if (MKVP_ZERO(mkvp))
			memcpy(mkvp, mk_info.cur_mk.mkvp, sizeof(mkvp));

		if (!MKVP_EQ(mk_info.cur_mk.mkvp, mkvp)) {
			mismatch = true;
			print = true;
			sprintf(temp, "WARNING: APQN %02x.%04x: The CURRENT "
				"APKA master key register contains a different "
				"master key than the CURRENT APKA register of "
				"other APQNs.", apqns[i].card, apqns[i].domain);
			util_print_indented(temp, 0);
		}
	}

	if (mismatch) {
		pr_verbose(pd, "Your APKA master key setup is improper");
		rc = -ENODEV;
	}

	if (print)
		print_cca_apka_mks(pd, &cca, apqns, num_apqns);

out:
	if (cca.lib_csulcca != NULL)
		dlclose(cca.lib_csulcca);

	return rc;
}

/**
 * Selects one the CCA APQNs of the APQN string and loads the CCA
 * library and returns it.
 *
 * @param pd                the plugin data
 * @param apqns             the APQN string
 * @param cca_lib           On return: filled with CCA library infos
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
int select_cca_adapter_by_apqns(struct plugin_data *pd, const char *apqns,
				struct cca_lib *cca_lib)
{
	struct cca_lib cca = { 0 };
	unsigned int card, domain;
	char **apqn_list = NULL;
	bool selected = false;
	int rc = 0, i;

	pr_verbose(pd, "Select an APQN out of %s for the CCA host library",
		   apqns);

	apqn_list = str_list_split(apqns);
	for (i = 0; apqn_list[i] != NULL; i++) {
		if (sscanf(apqn_list[i], "%x.%x", &card, &domain) != 2)
			continue;

		if (sysfs_is_apqn_online(card, domain, CARD_TYPE_CCA) != 1)
			continue;

		rc = select_cca_adapter(&cca, card, domain, pd->verbose);
		if (rc != 0) {
			pr_verbose(pd, "Failed to select APQN %02x.%04x: "
				   "%s", card, domain, strerror(-rc));
			goto out;
		}

		selected = true;
		break;
	}

	if (!selected) {
		pr_verbose(pd, "None of the associated APQNs is "
			   "available: %s", apqns);
		rc = -ENODEV;
		goto out;
	}

	pr_verbose(pd, "Selected APQN %02x.%04x", card, domain);

	*cca_lib = cca;

out:
	if (apqn_list != NULL)
		str_list_free_string_array(apqn_list);

	if (rc != 0 && cca.lib_csulcca != NULL)
		dlclose(cca.lib_csulcca);

	return rc;
}

/**
 * Build an APQN string from an APQN array
 *
 * @param apqns           An array of APQNs
 * @param num_apqns       The number of elements in above array
 *
 * @returns an allocated string with the APQNs
 */
char *build_kms_apqn_string(const struct kms_apqn *apqns, size_t num_apqns)
{
	char *apqn_str, *str;
	size_t size, i;

	if (num_apqns == 0) {
		apqn_str = util_malloc(1);
		*apqn_str = '\0';
		return apqn_str;
	}

	size = num_apqns * 8; /* 'cc.dddd' plus ',' or '\0' */
	apqn_str = util_malloc(size);

	str = apqn_str;
	for (i = 0; i < num_apqns; i++) {
		if (i != 0) {
			*str = ',';
			str++;
		}

		sprintf(str, "%02x.%04x", apqns[i].card, apqns[i].domain);
		str += 7;
	}

	return apqn_str;
}

/**
 * Parse a semi-colon separated list and return an allocated array of the
 * elements.
 *
 * @param list              the semi-colon separated list
 * @param elements          on return, an allocated array of elements
 * @param num_elements      on return, the number of elements in the array
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
int parse_list(const char *list, char ***elements, size_t *num_elements)
{
	char *copy, *tok;
	size_t count;
	int i;

	for (i = 0, count = 1; list[i] != '\0'; i++)
		if (list[i] == ';')
			count++;

	*elements = util_zalloc(count * sizeof(char *));

	copy = util_strdup(list);
	tok = strtok(copy, ";");
	i = 0;
	while (tok != NULL) {
		if (strlen(tok) > 0) {
			(*elements)[i] = util_strdup(tok);
			i++;
		}
		tok = strtok(NULL, ";");
	}
	*num_elements = i;

	free(copy);

	return 0;
}
