summaryrefslogtreecommitdiff
path: root/src/libnmc-base
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnmc-base')
-rw-r--r--src/libnmc-base/meson.build15
-rw-r--r--src/libnmc-base/nm-client-utils.c886
-rw-r--r--src/libnmc-base/nm-client-utils.h52
-rw-r--r--src/libnmc-base/nm-polkit-listener.c910
-rw-r--r--src/libnmc-base/nm-polkit-listener.h33
-rw-r--r--src/libnmc-base/nm-secret-agent-simple.c1402
-rw-r--r--src/libnmc-base/nm-secret-agent-simple.h61
-rw-r--r--src/libnmc-base/nm-vpn-helpers.c821
-rw-r--r--src/libnmc-base/nm-vpn-helpers.h31
-rw-r--r--src/libnmc-base/qrcodegen.c1141
-rw-r--r--src/libnmc-base/qrcodegen.h312
11 files changed, 5664 insertions, 0 deletions
diff --git a/src/libnmc-base/meson.build b/src/libnmc-base/meson.build
new file mode 100644
index 0000000000..adb71531df
--- /dev/null
+++ b/src/libnmc-base/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+libnmc_base = static_library(
+ 'nmc-base',
+ sources: files(
+ 'nm-client-utils.c',
+ 'nm-secret-agent-simple.c',
+ 'nm-vpn-helpers.c',
+ 'nm-polkit-listener.c',
+ ),
+ dependencies: [
+ libnm_dep,
+ glib_dep,
+ ],
+)
diff --git a/src/libnmc-base/nm-client-utils.c b/src/libnmc-base/nm-client-utils.c
new file mode 100644
index 0000000000..701f8e1834
--- /dev/null
+++ b/src/libnmc-base/nm-client-utils.c
@@ -0,0 +1,886 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 - 2017 Red Hat, Inc.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nm-client-utils.h"
+
+#include "libnm-glib-aux/nm-secret-utils.h"
+#include "libnm-glib-aux/nm-io-utils.h"
+#include "nm-utils.h"
+#include "nm-device-bond.h"
+#include "nm-device-bridge.h"
+#include "nm-device-team.h"
+
+/*****************************************************************************/
+
+static int
+_nmc_objects_sort_by_path_cmp(gconstpointer pa, gconstpointer pb, gpointer user_data)
+{
+ NMObject *a = *((NMObject **) pa);
+ NMObject *b = *((NMObject **) pb);
+
+ NM_CMP_SELF(a, b);
+ NM_CMP_RETURN(nm_utils_dbus_path_cmp(nm_object_get_path(a), nm_object_get_path(b)));
+ return 0;
+}
+
+const NMObject **
+nmc_objects_sort_by_path(const NMObject *const *objs, gssize len)
+{
+ const NMObject **arr;
+ gsize i, l;
+
+ if (len < 0)
+ l = NM_PTRARRAY_LEN(objs);
+ else
+ l = len;
+
+ arr = g_new(const NMObject *, l + 1);
+ for (i = 0; i < l; i++)
+ arr[i] = objs[i];
+ arr[l] = NULL;
+
+ if (l > 1) {
+ g_qsort_with_data(arr, l, sizeof(gpointer), _nmc_objects_sort_by_path_cmp, NULL);
+ }
+ return arr;
+}
+
+/*****************************************************************************/
+/*
+ * Convert string to unsigned integer.
+ * If required, the resulting number is checked to be in the <min,max> range.
+ */
+static gboolean
+nmc_string_to_uint_base(const char * str,
+ int base,
+ gboolean range_check,
+ unsigned long int min,
+ unsigned long int max,
+ unsigned long int *value)
+{
+ char * end;
+ unsigned long int tmp;
+
+ if (!str || !str[0])
+ return FALSE;
+
+ /* FIXME: don't use this function, replace by _nm_utils_ascii_str_to_int64() */
+ errno = 0;
+ tmp = strtoul(str, &end, base);
+ if (errno || *end != '\0' || (range_check && (tmp < min || tmp > max))) {
+ return FALSE;
+ }
+ *value = tmp;
+ return TRUE;
+}
+
+gboolean
+nmc_string_to_uint(const char * str,
+ gboolean range_check,
+ unsigned long int min,
+ unsigned long int max,
+ unsigned long int *value)
+{
+ return nmc_string_to_uint_base(str, 10, range_check, min, max, value);
+}
+
+gboolean
+nmc_string_to_bool(const char *str, gboolean *val_bool, GError **error)
+{
+ const char *s_true[] = {"true", "yes", "on", "1", NULL};
+ const char *s_false[] = {"false", "no", "off", "0", NULL};
+
+ g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+
+ if (g_strcmp0(str, "o") == 0) {
+ g_set_error(error,
+ 1,
+ 0,
+ /* TRANSLATORS: the first %s is the partial value entered by
+ * the user, the second %s a list of compatible values.
+ */
+ _("'%s' is ambiguous (%s)"),
+ str,
+ "on x off");
+ return FALSE;
+ }
+
+ if (nmc_string_is_valid(str, s_true, NULL))
+ *val_bool = TRUE;
+ else if (nmc_string_is_valid(str, s_false, NULL))
+ *val_bool = FALSE;
+ else {
+ g_set_error(error,
+ 1,
+ 0,
+ _("'%s' is not valid; use [%s] or [%s]"),
+ str,
+ "true, yes, on",
+ "false, no, off");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean
+nmc_string_to_ternary(const char *str, NMTernary *val, GError **error)
+{
+ const char *s_true[] = {"true", "yes", "on", NULL};
+ const char *s_false[] = {"false", "no", "off", NULL};
+ const char *s_unknown[] = {"unknown", NULL};
+
+ g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+
+ if (g_strcmp0(str, "o") == 0) {
+ g_set_error(error,
+ 1,
+ 0,
+ /* TRANSLATORS: the first %s is the partial value entered by
+ * the user, the second %s a list of compatible values.
+ */
+ _("'%s' is ambiguous (%s)"),
+ str,
+ "on x off");
+ return FALSE;
+ }
+
+ if (nmc_string_is_valid(str, s_true, NULL))
+ *val = NM_TERNARY_TRUE;
+ else if (nmc_string_is_valid(str, s_false, NULL))
+ *val = NM_TERNARY_FALSE;
+ else if (nmc_string_is_valid(str, s_unknown, NULL))
+ *val = NM_TERNARY_DEFAULT;
+ else {
+ g_set_error(error,
+ 1,
+ 0,
+ _("'%s' is not valid; use [%s], [%s] or [%s]"),
+ str,
+ "true, yes, on",
+ "false, no, off",
+ "unknown");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Check whether 'input' is contained in 'allowed' array. It performs case
+ * insensitive comparison and supports shortcut strings if they are unique.
+ * Returns: a pointer to found string in allowed array on success or NULL.
+ * On failure: error->code : 0 - string not found; 1 - string is ambiguous
+ */
+const char *
+nmc_string_is_valid(const char *input, const char **allowed, GError **error)
+{
+ const char **p;
+ size_t input_ln, p_len;
+ const char * partial_match = NULL;
+ gboolean ambiguous = FALSE;
+
+ g_return_val_if_fail(!error || !*error, NULL);
+
+ if (!input || !*input)
+ goto finish;
+
+ input_ln = strlen(input);
+ for (p = allowed; p && *p; p++) {
+ p_len = strlen(*p);
+ if (g_ascii_strncasecmp(input, *p, input_ln) == 0) {
+ if (input_ln == p_len)
+ return *p;
+ if (!partial_match)
+ partial_match = *p;
+ else
+ ambiguous = TRUE;
+ }
+ }
+
+ if (ambiguous) {
+ GString *candidates = g_string_new("");
+
+ for (p = allowed; *p; p++) {
+ if (g_ascii_strncasecmp(input, *p, input_ln) == 0) {
+ if (candidates->len > 0)
+ g_string_append(candidates, ", ");
+ g_string_append(candidates, *p);
+ }
+ }
+ g_set_error(error, 1, 1, _("'%s' is ambiguous: %s"), input, candidates->str);
+ g_string_free(candidates, TRUE);
+ return NULL;
+ }
+finish:
+ if (!partial_match) {
+ char *valid_vals = g_strjoinv(", ", (char **) allowed);
+
+ if (!input || !*input)
+ g_set_error(error, 1, 0, _("missing name, try one of [%s]"), valid_vals);
+ else
+ g_set_error(error, 1, 0, _("'%s' not among [%s]"), input, valid_vals);
+
+ g_free(valid_vals);
+ }
+
+ return partial_match;
+}
+
+gboolean
+matches(const char *cmd, const char *pattern)
+{
+ size_t len = strlen(cmd);
+ if (!len || len > strlen(pattern))
+ return FALSE;
+ return memcmp(pattern, cmd, len) == 0;
+}
+
+const char *
+nmc_bond_validate_mode(const char *mode, GError **error)
+{
+ unsigned long mode_int;
+ static const char *valid_modes[] = {"balance-rr",
+ "active-backup",
+ "balance-xor",
+ "broadcast",
+ "802.3ad",
+ "balance-tlb",
+ "balance-alb",
+ NULL};
+ if (nmc_string_to_uint(mode, TRUE, 0, 6, &mode_int)) {
+ /* Translate bonding mode numbers to mode names:
+ * https://www.kernel.org/doc/Documentation/networking/bonding.txt
+ */
+ return valid_modes[mode_int];
+ } else
+ return nmc_string_is_valid(mode, valid_modes, error);
+}
+
+NM_UTILS_LOOKUP_STR_DEFINE(
+ nmc_device_state_to_string,
+ NMDeviceState,
+ NM_UTILS_LOOKUP_DEFAULT(N_("unknown")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_UNMANAGED, N_("unmanaged")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_UNAVAILABLE, N_("unavailable")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_DISCONNECTED, N_("disconnected")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_PREPARE, N_("connecting (prepare)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_CONFIG, N_("connecting (configuring)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_NEED_AUTH, N_("connecting (need authentication)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_IP_CONFIG, N_("connecting (getting IP configuration)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_IP_CHECK, N_("connecting (checking IP connectivity)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_SECONDARIES,
+ N_("connecting (starting secondary connections)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_ACTIVATED, N_("connected")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_DEACTIVATING, N_("deactivating")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_FAILED, N_("connection failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_UNKNOWN, N_("unknown")), );
+
+static NM_UTILS_LOOKUP_STR_DEFINE(
+ _device_state_to_string,
+ NMDeviceState,
+ NM_UTILS_LOOKUP_DEFAULT(NULL),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_PREPARE, N_("connecting (externally)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_CONFIG, N_("connecting (externally)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_NEED_AUTH, N_("connecting (externally)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_IP_CONFIG, N_("connecting (externally)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_IP_CHECK, N_("connecting (externally)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_SECONDARIES, N_("connecting (externally)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_ACTIVATED, N_("connected (externally)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_DEACTIVATING, N_("deactivating (externally)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_FAILED, N_("deactivating (externally)")),
+ NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), );
+
+const char *
+nmc_device_state_to_string_with_external(NMDevice *device)
+{
+ NMActiveConnection *ac;
+ NMDeviceState state;
+ const char * s;
+
+ state = nm_device_get_state(device);
+
+ if ((ac = nm_device_get_active_connection(device))
+ && NM_FLAGS_HAS(nm_active_connection_get_state_flags(ac), NM_ACTIVATION_STATE_FLAG_EXTERNAL)
+ && (s = _device_state_to_string(state)))
+ return s;
+
+ return nmc_device_state_to_string(state);
+}
+
+NM_UTILS_LOOKUP_STR_DEFINE(nmc_device_metered_to_string,
+ NMMetered,
+ NM_UTILS_LOOKUP_DEFAULT(N_("unknown")),
+ NM_UTILS_LOOKUP_ITEM(NM_METERED_YES, N_("yes")),
+ NM_UTILS_LOOKUP_ITEM(NM_METERED_NO, N_("no")),
+ NM_UTILS_LOOKUP_ITEM(NM_METERED_GUESS_YES, N_("yes (guessed)")),
+ NM_UTILS_LOOKUP_ITEM(NM_METERED_GUESS_NO, N_("no (guessed)")),
+ NM_UTILS_LOOKUP_ITEM(NM_METERED_UNKNOWN, N_("unknown")), );
+
+NM_UTILS_LOOKUP_STR_DEFINE(
+ nmc_device_reason_to_string,
+ NMDeviceStateReason,
+ /* TRANSLATORS: Unknown reason for a device state change (NMDeviceStateReason) */
+ NM_UTILS_LOOKUP_DEFAULT(N_("Unknown")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_NONE, N_("No reason given")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNKNOWN, N_("Unknown error")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_NOW_MANAGED, N_("Device is now managed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_NOW_UNMANAGED, N_("Device is now unmanaged")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_CONFIG_FAILED,
+ N_("The device could not be readied for configuration")),
+ NM_UTILS_LOOKUP_ITEM(
+ NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE,
+ N_("IP configuration could not be reserved (no available address, timeout, etc.)")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED,
+ N_("The IP configuration is no longer valid")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_NO_SECRETS,
+ N_("Secrets were required, but not provided")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT,
+ N_("802.1X supplicant disconnected")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED,
+ N_("802.1X supplicant configuration failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED, N_("802.1X supplicant failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT,
+ N_("802.1X supplicant took too long to authenticate")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_PPP_START_FAILED,
+ N_("PPP service failed to start")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_PPP_DISCONNECT, N_("PPP service disconnected")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_PPP_FAILED, N_("PPP failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_DHCP_START_FAILED,
+ N_("DHCP client failed to start")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_DHCP_ERROR, N_("DHCP client error")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_DHCP_FAILED, N_("DHCP client failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SHARED_START_FAILED,
+ N_("Shared connection service failed to start")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SHARED_FAILED,
+ N_("Shared connection service failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED,
+ N_("AutoIP service failed to start")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_ERROR, N_("AutoIP service error")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_FAILED, N_("AutoIP service failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_MODEM_BUSY, N_("The line is busy")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE, N_("No dial tone")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER,
+ N_("No carrier could be established")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT,
+ N_("The dialing request timed out")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED,
+ N_("The dialing attempt failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED,
+ N_("Modem initialization failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_GSM_APN_FAILED,
+ N_("Failed to select the specified APN")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING,
+ N_("Not searching for networks")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED,
+ N_("Network registration denied")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT,
+ N_("Network registration timed out")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED,
+ N_("Failed to register with the requested network")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED, N_("PIN check failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_FIRMWARE_MISSING,
+ N_("Necessary firmware for the device may be missing")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_REMOVED, N_("The device was removed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SLEEPING, N_("NetworkManager went to sleep")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_REMOVED,
+ N_("The device's active connection disappeared")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_USER_REQUESTED,
+ N_("Device disconnected by user or client")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_CARRIER, N_("Carrier/link changed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED,
+ N_("The device's existing connection was assumed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+ N_("The supplicant is now available")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND,
+ N_("The modem could not be found")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_BT_FAILED,
+ N_("The Bluetooth connection failed or timed out")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED,
+ N_("GSM Modem's SIM card not inserted")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED,
+ N_("GSM Modem's SIM PIN required")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED,
+ N_("GSM Modem's SIM PUK required")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_WRONG, N_("GSM Modem's SIM wrong")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_INFINIBAND_MODE,
+ N_("InfiniBand device does not support connected mode")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED,
+ N_("A dependency of the connection failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_BR2684_FAILED,
+ N_("A problem with the RFC 2684 Ethernet over ADSL bridge")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE,
+ N_("ModemManager is unavailable")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SSID_NOT_FOUND,
+ N_("The Wi-Fi network could not be found")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED,
+ N_("A secondary connection of the base connection failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED, N_("DCB or FCoE setup failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED, N_("teamd control failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_MODEM_FAILED,
+ N_("Modem failed or no longer available")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_MODEM_AVAILABLE,
+ N_("Modem now ready and available")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT, N_("SIM PIN was incorrect")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_NEW_ACTIVATION,
+ N_("New connection activation was enqueued")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_PARENT_CHANGED, N_("The device's parent changed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED,
+ N_("The device parent's management changed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_OVSDB_FAILED,
+ N_("Open vSwitch database connection failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE,
+ N_("A duplicate IP address was detected")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED,
+ N_("The selected IP method is not supported")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED,
+ N_("Failed to configure SR-IOV parameters")),
+ NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_PEER_NOT_FOUND,
+ N_("The Wi-Fi P2P peer could not be found")), );
+
+NM_UTILS_LOOKUP_STR_DEFINE(
+ nm_active_connection_state_reason_to_string,
+ NMActiveConnectionStateReason,
+ /* TRANSLATORS: Unknown reason for a connection state change (NMActiveConnectionStateReason) */
+ NM_UTILS_LOOKUP_DEFAULT(N_("Unknown")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, N_("Unknown reason")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_NONE,
+ N_("The connection was disconnected")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED,
+ N_("Disconnected by user")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED,
+ N_("The base network connection was interrupted")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED,
+ N_("The VPN service stopped unexpectedly")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID,
+ N_("The VPN service returned invalid configuration")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT,
+ N_("The connection attempt timed out")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT,
+ N_("The VPN service did not start in time")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED,
+ N_("The VPN service failed to start")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS, N_("No valid secrets")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_LOGIN_FAILED, N_("Invalid secrets")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED,
+ N_("The connection was removed")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_DEPENDENCY_FAILED,
+ N_("Master connection failed")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REALIZE_FAILED,
+ N_("Could not create a software link")),
+ NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REMOVED,
+ N_("The device disappeared")), );
+
+NMActiveConnectionState
+nmc_activation_get_effective_state(NMActiveConnection *active,
+ NMDevice * device,
+ const char ** reason)
+{
+ NMActiveConnectionState ac_state;
+ NMActiveConnectionStateReason ac_reason;
+ NMDeviceState dev_state = NM_DEVICE_STATE_UNKNOWN;
+ NMDeviceStateReason dev_reason = NM_DEVICE_STATE_REASON_UNKNOWN;
+
+ g_return_val_if_fail(active, NM_ACTIVE_CONNECTION_STATE_UNKNOWN);
+ g_return_val_if_fail(reason, NM_ACTIVE_CONNECTION_STATE_UNKNOWN);
+
+ *reason = NULL;
+ ac_reason = nm_active_connection_get_state_reason(active);
+
+ if (device) {
+ dev_state = nm_device_get_state(device);
+ dev_reason = nm_device_get_state_reason(device);
+ }
+
+ ac_state = nm_active_connection_get_state(active);
+ switch (ac_state) {
+ case NM_ACTIVE_CONNECTION_STATE_DEACTIVATED:
+ if (!device || ac_reason != NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED
+ || nm_device_get_active_connection(device) != active) {
+ /* (1)
+ * - we have no device,
+ * - or, @ac_reason is specific
+ * - or, @device no longer references the current @active
+ * >> we complete with @ac_reason. */
+ *reason = gettext(nm_active_connection_state_reason_to_string(ac_reason));
+ } else if (dev_state <= NM_DEVICE_STATE_DISCONNECTED
+ || dev_state >= NM_DEVICE_STATE_FAILED) {
+ /* (2)
+ * - not (1)
+ * - and, the device is no longer in an activated state,
+ * >> we complete with @dev_reason. */
+ *reason = gettext(nmc_device_reason_to_string(dev_reason));
+ } else {
+ /* (3)
+ * we wait for the device go disconnect. We will get a better
+ * failure reason from the device (2). */
+ return NM_ACTIVE_CONNECTION_STATE_UNKNOWN;
+ }
+ break;
+ case NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
+ /* activating master connection does not automatically activate any slaves, so their
+ * active connection state will not progress beyond ACTIVATING state.
+ * Monitor the device instead. */
+ if (device
+ && (NM_IS_DEVICE_BOND(device) || NM_IS_DEVICE_TEAM(device)
+ || NM_IS_DEVICE_BRIDGE(device))
+ && dev_state >= NM_DEVICE_STATE_IP_CONFIG && dev_state <= NM_DEVICE_STATE_ACTIVATED) {
+ *reason = "master waiting for slaves";
+ return NM_ACTIVE_CONNECTION_STATE_ACTIVATED;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ac_state;
+}
+
+static gboolean
+can_show_utf8(void)
+{
+ static gboolean can_show_utf8_set = FALSE;
+ static gboolean can_show_utf8 = TRUE;
+ char * locale_str;
+
+ if (G_LIKELY(can_show_utf8_set))
+ return can_show_utf8;
+
+ if (!g_get_charset(NULL)) {
+ /* Non-UTF-8 locale */
+ locale_str = g_locale_from_utf8("\342\226\202\342\226\204\342\226\206\342\226\210",
+ -1,
+ NULL,
+ NULL,
+ NULL);
+ if (locale_str)
+ g_free(locale_str);
+ else
+ can_show_utf8 = FALSE;
+ }
+
+ return can_show_utf8;
+}
+
+static gboolean
+can_show_graphics(void)
+{
+ static gboolean can_show_graphics_set = FALSE;
+ static gboolean can_show_graphics = TRUE;
+
+ if (G_LIKELY(can_show_graphics_set))
+ return can_show_graphics;
+
+ can_show_graphics = can_show_utf8();
+
+ /* The linux console font typically doesn't have characters we need */
+ if (g_strcmp0(g_getenv("TERM"), "linux") == 0)
+ can_show_graphics = FALSE;
+
+ return can_show_graphics;
+}
+
+/**
+ * nmc_wifi_strength_bars:
+ * @strength: the access point strength, from 0 to 100
+ *
+ * Converts @strength into a 4-character-wide graphical representation of
+ * strength suitable for printing to stdout. If the current locale and terminal
+ * support it, this will use unicode graphics characters to represent
+ * "bars". Otherwise, it will use 0 to 4 asterisks.
+ *
+ * Returns: the graphical representation of the access point strength
+ */
+const char *
+nmc_wifi_strength_bars(guint8 strength)
+{
+ if (!can_show_graphics())
+ return nm_utils_wifi_strength_bars(strength);
+
+ if (strength > 80)
+ return /* ▂▄▆█ */ "\342\226\202\342\226\204\342\226\206\342\226\210";
+ else if (strength > 55)
+ return /* â–‚â–„â–†_ */ "\342\226\202\342\226\204\342\226\206_";
+ else if (strength > 30)
+ return /* â–‚â–„__ */ "\342\226\202\342\226\204__";
+ else if (strength > 5)
+ return /* â–‚___ */ "\342\226\202___";
+ else
+ return /* ____ */ "____";
+}
+
+/**
+ * nmc_utils_password_subst_char:
+ *
+ * Returns: the string substituted when hiding actual password glyphs
+ */
+const char *
+nmc_password_subst_char(void)
+{
+ if (can_show_graphics())
+ return "\u2022"; /* Bullet */
+ else
+ return "*";
+}
+
+/*
+ * We actually use a small part of qrcodegen.c, but we'd prefer to keep it
+ * intact. Include it instead of linking to it to give the compiler a
+ * chance to optimize bits we don't need away.
+ */
+
+#pragma GCC visibility push(hidden)
+NM_PRAGMA_WARNING_DISABLE("-Wdeclaration-after-statement")
+#undef NDEBUG
+#define NDEBUG
+#include "qrcodegen.c"
+NM_PRAGMA_WARNING_REENABLE
+#pragma GCC visibility pop
+
+void
+nmc_print_qrcode(const char *str)
+{
+ uint8_t tempBuffer[qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX)];
+ uint8_t qrcode[qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX)];
+ gboolean term_linux;
+ int size;
+ int x;
+ int y;
+
+ term_linux = g_strcmp0(g_getenv("TERM"), "linux") == 0;
+ if (!term_linux && !can_show_graphics())
+ return;
+
+ if (!qrcodegen_encodeText(str,
+ tempBuffer,
+ qrcode,
+ qrcodegen_Ecc_LOW,
+ qrcodegen_VERSION_MIN,
+ qrcodegen_VERSION_MAX,
+ qrcodegen_Mask_AUTO,
+ FALSE)) {
+ return;
+ }
+
+ size = qrcodegen_getSize(qrcode);
+
+ g_print("\n");
+
+ if (term_linux) {
+ /* G1 alternate character set on Linux console. */
+ for (y = -1; y < size + 1; y += 1) {
+ g_print(" \033[37;40;1m\016");
+ for (x = -1; x < size + 1; x++) {
+ g_print(qrcodegen_getModule(qrcode, x, y) ? " " : "\060\060");
+ }
+ g_print("\017\033[0m\n");
+ }
+ } else {
+ /* UTF-8 */
+ for (y = -2; y < size + 2; y += 2) {
+ g_print(" \033[37;40m");
+ for (x = -2; x < size + 2; x++) {
+ bool top = qrcodegen_getModule(qrcode, x, y);
+ bool bottom = qrcodegen_getModule(qrcode, x, y + 1);
+ if (top) {
+ g_print(bottom ? " " : "\u2584");
+ } else {
+ g_print(bottom ? "\u2580" : "\u2588");
+ }
+ }
+ g_print("\033[0m\n");
+ }
+ }
+}
+
+/**
+ * nmc_utils_read_passwd_file:
+ * @passwd_file: file with passwords to parse
+ * @out_error_line: returns in case of a syntax error in the file, the line
+ * on which it occurred.
+ * @error: location to store error, or %NULL
+ *
+ * Parse passwords given in @passwd_file and insert them into a hash table.
+ * Example of @passwd_file contents:
+ * wifi.psk:tajne heslo
+ * 802-1x.password:krakonos
+ * 802-11-wireless-security:leap-password:my leap password
+ *
+ * Returns: (transfer full): hash table with parsed passwords, or %NULL on an error
+ */
+GHashTable *
+nmc_utils_read_passwd_file(const char *passwd_file, gssize *out_error_line, GError **error)
+{
+ nm_auto_clear_secret_ptr NMSecretPtr contents = {0};
+
+ NM_SET_OUT(out_error_line, -1);
+
+ if (!passwd_file)
+ return g_hash_table_new_full(nm_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) nm_free_secret);
+
+ if (!nm_utils_file_get_contents(-1,
+ passwd_file,
+ 1024 * 1024,
+ NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET,
+ &contents.str,
+ &contents.len,
+ NULL,
+ error))
+ return NULL;
+
+ return nmc_utils_parse_passwd_file(contents.str, out_error_line, error);
+}
+
+GHashTable *
+nmc_utils_parse_passwd_file(char * contents /* will be modified */,
+ gssize * out_error_line,
+ GError **error)
+{
+ gs_unref_hashtable GHashTable *pwds_hash = NULL;
+ const char * contents_str;
+ gsize contents_line;
+
+ pwds_hash =
+ g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, (GDestroyNotify) nm_free_secret);
+
+ NM_SET_OUT(out_error_line, -1);
+
+ contents_str = contents;
+ contents_line = 0;
+ while (contents_str[0]) {
+ nm_auto_free_secret char *l_hash_key = NULL;
+ nm_auto_free_secret char *l_hash_val = NULL;
+ const char * l_content_line;
+ const char * l_setting;
+ const char * l_prop;
+ const char * l_val;
+ const char * s;
+ gsize l_hash_val_len;
+
+ /* consume first line. As line delimiters we accept "\r\n", "\n", and "\r". */
+ l_content_line = contents_str;
+ s = l_content_line;
+ while (!NM_IN_SET(s[0], '\0', '\r', '\n'))
+ s++;
+ if (s[0] != '\0') {
+ if (s[0] == '\r' && s[1] == '\n') {
+ ((char *) s)[0] = '\0';
+ s += 2;
+ } else {
+ ((char *) s)[0] = '\0';
+ s += 1;
+ }
+ }
+ contents_str = s;
+ contents_line++;
+
+ l_content_line = nm_str_skip_leading_spaces(l_content_line);
+ if (NM_IN_SET(l_content_line[0], '\0', '#')) {
+ /* a comment or empty line. Ignore. */
+ continue;
+ }
+
+ l_setting = l_content_line;
+
+ s = l_setting;
+ while (!NM_IN_SET(s[0], '\0', ':', '='))
+ s++;
+ if (s[0] == '\0') {
+ NM_SET_OUT(out_error_line, contents_line);
+ nm_utils_error_set(error,
+ NM_UTILS_ERROR_UNKNOWN,
+ _("missing colon for \"<setting>.<property>:<secret>\" format"));
+ return NULL;
+ }
+ ((char *) s)[0] = '\0';
+ s++;
+
+ l_val = s;
+
+ g_strchomp((char *) l_setting);
+
+ nm_assert(nm_str_is_stripped(l_setting));
+
+ s = strchr(l_setting, '.');
+ if (!s) {
+ NM_SET_OUT(out_error_line, contents_line);
+ nm_utils_error_set(error,
+ NM_UTILS_ERROR_UNKNOWN,
+ _("missing dot for \"<setting>.<property>:<secret>\" format"));
+ return NULL;
+ } else if (s == l_setting) {
+ NM_SET_OUT(out_error_line, contents_line);
+ nm_utils_error_set(error,
+ NM_UTILS_ERROR_UNKNOWN,
+ _("missing setting for \"<setting>.<property>:<secret>\" format"));
+ return NULL;
+ }
+ ((char *) s)[0] = '\0';
+ s++;
+
+ l_prop = s;
+ if (l_prop[0] == '\0') {
+ NM_SET_OUT(out_error_line, contents_line);
+ nm_utils_error_set(error,
+ NM_UTILS_ERROR_UNKNOWN,
+ _("missing property for \"<setting>.<property>:<secret>\" format"));
+ return NULL;
+ }
+
+ /* Accept wifi-sec or wifi instead of cumbersome '802-11-wireless-security' */
+ if (NM_IN_STRSET(l_setting, "wifi-sec", "wifi"))
+ l_setting = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
+
+ if (nm_setting_lookup_type(l_setting) == G_TYPE_INVALID) {
+ NM_SET_OUT(out_error_line, contents_line);
+ nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("invalid setting name"));
+ return NULL;
+ }
+
+ if (nm_streq(l_setting, "vpn") && NM_STR_HAS_PREFIX(l_prop, "secret.")) {
+ /* in 1.12.0, we wrongly required the VPN secrets to be named
+ * "vpn.secret". It should be "vpn.secrets". Work around it
+ * (rh#1628833). */
+ l_hash_key = g_strdup_printf("vpn.secrets.%s", &l_prop[NM_STRLEN("secret.")]);
+ } else
+ l_hash_key = g_strdup_printf("%s.%s", l_setting, l_prop);
+
+ if (!g_utf8_validate(l_hash_key, -1, NULL)) {
+ NM_SET_OUT(out_error_line, contents_line);
+ nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("property name is not UTF-8"));
+ return NULL;
+ }
+
+ /* Support backslash escaping in the secret value. We strip non-escaped leading/trailing whitespaces. */
+ s = nm_utils_buf_utf8safe_unescape(l_val,
+ NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES,
+ &l_hash_val_len,
+ (gpointer *) &l_hash_val);
+ if (!l_hash_val)
+ l_hash_val = g_strdup(s);
+
+ if (!g_utf8_validate(l_hash_val, -1, NULL)) {
+ /* In some cases it might make sense to support binary secrets (like the WPA-PSK which has no
+ * defined encoding. However, all API that follows can only handle UTF-8, and no mechanism
+ * to escape the secrets. Reject non-UTF-8 early. */
+ NM_SET_OUT(out_error_line, contents_line);
+ nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("secret is not UTF-8"));
+ return NULL;
+ }
+
+ if (strlen(l_hash_val) != l_hash_val_len) {
+ NM_SET_OUT(out_error_line, contents_line);
+ nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("secret is not UTF-8"));
+ return NULL;
+ }
+
+ g_hash_table_insert(pwds_hash, g_steal_pointer(&l_hash_key), g_steal_pointer(&l_hash_val));
+ }
+
+ return g_steal_pointer(&pwds_hash);
+}
diff --git a/src/libnmc-base/nm-client-utils.h b/src/libnmc-base/nm-client-utils.h
new file mode 100644
index 0000000000..7017e39a75
--- /dev/null
+++ b/src/libnmc-base/nm-client-utils.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 - 2017 Red Hat, Inc.
+ */
+
+#ifndef __NM_CLIENT_UTILS_H__
+#define __NM_CLIENT_UTILS_H__
+
+#include "nm-active-connection.h"
+#include "nm-device.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+
+const NMObject **nmc_objects_sort_by_path(const NMObject *const *objs, gssize len);
+
+const char *nmc_string_is_valid(const char *input, const char **allowed, GError **error);
+
+gboolean nmc_string_to_uint(const char * str,
+ gboolean range_check,
+ unsigned long int min,
+ unsigned long int max,
+ unsigned long int *value);
+gboolean nmc_string_to_bool(const char *str, gboolean *val_bool, GError **error);
+gboolean nmc_string_to_ternary(const char *str, NMTernary *val, GError **error);
+
+gboolean matches(const char *cmd, const char *pattern);
+
+/* FIXME: don't expose this function on its own, at least not from this file. */
+const char *nmc_bond_validate_mode(const char *mode, GError **error);
+
+const char *nmc_device_state_to_string_with_external(NMDevice *device);
+
+const char *nm_active_connection_state_reason_to_string(NMActiveConnectionStateReason reason);
+const char *nmc_device_state_to_string(NMDeviceState state);
+const char *nmc_device_reason_to_string(NMDeviceStateReason reason);
+const char *nmc_device_metered_to_string(NMMetered value);
+
+NMActiveConnectionState nmc_activation_get_effective_state(NMActiveConnection *active,
+ NMDevice * device,
+ const char ** reason);
+
+const char *nmc_wifi_strength_bars(guint8 strength);
+
+const char *nmc_password_subst_char(void);
+
+void nmc_print_qrcode(const char *str);
+
+GHashTable *nmc_utils_parse_passwd_file(char *contents, gssize *out_error_line, GError **error);
+
+GHashTable *
+nmc_utils_read_passwd_file(const char *passwd_file, gssize *out_error_line, GError **error);
+
+#endif /* __NM_CLIENT_UTILS_H__ */
diff --git a/src/libnmc-base/nm-polkit-listener.c b/src/libnmc-base/nm-polkit-listener.c
new file mode 100644
index 0000000000..29c25b4e67
--- /dev/null
+++ b/src/libnmc-base/nm-polkit-listener.c
@@ -0,0 +1,910 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nm-polkit-listener
+ * @short_description: A polkit agent listener
+ *
+ * #NMPolkitListener is the polkit agent listener used by nmcli and nmtui.
+ * http://www.freedesktop.org/software/polkit/docs/latest/index.html
+ *
+ * For an example polkit agent you can look at polkit source tree:
+ * http://cgit.freedesktop.org/polkit/tree/src/polkitagent/polkitagenttextlistener.c
+ * http://cgit.freedesktop.org/polkit/tree/src/programs/pkttyagent.c
+ * or LXDE polkit agent:
+ * http://git.lxde.org/gitweb/?p=debian/lxpolkit.git;a=blob;f=src/lxpolkit-listener.c
+ * https://github.com/lxde/lxqt-policykit/tree/master/src
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nm-polkit-listener.h"
+
+#include <gio/gio.h>
+#include <pwd.h>
+#include <fcntl.h>
+
+#include "libnm-glib-aux/nm-dbus-aux.h"
+#include "libnm-glib-aux/nm-str-buf.h"
+#include "libnm-glib-aux/nm-secret-utils.h"
+#include "libnm-glib-aux/nm-io-utils.h"
+#include "libnm-core-aux-intern/nm-auth-subject.h"
+#include "c-list/src/c-list.h"
+
+#define LOGIND_BUS_NAME "org.freedesktop.login1"
+#define POLKIT_BUS_NAME "org.freedesktop.PolicyKit1"
+
+#define POLKIT_AUTHORITY_OBJ_PATH "/org/freedesktop/PolicyKit1/Authority"
+#define POLKIT_AUTHORITY_IFACE_NAME "org.freedesktop.PolicyKit1.Authority"
+
+#define POLKIT_AGENT_OBJ_PATH "/org/freedesktop/PolicyKit1/AuthenticationAgent"
+#define POLKIT_AGENT_DBUS_INTERFACE "org.freedesktop.PolicyKit1.AuthenticationAgent"
+
+#define LOGIND_OBJ_PATH "/org/freedesktop/login1"
+#define LOGIND_MANAGER_INTERFACE "org.freedesktop.login1.Manager"
+
+#define NM_POLKIT_LISTENER_DBUS_CONNECTION "dbus-connection"
+#define NM_POLKIT_LISTENER_SESSION_AGENT "session-agent"
+
+#define POLKIT_DBUS_ERROR_FAILED "org.freedesktop.PolicyKit1.Error.Failed"
+
+/*****************************************************************************/
+
+enum { REGISTERED, REQUEST_SYNC, ERROR, LAST_SIGNAL };
+
+static guint signals[LAST_SIGNAL] = {0};
+
+struct _NMPolkitListener {
+ GObject parent;
+ GDBusConnection *dbus_connection;
+ char * name_owner;
+ GCancellable * cancellable;
+ GMainContext * main_context;
+ CList request_lst_head;
+ guint pk_auth_agent_reg_id;
+ guint name_owner_changed_id;
+ bool session_agent : 1;
+};
+
+struct _NMPolkitListenerClass {
+ GObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMPolkitListener, nm_polkit_listener, G_TYPE_OBJECT);
+
+/*****************************************************************************/
+
+typedef struct {
+ CList request_lst;
+
+ NMPolkitListener * listener;
+ GSource * child_stdout_watch_source;
+ GSource * child_stdin_watch_source;
+ GDBusMethodInvocation *dbus_invocation;
+ char * action_id;
+ char * message;
+ char * username;
+ char * cookie;
+ NMStrBuf in_buffer;
+ NMStrBuf out_buffer;
+ gsize out_buffer_offset;
+
+ int child_stdout;
+ int child_stdin;
+
+ bool request_any_response : 1;
+ bool request_is_completed : 1;
+} AuthRequest;
+
+static const GDBusInterfaceInfo interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+ POLKIT_AGENT_DBUS_INTERFACE,
+ .methods = NM_DEFINE_GDBUS_METHOD_INFOS(
+ NM_DEFINE_GDBUS_METHOD_INFO("BeginAuthentication",
+ .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
+ NM_DEFINE_GDBUS_ARG_INFO("action_id", "s"),
+ NM_DEFINE_GDBUS_ARG_INFO("message", "s"),
+ NM_DEFINE_GDBUS_ARG_INFO("icon_name", "s"),
+ NM_DEFINE_GDBUS_ARG_INFO("details", "a{ss}"),
+ NM_DEFINE_GDBUS_ARG_INFO("cookie", "s"),
+ NM_DEFINE_GDBUS_ARG_INFO("identities", "a(sa{sv})"), ), ),
+ NM_DEFINE_GDBUS_METHOD_INFO("CancelAuthentication",
+ .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
+ NM_DEFINE_GDBUS_ARG_INFO("cookie", "s"), ), ), ), );
+
+static void
+auth_request_complete(AuthRequest *request, gboolean success)
+{
+ c_list_unlink(&request->request_lst);
+
+ if (success)
+ g_dbus_method_invocation_return_value(request->dbus_invocation, NULL);
+ else {
+ g_dbus_method_invocation_return_dbus_error(request->dbus_invocation,
+ POLKIT_DBUS_ERROR_FAILED,
+ "");
+ }
+
+ nm_clear_g_free(&request->action_id);
+ nm_clear_g_free(&request->message);
+ nm_clear_g_free(&request->username);
+ nm_clear_g_free(&request->cookie);
+ nm_clear_g_source_inst(&request->child_stdout_watch_source);
+ nm_clear_g_source_inst(&request->child_stdin_watch_source);
+
+ nm_str_buf_destroy(&request->in_buffer);
+ nm_str_buf_destroy(&request->out_buffer);
+
+ if (request->child_stdout != -1) {
+ nm_close(request->child_stdout);
+ request->child_stdout = -1;
+ }
+
+ if (request->child_stdin != -1) {
+ nm_close(request->child_stdin);
+ request->child_stdin = -1;
+ }
+
+ nm_g_slice_free(request);
+}
+
+static gboolean
+uid_to_name(uid_t uid, gboolean *out_cached, char **out_name)
+{
+ if (!*out_cached) {
+ *out_cached = TRUE;
+ *out_name = nm_utils_uid_to_name(uid);
+ }
+ return !!(*out_name);
+}
+
+static char *
+choose_identity(GVariant *identities)
+{
+ GVariantIter identity_iter;
+ GVariant * details_tmp;
+ const char * kind;
+ gs_free char *username_first = NULL;
+ gs_free char *username_root = NULL;
+ const char * user;
+
+ /* Choose identity. First try current user, then root, and else
+ * take the first one we find. */
+
+ user = getenv("USER");
+
+ g_variant_iter_init(&identity_iter, identities);
+ while (g_variant_iter_next(&identity_iter, "(&s@a{sv})", &kind, &details_tmp)) {
+ gs_unref_variant GVariant *details = g_steal_pointer(&details_tmp);
+
+ if (nm_streq(kind, "unix-user")) {
+ gs_unref_variant GVariant *v = NULL;
+
+ v = g_variant_lookup_value(details, "uid", G_VARIANT_TYPE_UINT32);
+ if (v) {
+ guint32 uid = g_variant_get_uint32(v);
+ gs_free char *u = NULL;
+ gboolean cached = FALSE;
+
+ if (user) {
+ if (!uid_to_name(uid, &cached, &u))
+ continue;
+ if (nm_streq(u, user))
+ return g_steal_pointer(&u);
+ }
+ if (!username_root && uid == 0) {
+ if (!uid_to_name(uid, &cached, &u))
+ continue;
+ username_root = g_strdup(u);
+ if (!user)
+ break;
+ }
+ if (!username_root && !username_first) {
+ if (!uid_to_name(uid, &cached, &u))
+ continue;
+ username_first = g_strdup(u);
+ }
+ }
+ }
+ }
+
+ if (username_root)
+ return g_steal_pointer(&username_root);
+
+ if (username_first)
+ return g_steal_pointer(&username_first);
+
+ return NULL;
+}
+
+static void
+agent_register_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ NMPolkitListener *listener = NM_POLKIT_LISTENER(user_data);
+ GDBusConnection * dbus_connection = G_DBUS_CONNECTION(source_object);
+ gs_free_error GError *error = NULL;
+ gs_unref_variant GVariant *ret = NULL;
+
+ ret = g_dbus_connection_call_finish(dbus_connection, res, &error);
+
+ if (nm_utils_error_is_cancelled(error)) {
+ return;
+ }
+
+ if (ret) {
+ g_signal_emit(listener, signals[REGISTERED], 0);
+ } else {
+ g_signal_emit(listener, signals[ERROR], 0, error->message);
+ }
+}
+
+static void
+agent_register(NMPolkitListener *self, const char *session_id)
+{
+ const char * locale = NULL;
+ gs_unref_object NMAuthSubject *subject = NULL;
+ GVariant * subject_variant = NULL;
+
+ locale = g_getenv("LANG");
+ if (locale == NULL) {
+ locale = "en_US.UTF-8";
+ }
+
+ if (self->session_agent) {
+ subject = nm_auth_subject_new_unix_session(session_id);
+ } else {
+ subject = nm_auth_subject_new_unix_process_self();
+ }
+ subject_variant = nm_auth_subject_unix_to_polkit_gvariant(subject);
+
+ g_dbus_connection_call(
+ self->dbus_connection,
+ self->name_owner,
+ POLKIT_AUTHORITY_OBJ_PATH,
+ POLKIT_AUTHORITY_IFACE_NAME,
+ "RegisterAuthenticationAgent",
+ g_variant_new("(@(sa{sv})ss)", subject_variant, locale, POLKIT_AGENT_OBJ_PATH),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ self->cancellable,
+ agent_register_cb,
+ self);
+}
+
+static void
+agent_unregister(NMPolkitListener *self)
+{
+ gs_unref_object NMAuthSubject *subject = NULL;
+ GVariant * subject_variant = NULL;
+
+ subject = nm_auth_subject_new_unix_process_self();
+ subject_variant = nm_auth_subject_unix_to_polkit_gvariant(subject);
+
+ g_dbus_connection_call(self->dbus_connection,
+ self->name_owner,
+ POLKIT_AUTHORITY_OBJ_PATH,
+ POLKIT_AUTHORITY_IFACE_NAME,
+ "UnregisterAuthenticationAgent",
+ g_variant_new("(@(sa{sv})s)", subject_variant, POLKIT_AGENT_OBJ_PATH),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL,
+ self);
+}
+
+static void
+retrieve_session_id_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ NMPolkitListener * listener = NM_POLKIT_LISTENER(user_data);
+ char * session_id;
+ guint32 session_uid;
+ nm_auto_free_variant_iter GVariantIter *iter = NULL;
+ gs_unref_variant GVariant *ret = NULL;
+ gs_free_error GError *error = NULL;
+ gs_free char * err_str = NULL;
+ uid_t uid = getuid();
+
+ ret = g_dbus_connection_call_finish(listener->dbus_connection, res, &error);
+
+ if (nm_utils_error_is_cancelled(error)) {
+ return;
+ }
+
+ if (ret) {
+ g_variant_get_child(ret, 0, "a(susso)", &iter);
+
+ while (
+ g_variant_iter_next(iter, "(&su@s@s@o)", &session_id, &session_uid, NULL, NULL, NULL)) {
+ if (session_uid == uid) {
+ agent_register(listener, session_id);
+ return;
+ }
+ }
+ err_str = g_strdup_printf(_("Could not find any session id for uid %d"), uid);
+ } else {
+ err_str = g_strdup_printf(_("Could not retrieve session id: %s"), error->message);
+ }
+
+ g_signal_emit(listener, signals[ERROR], 0, err_str);
+}
+
+static void
+retrieve_session_id(NMPolkitListener *self)
+{
+ g_dbus_connection_call(self->dbus_connection,
+ LOGIND_BUS_NAME,
+ LOGIND_OBJ_PATH,
+ LOGIND_MANAGER_INTERFACE,
+ "ListSessions",
+ NULL,
+ G_VARIANT_TYPE("(a(susso))"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ self->cancellable,
+ retrieve_session_id_cb,
+ self);
+}
+
+static gboolean
+io_watch_can_write(int fd, GIOCondition condition, gpointer user_data)
+{
+ AuthRequest *request = user_data;
+ gssize n_written;
+
+ if (NM_FLAGS_ANY(condition, (G_IO_HUP | G_IO_ERR)))
+ goto done;
+
+ n_written =
+ write(request->child_stdin,
+ &((nm_str_buf_get_str_unsafe(&request->out_buffer))[request->out_buffer_offset]),
+ request->out_buffer.len - request->out_buffer_offset);
+
+ if (n_written < 0 && errno != EAGAIN)
+ goto done;
+
+ if (n_written > 0) {
+ if ((gsize) n_written >= (request->out_buffer.len - request->out_buffer_offset)) {
+ nm_assert((gsize) n_written == (request->out_buffer.len - request->out_buffer_offset));
+ goto done;
+ }
+ request->out_buffer_offset += (gsize) n_written;
+ }
+
+ return G_SOURCE_CONTINUE;
+
+done:
+ nm_str_buf_set_size(&request->out_buffer, 0, TRUE, FALSE);
+ request->out_buffer_offset = 0;
+ nm_clear_g_source_inst(&request->child_stdin_watch_source);
+
+ if (request->request_is_completed)
+ auth_request_complete(request, TRUE);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+queue_string_to_helper(AuthRequest *request, const char *response)
+{
+ g_return_if_fail(response);
+
+ if (!nm_str_buf_is_initalized(&request->out_buffer))
+ nm_str_buf_init(&request->out_buffer, strlen(response) + 2u, TRUE);
+
+ nm_str_buf_append(&request->out_buffer, response);
+ nm_str_buf_ensure_trailing_c(&request->out_buffer, '\n');
+
+ if (!request->child_stdin_watch_source) {
+ request->child_stdin_watch_source = nm_g_unix_fd_source_new(request->child_stdin,
+ G_IO_OUT | G_IO_ERR | G_IO_HUP,
+ G_PRIORITY_DEFAULT,
+ io_watch_can_write,
+ request,
+ NULL);
+ g_source_attach(request->child_stdin_watch_source, request->listener->main_context);
+ }
+}
+
+static gboolean
+io_watch_have_data(int fd, GIOCondition condition, gpointer user_data)
+{
+ AuthRequest *request = user_data;
+ gboolean auth_result;
+ gssize n_read;
+
+ if (NM_FLAGS_ANY(condition, G_IO_HUP | G_IO_ERR))
+ n_read = -EIO;
+ else
+ n_read = nm_utils_fd_read(fd, &request->in_buffer);
+
+ if (n_read <= 0) {
+ if (n_read == -EAGAIN) {
+ /* wait longer. */
+ return G_SOURCE_CONTINUE;
+ }
+
+ /* Either an error or EOF happened. The data we parsed so far was not relevant.
+ * Regardless of what we still have unprocessed in the receive buffers, we are done.
+ *
+ * We would expect that the other side completed with SUCCESS or FAILURE. Apparently
+ * it didn't. If we had any good request, we assume success. */
+ auth_result = request->request_any_response;
+ goto out;
+ }
+
+ while (TRUE) {
+ char * line_terminator;
+ const char *line;
+
+ line = nm_str_buf_get_str(&request->in_buffer);
+ line_terminator = (char *) strchr(line, '\n');
+ if (!line_terminator) {
+ /* We don't have a complete line. Wait longer. */
+ return G_SOURCE_CONTINUE;
+ }
+ line_terminator[0] = '\0';
+
+ if (NM_STR_HAS_PREFIX(line, "PAM_PROMPT_ECHO_OFF ")
+ || NM_STR_HAS_PREFIX(line, "PAM_PROMPT_ECHO_ON ")) {
+ nm_auto_free_secret char *response = NULL;
+
+ /* FIXME(cli-async): emit signal and wait for response (blocking) */
+ g_signal_emit(request->listener,
+ signals[REQUEST_SYNC],
+ 0,
+ request->action_id,
+ request->message,
+ request->username,
+ &response);
+
+ if (response) {
+ queue_string_to_helper(request, response);
+ request->request_any_response = TRUE;
+ goto erase_line;
+ }
+ auth_result = FALSE;
+ } else if (nm_streq(line, "SUCCESS"))
+ auth_result = TRUE;
+ else if (nm_streq(line, "FAILURE"))
+ auth_result = FALSE;
+ else if (NM_STR_HAS_PREFIX(line, "PAM_ERROR_MSG ")
+ || NM_STR_HAS_PREFIX(line, "PAM_TEXT_INFO ")) {
+ /* ignore. */
+ goto erase_line;
+ } else {
+ /* unknown command. Fail. */
+ auth_result = FALSE;
+ }
+
+ break;
+erase_line:
+ nm_str_buf_erase(&request->in_buffer, 0, line_terminator - line + 1u, TRUE);
+ }
+
+out:
+ request->request_is_completed = TRUE;
+ nm_clear_g_source_inst(&request->child_stdout_watch_source);
+ if (auth_result && request->child_stdin_watch_source) {
+ /* we need to wait for the buffer to send the response. */
+ } else
+ auth_request_complete(request, auth_result);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+begin_authentication(AuthRequest *request)
+{
+ int fd_flags;
+ const char *helper_argv[] = {
+ POLKIT_AGENT_HELPER_1_PATH,
+ request->username,
+ NULL,
+ };
+
+ if (!request->username) {
+ auth_request_complete(request, FALSE);
+ return;
+ }
+
+ if (!g_spawn_async_with_pipes(NULL,
+ (char **) helper_argv,
+ NULL,
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ NULL,
+ NULL,
+ NULL,
+ &request->child_stdin,
+ &request->child_stdout,
+ NULL,
+ NULL)) {
+ /* not findind the PolicyKit setuid helper is a critical error */
+ request->child_stdin = -1;
+ request->child_stdout = -1;
+ g_signal_emit(request->listener,
+ signals[ERROR],
+ 0,
+ "The PolicyKit setuid helper 'polkit-agent-helper-1' has not been found");
+
+ auth_request_complete(request, FALSE);
+ return;
+ }
+
+ fd_flags = fcntl(request->child_stdin, F_GETFD, 0);
+ fcntl(request->child_stdin, F_SETFL, fd_flags | O_NONBLOCK);
+
+ fd_flags = fcntl(request->child_stdout, F_GETFD, 0);
+ fcntl(request->child_stdout, F_SETFL, fd_flags | O_NONBLOCK);
+
+ request->child_stdout_watch_source = nm_g_unix_fd_source_new(request->child_stdout,
+ G_IO_IN | G_IO_ERR | G_IO_HUP,
+ G_PRIORITY_DEFAULT,
+ io_watch_have_data,
+ request,
+ NULL);
+ g_source_attach(request->child_stdout_watch_source, request->listener->main_context);
+
+ /* Write the cookie on stdin so it can't be seen by other processes */
+ queue_string_to_helper(request, request->cookie);
+
+ return;
+}
+
+static AuthRequest *
+get_request(NMPolkitListener *listener, const char *cookie)
+{
+ AuthRequest *request;
+
+ c_list_for_each_entry (request, &listener->request_lst_head, request_lst) {
+ if (nm_streq0(cookie, request->cookie)) {
+ return request;
+ }
+ }
+ return NULL;
+}
+
+static AuthRequest *
+create_request(NMPolkitListener * listener,
+ GDBusMethodInvocation *invocation,
+ const char * action_id,
+ const char * message,
+ char * username_take,
+ const char * cookie)
+{
+ AuthRequest *request;
+
+ request = g_slice_new(AuthRequest);
+ *request = (AuthRequest){
+ .listener = listener,
+ .dbus_invocation = invocation,
+ .action_id = g_strdup(action_id),
+ .message = g_strdup(message),
+ .username = g_steal_pointer(&username_take),
+ .cookie = g_strdup(cookie),
+ .request_any_response = FALSE,
+ .request_is_completed = FALSE,
+ };
+
+ nm_str_buf_init(&request->in_buffer, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
+
+ c_list_link_tail(&listener->request_lst_head, &request->request_lst);
+ return request;
+}
+
+static void
+dbus_method_call_cb(GDBusConnection * connection,
+ const char * sender,
+ const char * object_path,
+ const char * interface_name,
+ const char * method_name,
+ GVariant * parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ NMPolkitListener *listener = NM_POLKIT_LISTENER(user_data);
+ const char * action_id;
+ const char * message;
+ const char * cookie;
+ AuthRequest * request;
+ gs_unref_variant GVariant *identities_gvariant = NULL;
+
+ if (nm_streq(method_name, "BeginAuthentication")) {
+ g_variant_get(parameters,
+ "(&s&s&s@a{ss}&s@a(sa{sv}))",
+ &action_id,
+ &message,
+ NULL,
+ NULL,
+ &cookie,
+ &identities_gvariant);
+
+ request = create_request(listener,
+ invocation,
+ action_id,
+ message,
+ choose_identity(identities_gvariant),
+ cookie);
+ begin_authentication(request);
+ return;
+ }
+
+ if (nm_streq(method_name, "CancelAuthentication")) {
+ g_variant_get(parameters, "&s", &cookie);
+ request = get_request(listener, cookie);
+
+ if (!request) {
+ gs_free char *msg = NULL;
+
+ msg = g_strdup_printf("No pending authentication request for cookie '%s'", cookie);
+ g_dbus_method_invocation_return_dbus_error(invocation, POLKIT_DBUS_ERROR_FAILED, msg);
+ return;
+ }
+
+ /* Complete a cancelled request with success. */
+ auth_request_complete(request, TRUE);
+ return;
+ }
+
+ g_dbus_method_invocation_return_error(invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_UNKNOWN_METHOD,
+ "Unknown method %s",
+ method_name);
+}
+
+static gboolean
+export_dbus_iface(NMPolkitListener *self, GError **error)
+{
+ GDBusInterfaceVTable interface_vtable = {
+ .method_call = dbus_method_call_cb,
+ .set_property = NULL,
+ .get_property = NULL,
+ };
+
+ g_return_val_if_fail(NM_IS_POLKIT_LISTENER(self), FALSE);
+ g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+
+ /* Agent listener iface has been exported already */
+ if (self->pk_auth_agent_reg_id) {
+ return TRUE;
+ }
+
+ self->pk_auth_agent_reg_id =
+ g_dbus_connection_register_object(self->dbus_connection,
+ POLKIT_AGENT_OBJ_PATH,
+ (GDBusInterfaceInfo *) &interface_info,
+ &interface_vtable,
+ self,
+ NULL,
+ error);
+ if (!self->pk_auth_agent_reg_id) {
+ g_signal_emit(self,
+ signals[ERROR],
+ 0,
+ "Could not register as a PolicyKit Authentication Agent");
+ }
+ return self->pk_auth_agent_reg_id;
+}
+
+static void
+name_owner_changed(NMPolkitListener *self, const char *name_owner)
+{
+ gs_free_error GError *error = NULL;
+
+ name_owner = nm_str_not_empty(name_owner);
+
+ if (nm_streq0(self->name_owner, name_owner)) {
+ return;
+ }
+
+ g_free(self->name_owner);
+ self->name_owner = g_strdup(name_owner);
+
+ if (!self->name_owner) {
+ return;
+ }
+
+ if (export_dbus_iface(self, &error)) {
+ if (self->session_agent) {
+ retrieve_session_id(self);
+ } else {
+ agent_register(self, NULL);
+ }
+ } else {
+ g_signal_emit(self,
+ signals[ERROR],
+ 0,
+ "Could not export the PolicyKit Authentication Agent DBus interface");
+ }
+}
+
+static void
+name_owner_changed_cb(GDBusConnection *connection,
+ const char * sender_name,
+ const char * object_path,
+ const char * interface_name,
+ const char * signal_name,
+ GVariant * parameters,
+ gpointer user_data)
+{
+ NMPolkitListener *self = user_data;
+ const char * new_owner;
+
+ if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) {
+ return;
+ }
+
+ g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner);
+
+ name_owner_changed(self, new_owner);
+}
+
+static void
+get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data)
+{
+ if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ return;
+ }
+ name_owner_changed(user_data, name_owner);
+}
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMPolkitListener, PROP_DBUS_CONNECTION, PROP_SESSION_AGENT, );
+
+static void
+set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ NMPolkitListener *self = NM_POLKIT_LISTENER(object);
+
+ switch (prop_id) {
+ case PROP_DBUS_CONNECTION:
+ self->dbus_connection = g_value_dup_object(value);
+ break;
+ case PROP_SESSION_AGENT:
+ self->session_agent = g_value_get_boolean(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+nm_polkit_listener_init(NMPolkitListener *self)
+{
+ c_list_init(&self->request_lst_head);
+ self->main_context = g_main_context_ref_thread_default();
+}
+
+static void
+constructed(GObject *object)
+{
+ NMPolkitListener *self = NM_POLKIT_LISTENER(object);
+
+ self->cancellable = g_cancellable_new();
+
+ self->name_owner_changed_id =
+ nm_dbus_connection_signal_subscribe_name_owner_changed(self->dbus_connection,
+ POLKIT_BUS_NAME,
+ name_owner_changed_cb,
+ self,
+ NULL);
+
+ nm_dbus_connection_call_get_name_owner(self->dbus_connection,
+ POLKIT_BUS_NAME,
+ -1,
+ self->cancellable,
+ get_name_owner_cb,
+ self);
+
+ G_OBJECT_CLASS(nm_polkit_listener_parent_class)->constructed(object);
+}
+
+/**
+ * nm_polkit_listener_new:
+ * @dbus_connection: a open DBus connection
+ * @session_agent: TRUE if a session agent is wanted, FALSE for a process agent
+ *
+ * Creates a new #NMPolkitListener and registers it as a polkit agent.
+ *
+ * Returns: a new #NMPolkitListener
+ */
+NMPolkitListener *
+nm_polkit_listener_new(GDBusConnection *dbus_connection, gboolean session_agent)
+{
+ return g_object_new(NM_TYPE_POLKIT_LISTENER,
+ NM_POLKIT_LISTENER_DBUS_CONNECTION,
+ dbus_connection,
+ NM_POLKIT_LISTENER_SESSION_AGENT,
+ session_agent,
+ NULL);
+}
+
+static void
+dispose(GObject *object)
+{
+ NMPolkitListener *self = NM_POLKIT_LISTENER(object);
+ AuthRequest * request;
+
+ nm_clear_g_cancellable(&self->cancellable);
+
+ while ((request = c_list_first_entry(&self->request_lst_head, AuthRequest, request_lst)))
+ auth_request_complete(request, FALSE);
+
+ if (self->dbus_connection) {
+ nm_clear_g_dbus_connection_signal(self->dbus_connection, &self->name_owner_changed_id);
+ g_dbus_connection_unregister_object(self->dbus_connection, self->pk_auth_agent_reg_id);
+ agent_unregister(self);
+ nm_clear_g_free(&self->name_owner);
+ g_clear_object(&self->dbus_connection);
+ }
+
+ nm_clear_pointer(&self->main_context, g_main_context_unref);
+
+ G_OBJECT_CLASS(nm_polkit_listener_parent_class)->dispose(object);
+}
+
+static void
+nm_polkit_listener_class_init(NMPolkitListenerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->set_property = set_property;
+ object_class->constructed = constructed;
+ object_class->dispose = dispose;
+
+ obj_properties[PROP_DBUS_CONNECTION] =
+ g_param_spec_object(NM_POLKIT_LISTENER_DBUS_CONNECTION,
+ "",
+ "",
+ G_TYPE_DBUS_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_SESSION_AGENT] =
+ g_param_spec_boolean(NM_POLKIT_LISTENER_SESSION_AGENT,
+ "",
+ "",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+ signals[REQUEST_SYNC] = g_signal_new(NM_POLKIT_LISTENER_SIGNAL_REQUEST_SYNC,
+ NM_TYPE_POLKIT_LISTENER,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_STRING,
+ 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ signals[REGISTERED] = g_signal_new(NM_POLKIT_LISTENER_SIGNAL_REGISTERED,
+ NM_TYPE_POLKIT_LISTENER,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+
+ signals[ERROR] = g_signal_new(NM_POLKIT_LISTENER_SIGNAL_ERROR,
+ NM_TYPE_POLKIT_LISTENER,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING);
+}
diff --git a/src/libnmc-base/nm-polkit-listener.h b/src/libnmc-base/nm-polkit-listener.h
new file mode 100644
index 0000000000..8a4c6c38d7
--- /dev/null
+++ b/src/libnmc-base/nm-polkit-listener.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#ifndef __NM_POLKIT_LISTENER_H__
+#define __NM_POLKIT_LISTENER_H__
+
+#define NM_POLKIT_LISTENER_SIGNAL_REGISTERED "registered"
+#define NM_POLKIT_LISTENER_SIGNAL_REQUEST_SYNC "request-sync"
+#define NM_POLKIT_LISTENER_SIGNAL_AUTH_SUCCESS "auth-success"
+#define NM_POLKIT_LISTENER_SIGNAL_AUTH_FAILURE "auth-failure"
+#define NM_POLKIT_LISTENER_SIGNAL_ERROR "error"
+
+typedef struct _NMPolkitListener NMPolkitListener;
+typedef struct _NMPolkitListenerClass NMPolkitListenerClass;
+
+#define NM_TYPE_POLKIT_LISTENER (nm_polkit_listener_get_type())
+#define NM_POLKIT_LISTENER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_POLKIT_LISTENER, NMPolkitListener))
+#define NM_POLKIT_LISTENER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_POLKIT_LISTENER, NMPolkitListenerClass))
+#define NM_IS_POLKIT_LISTENER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_POLKIT_LISTENER))
+#define NM_IS_POLKIT_LISTENER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_POLKIT_LISTENER))
+#define NM_POLKIT_LISTENER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_POLKIT_LISTENER, NMPolkitListenerClass))
+
+GType nm_polkit_listener_get_type(void);
+
+NMPolkitListener *nm_polkit_listener_new(GDBusConnection *dbus_connection, gboolean session_agent);
+
+#endif /* __NM_POLKIT_LISTENER_H__ */
diff --git a/src/libnmc-base/nm-secret-agent-simple.c b/src/libnmc-base/nm-secret-agent-simple.c
new file mode 100644
index 0000000000..69617d0fee
--- /dev/null
+++ b/src/libnmc-base/nm-secret-agent-simple.c
@@ -0,0 +1,1402 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2011 - 2015 Red Hat, Inc.
+ * Copyright (C) 2011 Giovanni Campagna <scampa.giovanni@gmail.com>
+ */
+
+/**
+ * SECTION:nm-secret-agent-simple
+ * @short_description: A simple secret agent for NetworkManager
+ *
+ * #NMSecretAgentSimple is the secret agent used by nmtui-connect and nmcli.
+ *
+ * This is a stripped-down version of gnome-shell's ShellNetworkAgent,
+ * with bits of the corresponding JavaScript code squished down into
+ * it. It is intended to eventually be generic enough that it could
+ * replace ShellNetworkAgent.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nm-secret-agent-simple.h"
+
+#include <gio/gunixoutputstream.h>
+#include <gio/gunixinputstream.h>
+
+#include "nm-vpn-service-plugin.h"
+#include "nm-vpn-helpers.h"
+#include "libnm-glib-aux/nm-secret-utils.h"
+
+/*****************************************************************************/
+
+typedef struct {
+ char *request_id;
+
+ NMSecretAgentSimple *self;
+
+ NMConnection * connection;
+ const char * setting_name;
+ char ** hints;
+ NMSecretAgentOldGetSecretsFunc callback;
+ gpointer callback_data;
+ GCancellable * cancellable;
+ NMSecretAgentGetSecretsFlags flags;
+} RequestData;
+
+enum {
+ REQUEST_SECRETS,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+typedef struct {
+ GHashTable *requests;
+
+ char * path;
+ gboolean enabled;
+} NMSecretAgentSimplePrivate;
+
+struct _NMSecretAgentSimple {
+ NMSecretAgentOld parent;
+ NMSecretAgentSimplePrivate _priv;
+};
+
+struct _NMSecretAgentSimpleClass {
+ NMSecretAgentOldClass parent;
+};
+
+G_DEFINE_TYPE(NMSecretAgentSimple, nm_secret_agent_simple, NM_TYPE_SECRET_AGENT_OLD)
+
+#define NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self) \
+ _NM_GET_PRIVATE(self, NMSecretAgentSimple, NM_IS_SECRET_AGENT_SIMPLE, NMSecretAgentOld)
+
+/*****************************************************************************/
+
+static void
+_request_data_free(gpointer data)
+{
+ RequestData *request = data;
+
+ g_free(request->request_id);
+ nm_clear_g_cancellable(&request->cancellable);
+ g_object_unref(request->connection);
+ g_strfreev(request->hints);
+
+ g_slice_free(RequestData, request);
+}
+
+static void
+_request_data_complete(RequestData * request,
+ GVariant * secrets,
+ GError * error,
+ GHashTableIter *iter_to_remove)
+{
+ NMSecretAgentSimple * self = request->self;
+ NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self);
+
+ nm_assert((secrets != NULL) != (error != NULL));
+
+ request->callback(NM_SECRET_AGENT_OLD(request->self),
+ request->connection,
+ secrets,
+ error,
+ request->callback_data);
+
+ if (iter_to_remove)
+ g_hash_table_iter_remove(iter_to_remove);
+ else
+ g_hash_table_remove(priv->requests, request);
+}
+
+/*****************************************************************************/
+
+/**
+ * NMSecretAgentSimpleSecret:
+ * @name: the user-visible name of the secret. Eg, "WEP Passphrase".
+ * @value: the value of the secret
+ * @password: %TRUE if this secret represents a password, %FALSE
+ * if it represents non-secret data.
+ *
+ * A single "secret" being requested.
+ */
+
+typedef struct {
+ NMSecretAgentSimpleSecret base;
+ NMSetting * setting;
+ char * property;
+} SecretReal;
+
+static void
+_secret_real_free(NMSecretAgentSimpleSecret *secret)
+{
+ SecretReal *real = (SecretReal *) secret;
+
+ g_free((char *) secret->pretty_name);
+ g_free((char *) secret->entry_id);
+ nm_free_secret(secret->value);
+ g_free((char *) secret->vpn_type);
+ g_free(real->property);
+ g_clear_object(&real->setting);
+
+ g_slice_free(SecretReal, real);
+}
+
+static NMSecretAgentSimpleSecret *
+_secret_real_new_plain(NMSecretAgentSecretType secret_type,
+ const char * pretty_name,
+ NMSetting * setting,
+ const char * property)
+{
+ SecretReal * real;
+ gs_free char *value = NULL;
+
+ nm_assert(property);
+ nm_assert(NM_IS_SETTING(setting));
+ nm_assert(NM_IN_SET(secret_type,
+ NM_SECRET_AGENT_SECRET_TYPE_PROPERTY,
+ NM_SECRET_AGENT_SECRET_TYPE_SECRET));
+ nm_assert(g_object_class_find_property(G_OBJECT_GET_CLASS(setting), property));
+ nm_assert((secret_type == NM_SECRET_AGENT_SECRET_TYPE_SECRET)
+ == nm_setting_get_secret_flags(setting, property, NULL, NULL));
+
+ g_object_get(setting, property, &value, NULL);
+
+ real = g_slice_new(SecretReal);
+ *real = (SecretReal){
+ .base.secret_type = secret_type,
+ .base.pretty_name = g_strdup(pretty_name),
+ .base.entry_id = g_strdup_printf("%s.%s", nm_setting_get_name(setting), property),
+ .base.value = g_steal_pointer(&value),
+ .base.is_secret = (secret_type != NM_SECRET_AGENT_SECRET_TYPE_PROPERTY),
+ .setting = g_object_ref(setting),
+ .property = g_strdup(property),
+ };
+ return &real->base;
+}
+
+static NMSecretAgentSimpleSecret *
+_secret_real_new_vpn_secret(const char *pretty_name,
+ NMSetting * setting,
+ const char *property,
+ const char *vpn_type)
+{
+ SecretReal *real;
+ const char *value;
+
+ nm_assert(property);
+ nm_assert(NM_IS_SETTING_VPN(setting));
+ nm_assert(vpn_type);
+
+ value = nm_setting_vpn_get_secret(NM_SETTING_VPN(setting), property);
+
+ real = g_slice_new(SecretReal);
+ *real = (SecretReal){
+ .base.secret_type = NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET,
+ .base.pretty_name = g_strdup(pretty_name),
+ .base.entry_id =
+ g_strdup_printf("%s%s", NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS, property),
+ .base.value = g_strdup(value),
+ .base.is_secret = TRUE,
+ .base.vpn_type = g_strdup(vpn_type),
+ .setting = g_object_ref(setting),
+ .property = g_strdup(property),
+ };
+ return &real->base;
+}
+
+static NMSecretAgentSimpleSecret *
+_secret_real_new_wireguard_peer_psk(NMSettingWireGuard *s_wg,
+ const char * public_key,
+ const char * preshared_key)
+{
+ SecretReal *real;
+
+ nm_assert(NM_IS_SETTING_WIREGUARD(s_wg));
+ nm_assert(public_key);
+
+ real = g_slice_new(SecretReal);
+ *real = (SecretReal){
+ .base.secret_type = NM_SECRET_AGENT_SECRET_TYPE_WIREGUARD_PEER_PSK,
+ .base.pretty_name = g_strdup_printf(_("Preshared-key for %s"), public_key),
+ .base.entry_id = g_strdup_printf(NM_SETTING_WIREGUARD_SETTING_NAME
+ "." NM_SETTING_WIREGUARD_PEERS
+ ".%s." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY,
+ public_key),
+ .base.value = g_strdup(preshared_key),
+ .base.is_secret = TRUE,
+ .base.no_prompt_entry_id = TRUE,
+ .setting = NM_SETTING(g_object_ref(s_wg)),
+ .property = g_strdup(public_key),
+ };
+ return &real->base;
+}
+
+/*****************************************************************************/
+
+static gboolean
+add_8021x_secrets(RequestData *request, GPtrArray *secrets)
+{
+ NMSetting8021x * s_8021x = nm_connection_get_setting_802_1x(request->connection);
+ const char * eap_method;
+ NMSecretAgentSimpleSecret *secret;
+
+ /* If hints are given, then always ask for what the hints require */
+ if (request->hints && request->hints[0]) {
+ char **iter;
+
+ for (iter = request->hints; *iter; iter++) {
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _(*iter),
+ NM_SETTING(s_8021x),
+ *iter);
+ g_ptr_array_add(secrets, secret);
+ }
+
+ return TRUE;
+ }
+
+ eap_method = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+ if (!eap_method)
+ return FALSE;
+
+ if (NM_IN_STRSET(eap_method, "md5", "leap", "ttls", "peap")) {
+ /* TTLS and PEAP are actually much more complicated, but this complication
+ * is not visible here since we only care about phase2 authentication
+ * (and don't even care of which one)
+ */
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_PROPERTY,
+ _("Username"),
+ NM_SETTING(s_8021x),
+ NM_SETTING_802_1X_IDENTITY);
+ g_ptr_array_add(secrets, secret);
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("Password"),
+ NM_SETTING(s_8021x),
+ NM_SETTING_802_1X_PASSWORD);
+ g_ptr_array_add(secrets, secret);
+ return TRUE;
+ }
+
+ if (nm_streq(eap_method, "tls")) {
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_PROPERTY,
+ _("Identity"),
+ NM_SETTING(s_8021x),
+ NM_SETTING_802_1X_IDENTITY);
+ g_ptr_array_add(secrets, secret);
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("Private key password"),
+ NM_SETTING(s_8021x),
+ NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD);
+ g_ptr_array_add(secrets, secret);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+add_wireless_secrets(RequestData *request, GPtrArray *secrets)
+{
+ NMSettingWirelessSecurity *s_wsec =
+ nm_connection_get_setting_wireless_security(request->connection);
+ const char * key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec);
+ NMSecretAgentSimpleSecret *secret;
+
+ if (!key_mgmt || nm_streq(key_mgmt, "owe"))
+ return FALSE;
+
+ if (NM_IN_STRSET(key_mgmt, "wpa-psk", "sae")) {
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("Password"),
+ NM_SETTING(s_wsec),
+ NM_SETTING_WIRELESS_SECURITY_PSK);
+ g_ptr_array_add(secrets, secret);
+ return TRUE;
+ }
+
+ if (nm_streq(key_mgmt, "none")) {
+ guint32 index;
+ char key[100];
+
+ index = nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec);
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("Key"),
+ NM_SETTING(s_wsec),
+ nm_sprintf_buf(key, "wep-key%u", (guint) index));
+ g_ptr_array_add(secrets, secret);
+ return TRUE;
+ }
+
+ if (nm_streq(key_mgmt, "iee8021x")) {
+ if (nm_streq0(nm_setting_wireless_security_get_auth_alg(s_wsec), "leap")) {
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("Password"),
+ NM_SETTING(s_wsec),
+ NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD);
+ g_ptr_array_add(secrets, secret);
+ return TRUE;
+ } else
+ return add_8021x_secrets(request, secrets);
+ }
+
+ if (nm_streq(key_mgmt, "wpa-eap") || nm_streq(key_mgmt, "wpa-eap-suite-b-192"))
+ return add_8021x_secrets(request, secrets);
+
+ return FALSE;
+}
+
+static gboolean
+add_pppoe_secrets(RequestData *request, GPtrArray *secrets)
+{
+ NMSettingPppoe * s_pppoe = nm_connection_get_setting_pppoe(request->connection);
+ NMSecretAgentSimpleSecret *secret;
+
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_PROPERTY,
+ _("Username"),
+ NM_SETTING(s_pppoe),
+ NM_SETTING_PPPOE_USERNAME);
+ g_ptr_array_add(secrets, secret);
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_PROPERTY,
+ _("Service"),
+ NM_SETTING(s_pppoe),
+ NM_SETTING_PPPOE_SERVICE);
+ g_ptr_array_add(secrets, secret);
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("Password"),
+ NM_SETTING(s_pppoe),
+ NM_SETTING_PPPOE_PASSWORD);
+ g_ptr_array_add(secrets, secret);
+ return TRUE;
+}
+
+static NMSettingSecretFlags
+get_vpn_secret_flags(NMSettingVpn *s_vpn, const char *secret_name)
+{
+ NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
+ GHashTable * vpn_data;
+
+ g_object_get(s_vpn, NM_SETTING_VPN_DATA, &vpn_data, NULL);
+ nm_vpn_service_plugin_get_secret_flags(vpn_data, secret_name, &flags);
+ g_hash_table_unref(vpn_data);
+
+ return flags;
+}
+
+static void
+add_vpn_secret_helper(GPtrArray * secrets,
+ NMSettingVpn *s_vpn,
+ const char * name,
+ const char * ui_name)
+{
+ NMSecretAgentSimpleSecret *secret;
+ NMSettingSecretFlags flags;
+ int i;
+
+ flags = get_vpn_secret_flags(s_vpn, name);
+ if (flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED || flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) {
+ secret = _secret_real_new_vpn_secret(ui_name,
+ NM_SETTING(s_vpn),
+ name,
+ nm_setting_vpn_get_service_type(s_vpn));
+
+ /* Check for duplicates */
+ for (i = 0; i < secrets->len; i++) {
+ NMSecretAgentSimpleSecret *s = secrets->pdata[i];
+
+ if (s->secret_type == secret->secret_type && nm_streq0(s->vpn_type, secret->vpn_type)
+ && nm_streq0(s->entry_id, secret->entry_id)) {
+ _secret_real_free(secret);
+ return;
+ }
+ }
+
+ g_ptr_array_add(secrets, secret);
+ }
+}
+
+#define VPN_MSG_TAG "x-vpn-message:"
+
+static gboolean
+add_vpn_secrets(RequestData *request, GPtrArray *secrets, char **msg)
+{
+ NMSettingVpn * s_vpn = nm_connection_get_setting_vpn(request->connection);
+ const NmcVpnPasswordName *p;
+ const char * vpn_msg = NULL;
+ char ** iter;
+
+ /* If hints are given, then always ask for what the hints require */
+ if (request->hints) {
+ for (iter = request->hints; *iter; iter++) {
+ if (!vpn_msg && g_str_has_prefix(*iter, VPN_MSG_TAG))
+ vpn_msg = &(*iter)[NM_STRLEN(VPN_MSG_TAG)];
+ else
+ add_vpn_secret_helper(secrets, s_vpn, *iter, *iter);
+ }
+ }
+
+ NM_SET_OUT(msg, g_strdup(vpn_msg));
+
+ /* Now add what client thinks might be required, because hints may be empty or incomplete */
+ p = nm_vpn_get_secret_names(nm_setting_vpn_get_service_type(s_vpn));
+ while (p && p->name) {
+ add_vpn_secret_helper(secrets, s_vpn, p->name, _(p->ui_name));
+ p++;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+add_wireguard_secrets(RequestData *request, GPtrArray *secrets, char **msg, GError **error)
+{
+ NMSettingWireGuard * s_wg;
+ NMSecretAgentSimpleSecret *secret;
+ guint i;
+
+ s_wg = NM_SETTING_WIREGUARD(
+ nm_connection_get_setting(request->connection, NM_TYPE_SETTING_WIREGUARD));
+ if (!s_wg) {
+ g_set_error(error,
+ NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_FAILED,
+ "Cannot service a WireGuard secrets request %s for a connection without "
+ "WireGuard settings",
+ request->request_id);
+ return FALSE;
+ }
+
+ if (!request->hints || !request->hints[0]
+ || g_strv_contains(NM_CAST_STRV_CC(request->hints), NM_SETTING_WIREGUARD_PRIVATE_KEY)) {
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("WireGuard private-key"),
+ NM_SETTING(s_wg),
+ NM_SETTING_WIREGUARD_PRIVATE_KEY);
+ g_ptr_array_add(secrets, secret);
+ }
+
+ if (request->hints) {
+ for (i = 0; request->hints[i]; i++) {
+ NMWireGuardPeer *peer;
+ const char * name = request->hints[i];
+ gs_free char * public_key = NULL;
+
+ if (nm_streq(name, NM_SETTING_WIREGUARD_PRIVATE_KEY))
+ continue;
+
+ if (NM_STR_HAS_PREFIX(name, NM_SETTING_WIREGUARD_PEERS ".")) {
+ const char *tmp;
+
+ tmp = &name[NM_STRLEN(NM_SETTING_WIREGUARD_PEERS ".")];
+ if (NM_STR_HAS_SUFFIX(tmp, "." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) {
+ public_key = g_strndup(
+ tmp,
+ strlen(tmp) - NM_STRLEN("." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY));
+ }
+ }
+
+ if (!public_key)
+ continue;
+
+ peer = nm_setting_wireguard_get_peer_by_public_key(s_wg, public_key, NULL);
+
+ g_ptr_array_add(secrets,
+ _secret_real_new_wireguard_peer_psk(
+ s_wg,
+ (peer ? nm_wireguard_peer_get_public_key(peer) : public_key),
+ (peer ? nm_wireguard_peer_get_preshared_key(peer) : NULL)));
+ }
+ }
+
+ *msg = g_strdup_printf(_("Secrets are required to connect WireGuard VPN '%s'"),
+ nm_connection_get_id(request->connection));
+ return TRUE;
+}
+
+typedef struct {
+ GPid auth_dialog_pid;
+ GString * auth_dialog_response;
+ RequestData * request;
+ GPtrArray * secrets;
+ GCancellable * cancellable;
+ gulong cancellable_id;
+ guint child_watch_id;
+ GInputStream * input_stream;
+ GOutputStream *output_stream;
+ char read_buf[5];
+} AuthDialogData;
+
+static void
+_auth_dialog_data_free(AuthDialogData *data)
+{
+ nm_clear_g_signal_handler(data->cancellable, &data->cancellable_id);
+ g_clear_object(&data->cancellable);
+ nm_clear_g_source(&data->child_watch_id);
+ g_ptr_array_unref(data->secrets);
+ g_spawn_close_pid(data->auth_dialog_pid);
+ g_string_free(data->auth_dialog_response, TRUE);
+ g_object_unref(data->input_stream);
+ g_object_unref(data->output_stream);
+ g_slice_free(AuthDialogData, data);
+}
+
+static void
+_auth_dialog_exited(GPid pid, int status, gpointer user_data)
+{
+ AuthDialogData * data = user_data;
+ RequestData * request = data->request;
+ GPtrArray * secrets = data->secrets;
+ NMSettingVpn * s_vpn = nm_connection_get_setting_vpn(request->connection);
+ nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
+ gs_strfreev char ** groups = NULL;
+ gs_free char * title = NULL;
+ gs_free char * message = NULL;
+ int i;
+ gs_free_error GError *error = NULL;
+
+ data->child_watch_id = 0;
+
+ nm_clear_g_cancellable_disconnect(data->cancellable, &data->cancellable_id);
+
+ if (status != 0) {
+ g_set_error(&error,
+ NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_FAILED,
+ "Auth dialog failed with error code %d\n",
+ status);
+ goto out;
+ }
+
+ keyfile = g_key_file_new();
+ if (!g_key_file_load_from_data(keyfile,
+ data->auth_dialog_response->str,
+ data->auth_dialog_response->len,
+ G_KEY_FILE_NONE,
+ &error)) {
+ goto out;
+ }
+
+ groups = g_key_file_get_groups(keyfile, NULL);
+ if (!nm_streq0(groups[0], "VPN Plugin UI")) {
+ g_set_error(&error,
+ NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_FAILED,
+ "Expected [VPN Plugin UI] in auth dialog response");
+ goto out;
+ }
+
+ title = g_key_file_get_string(keyfile, "VPN Plugin UI", "Title", &error);
+ if (!title)
+ goto out;
+
+ message = g_key_file_get_string(keyfile, "VPN Plugin UI", "Description", &error);
+ if (!message)
+ goto out;
+
+ for (i = 1; groups[i]; i++) {
+ gs_free char *pretty_name = NULL;
+
+ if (!g_key_file_get_boolean(keyfile, groups[i], "IsSecret", NULL))
+ continue;
+ if (!g_key_file_get_boolean(keyfile, groups[i], "ShouldAsk", NULL))
+ continue;
+
+ pretty_name = g_key_file_get_string(keyfile, groups[i], "Label", NULL);
+ g_ptr_array_add(secrets,
+ _secret_real_new_vpn_secret(pretty_name,
+ NM_SETTING(s_vpn),
+ groups[i],
+ nm_setting_vpn_get_service_type(s_vpn)));
+ }
+
+out:
+ /* Try to fall back to the hardwired VPN support if the auth dialog fails.
+ * We may eventually get rid of the whole hardwired secrets handling at some point,
+ * when the auth helpers are goode enough.. */
+ if (error && add_vpn_secrets(request, secrets, &message)) {
+ g_clear_error(&error);
+ if (!message) {
+ message = g_strdup_printf(_("A password is required to connect to '%s'."),
+ nm_connection_get_id(request->connection));
+ }
+ }
+
+ if (error)
+ _request_data_complete(request, NULL, error, NULL);
+ else {
+ g_signal_emit(request->self,
+ signals[REQUEST_SECRETS],
+ 0,
+ request->request_id,
+ title,
+ message,
+ secrets);
+ }
+
+ _auth_dialog_data_free(data);
+}
+
+static void
+_request_cancelled(GObject *object, gpointer user_data)
+{
+ _auth_dialog_data_free(user_data);
+}
+
+static void
+_auth_dialog_read_done(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ GInputStream * auth_dialog_out = G_INPUT_STREAM(source_object);
+ AuthDialogData *data = user_data;
+ gssize read_size;
+ gs_free_error GError *error = NULL;
+
+ read_size = g_input_stream_read_finish(auth_dialog_out, res, &error);
+ switch (read_size) {
+ case -1:
+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ _request_data_complete(data->request, NULL, error, NULL);
+ _auth_dialog_data_free(data);
+ break;
+ case 0:
+ /* Done reading. Let's wait for the auth dialog to exit so that we're able to collect the status.
+ * Remember we can be cancelled in between. */
+ data->child_watch_id = g_child_watch_add(data->auth_dialog_pid, _auth_dialog_exited, data);
+ data->cancellable = g_object_ref(data->request->cancellable);
+ data->cancellable_id =
+ g_cancellable_connect(data->cancellable, G_CALLBACK(_request_cancelled), data, NULL);
+ break;
+ default:
+ g_string_append_len(data->auth_dialog_response, data->read_buf, read_size);
+ g_input_stream_read_async(auth_dialog_out,
+ data->read_buf,
+ sizeof(data->read_buf),
+ G_PRIORITY_DEFAULT,
+ NULL,
+ _auth_dialog_read_done,
+ data);
+ return;
+ }
+
+ g_input_stream_close(auth_dialog_out, NULL, NULL);
+}
+
+static void
+_auth_dialog_write_done(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ GOutputStream *auth_dialog_out = G_OUTPUT_STREAM(source_object);
+ _nm_unused gs_free char *auth_dialog_request_free = user_data;
+
+ /* We don't care about write errors. If there are any problems, the
+ * reader shall notice. */
+ g_output_stream_write_finish(auth_dialog_out, res, NULL);
+ g_output_stream_close(auth_dialog_out, NULL, NULL);
+}
+
+static void
+_add_to_string(GString *string, const char *key, const char *value)
+{
+ gs_strfreev char **lines = NULL;
+ int i;
+
+ lines = g_strsplit(value, "\n", -1);
+
+ g_string_append(string, key);
+ for (i = 0; lines[i]; i++) {
+ g_string_append_c(string, '=');
+ g_string_append(string, lines[i]);
+ g_string_append_c(string, '\n');
+ }
+}
+
+static void
+_add_data_item_to_string(const char *key, const char *value, gpointer user_data)
+{
+ GString *string = user_data;
+
+ _add_to_string(string, "DATA_KEY", key);
+ _add_to_string(string, "DATA_VAL", value);
+ g_string_append_c(string, '\n');
+}
+
+static void
+_add_secret_to_string(const char *key, const char *value, gpointer user_data)
+{
+ GString *string = user_data;
+
+ _add_to_string(string, "SECRET_KEY", key);
+ _add_to_string(string, "SECRET_VAL", value);
+ g_string_append_c(string, '\n');
+}
+
+static gboolean
+try_spawn_vpn_auth_helper(RequestData *request, GPtrArray *secrets)
+{
+ NMSettingVpn * s_vpn = nm_connection_get_setting_vpn(request->connection);
+ gs_unref_ptrarray GPtrArray *auth_dialog_argv = NULL;
+ NMVpnPluginInfo * plugin_info;
+ const char * s;
+ GPid auth_dialog_pid;
+ int auth_dialog_in_fd;
+ int auth_dialog_out_fd;
+ GOutputStream * auth_dialog_in;
+ GInputStream * auth_dialog_out;
+ GError * error = NULL;
+ GString * auth_dialog_request;
+ char * auth_dialog_request_str;
+ gsize auth_dialog_request_len;
+ AuthDialogData * data;
+ int i;
+
+ plugin_info = nm_vpn_plugin_info_list_find_by_service(nm_vpn_get_plugin_infos(),
+ nm_setting_vpn_get_service_type(s_vpn));
+ if (!plugin_info)
+ return FALSE;
+
+ s = nm_vpn_plugin_info_lookup_property(plugin_info, "GNOME", "supports-external-ui-mode");
+ if (!_nm_utils_ascii_str_to_bool(s, FALSE))
+ return FALSE;
+
+ auth_dialog_argv = g_ptr_array_new();
+
+ s = nm_vpn_plugin_info_lookup_property(plugin_info, "GNOME", "auth-dialog");
+ g_return_val_if_fail(s, FALSE);
+ g_ptr_array_add(auth_dialog_argv, (gpointer) s);
+
+ g_ptr_array_add(auth_dialog_argv, "-u");
+ g_ptr_array_add(auth_dialog_argv, (gpointer) nm_connection_get_uuid(request->connection));
+ g_ptr_array_add(auth_dialog_argv, "-n");
+ g_ptr_array_add(auth_dialog_argv, (gpointer) nm_connection_get_id(request->connection));
+ g_ptr_array_add(auth_dialog_argv, "-s");
+ g_ptr_array_add(auth_dialog_argv, (gpointer) nm_setting_vpn_get_service_type(s_vpn));
+ g_ptr_array_add(auth_dialog_argv, "--external-ui-mode");
+ g_ptr_array_add(auth_dialog_argv, "-i");
+
+ if (request->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW)
+ g_ptr_array_add(auth_dialog_argv, "-r");
+
+ s = nm_vpn_plugin_info_lookup_property(plugin_info, "GNOME", "supports-hints");
+ if (_nm_utils_ascii_str_to_bool(s, FALSE)) {
+ for (i = 0; request->hints[i]; i++) {
+ g_ptr_array_add(auth_dialog_argv, "-t");
+ g_ptr_array_add(auth_dialog_argv, request->hints[i]);
+ }
+ }
+
+ g_ptr_array_add(auth_dialog_argv, NULL);
+ if (!g_spawn_async_with_pipes(NULL,
+ (char **) auth_dialog_argv->pdata,
+ NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL,
+ NULL,
+ &auth_dialog_pid,
+ &auth_dialog_in_fd,
+ &auth_dialog_out_fd,
+ NULL,
+ &error)) {
+ g_warning("Failed to spawn the auth dialog%s\n", error->message);
+ return FALSE;
+ }
+
+ auth_dialog_in = g_unix_output_stream_new(auth_dialog_in_fd, TRUE);
+ auth_dialog_out = g_unix_input_stream_new(auth_dialog_out_fd, TRUE);
+
+ auth_dialog_request = g_string_new_len(NULL, 1024);
+ nm_setting_vpn_foreach_data_item(s_vpn, _add_data_item_to_string, auth_dialog_request);
+ nm_setting_vpn_foreach_secret(s_vpn, _add_secret_to_string, auth_dialog_request);
+ g_string_append(auth_dialog_request, "DONE\nQUIT\n");
+ auth_dialog_request_len = auth_dialog_request->len;
+ auth_dialog_request_str = g_string_free(auth_dialog_request, FALSE);
+
+ data = g_slice_new(AuthDialogData);
+ *data = (AuthDialogData){
+ .auth_dialog_response = g_string_new_len(NULL, sizeof(data->read_buf)),
+ .auth_dialog_pid = auth_dialog_pid,
+ .request = request,
+ .secrets = g_ptr_array_ref(secrets),
+ .input_stream = auth_dialog_out,
+ .output_stream = auth_dialog_in,
+ };
+
+ g_output_stream_write_async(auth_dialog_in,
+ auth_dialog_request_str,
+ auth_dialog_request_len,
+ G_PRIORITY_DEFAULT,
+ request->cancellable,
+ _auth_dialog_write_done,
+ auth_dialog_request_str);
+
+ g_input_stream_read_async(auth_dialog_out,
+ data->read_buf,
+ sizeof(data->read_buf),
+ G_PRIORITY_DEFAULT,
+ request->cancellable,
+ _auth_dialog_read_done,
+ data);
+
+ return TRUE;
+}
+
+static void
+request_secrets_from_ui(RequestData *request)
+{
+ gs_unref_ptrarray GPtrArray *secrets = NULL;
+ gs_free_error GError * error = NULL;
+ NMSecretAgentSimplePrivate *priv;
+ NMSecretAgentSimpleSecret * secret;
+ const char * title;
+ gs_free char * msg = NULL;
+
+ priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(request->self);
+ g_return_if_fail(priv->enabled);
+
+ /* We only handle requests for connection with @path if set. */
+ if (priv->path && !g_str_has_prefix(request->request_id, priv->path)) {
+ g_set_error(&error,
+ NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_FAILED,
+ "Request for %s secrets doesn't match path %s",
+ request->request_id,
+ priv->path);
+ goto out_fail_error;
+ }
+
+ secrets = g_ptr_array_new_with_free_func((GDestroyNotify) _secret_real_free);
+
+ if (nm_connection_is_type(request->connection, NM_SETTING_WIRELESS_SETTING_NAME)) {
+ NMSettingWireless *s_wireless;
+ GBytes * ssid;
+ char * ssid_utf8;
+
+ s_wireless = nm_connection_get_setting_wireless(request->connection);
+ ssid = nm_setting_wireless_get_ssid(s_wireless);
+ ssid_utf8 = nm_utils_ssid_to_utf8(g_bytes_get_data(ssid, NULL), g_bytes_get_size(ssid));
+
+ title = _("Authentication required by wireless network");
+ msg = g_strdup_printf(
+ _("Passwords or encryption keys are required to access the wireless network '%s'."),
+ ssid_utf8);
+
+ if (!add_wireless_secrets(request, secrets))
+ goto out_fail;
+ } else if (nm_connection_is_type(request->connection, NM_SETTING_WIRED_SETTING_NAME)) {
+ title = _("Wired 802.1X authentication");
+ msg = g_strdup_printf(_("Secrets are required to access the wired network '%s'"),
+ nm_connection_get_id(request->connection));
+
+ if (!add_8021x_secrets(request, secrets))
+ goto out_fail;
+ } else if (nm_connection_is_type(request->connection, NM_SETTING_PPPOE_SETTING_NAME)) {
+ title = _("DSL authentication");
+ msg = g_strdup_printf(_("Secrets are required for the DSL connection '%s'"),
+ nm_connection_get_id(request->connection));
+
+ if (!add_pppoe_secrets(request, secrets))
+ goto out_fail;
+ } else if (nm_connection_is_type(request->connection, NM_SETTING_GSM_SETTING_NAME)) {
+ NMSettingGsm *s_gsm = nm_connection_get_setting_gsm(request->connection);
+
+ if (g_strv_contains(NM_CAST_STRV_CC(request->hints), NM_SETTING_GSM_PIN)) {
+ title = _("PIN code required");
+ msg = g_strdup(_("PIN code is needed for the mobile broadband device"));
+
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("PIN"),
+ NM_SETTING(s_gsm),
+ NM_SETTING_GSM_PIN);
+ g_ptr_array_add(secrets, secret);
+ } else {
+ title = _("Mobile broadband network password");
+ msg = g_strdup_printf(_("A password is required to connect to '%s'."),
+ nm_connection_get_id(request->connection));
+
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("Password"),
+ NM_SETTING(s_gsm),
+ NM_SETTING_GSM_PASSWORD);
+ g_ptr_array_add(secrets, secret);
+ }
+ } else if (nm_connection_is_type(request->connection, NM_SETTING_MACSEC_SETTING_NAME)) {
+ NMSettingMacsec *s_macsec = nm_connection_get_setting_macsec(request->connection);
+
+ msg = g_strdup_printf(_("Secrets are required to access the MACsec network '%s'"),
+ nm_connection_get_id(request->connection));
+
+ if (nm_setting_macsec_get_mode(s_macsec) == NM_SETTING_MACSEC_MODE_PSK) {
+ title = _("MACsec PSK authentication");
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("MKA CAK"),
+ NM_SETTING(s_macsec),
+ NM_SETTING_MACSEC_MKA_CAK);
+ g_ptr_array_add(secrets, secret);
+ } else {
+ title = _("MACsec EAP authentication");
+ if (!add_8021x_secrets(request, secrets))
+ goto out_fail;
+ }
+ } else if (nm_connection_is_type(request->connection, NM_SETTING_WIREGUARD_SETTING_NAME)) {
+ title = _("WireGuard VPN secret");
+ if (!add_wireguard_secrets(request, secrets, &msg, &error))
+ goto out_fail_error;
+ } else if (nm_connection_is_type(request->connection, NM_SETTING_CDMA_SETTING_NAME)) {
+ NMSettingCdma *s_cdma = nm_connection_get_setting_cdma(request->connection);
+
+ title = _("Mobile broadband network password");
+ msg = g_strdup_printf(_("A password is required to connect to '%s'."),
+ nm_connection_get_id(request->connection));
+
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("Password"),
+ NM_SETTING(s_cdma),
+ NM_SETTING_CDMA_PASSWORD);
+ g_ptr_array_add(secrets, secret);
+ } else if (nm_connection_is_type(request->connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) {
+ NMSetting *setting = NULL;
+
+ setting = nm_connection_get_setting_by_name(request->connection,
+ NM_SETTING_BLUETOOTH_SETTING_NAME);
+ if (setting
+ && !nm_streq0(nm_setting_bluetooth_get_connection_type(NM_SETTING_BLUETOOTH(setting)),
+ NM_SETTING_BLUETOOTH_TYPE_NAP)) {
+ setting =
+ nm_connection_get_setting_by_name(request->connection, NM_SETTING_GSM_SETTING_NAME);
+ if (!setting)
+ setting = nm_connection_get_setting_by_name(request->connection,
+ NM_SETTING_CDMA_SETTING_NAME);
+ }
+
+ if (!setting)
+ goto out_fail;
+
+ title = _("Mobile broadband network password");
+ msg = g_strdup_printf(_("A password is required to connect to '%s'."),
+ nm_connection_get_id(request->connection));
+
+ secret = _secret_real_new_plain(NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("Password"),
+ setting,
+ "password");
+ g_ptr_array_add(secrets, secret);
+ } else if (nm_connection_is_type(request->connection, NM_SETTING_VPN_SETTING_NAME)) {
+ title = _("VPN password required");
+
+ if (try_spawn_vpn_auth_helper(request, secrets)) {
+ /* This will emit REQUEST_SECRETS when ready */
+ return;
+ }
+
+ if (!add_vpn_secrets(request, secrets, &msg))
+ goto out_fail;
+ if (!msg) {
+ msg = g_strdup_printf(_("A password is required to connect to '%s'."),
+ nm_connection_get_id(request->connection));
+ }
+ } else
+ goto out_fail;
+
+ if (secrets->len == 0)
+ goto out_fail;
+
+ g_signal_emit(request->self,
+ signals[REQUEST_SECRETS],
+ 0,
+ request->request_id,
+ title,
+ msg,
+ secrets);
+ return;
+
+out_fail:
+ g_set_error(&error,
+ NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_FAILED,
+ "Cannot service a secrets request %s for a %s connection",
+ request->request_id,
+ nm_connection_get_connection_type(request->connection));
+out_fail_error:
+ _request_data_complete(request, NULL, error, NULL);
+}
+
+static void
+get_secrets(NMSecretAgentOld * agent,
+ NMConnection * connection,
+ const char * connection_path,
+ const char * setting_name,
+ const char ** hints,
+ NMSecretAgentGetSecretsFlags flags,
+ NMSecretAgentOldGetSecretsFunc callback,
+ gpointer callback_data)
+{
+ NMSecretAgentSimple * self = NM_SECRET_AGENT_SIMPLE(agent);
+ NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self);
+ RequestData * request;
+ gs_free_error GError *error = NULL;
+ gs_free char * request_id = NULL;
+ const char * request_id_setting_name;
+
+ request_id = g_strdup_printf("%s/%s", connection_path, setting_name);
+
+ if (g_hash_table_contains(priv->requests, &request_id)) {
+ /* We already have a request pending for this (connection, setting) */
+ error = g_error_new(NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_FAILED,
+ "Request for %s secrets already pending",
+ request_id);
+ callback(agent, connection, NULL, error, callback_data);
+ return;
+ }
+
+ if (!(flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION)) {
+ /* We don't do stored passwords */
+ error = g_error_new(NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_NO_SECRETS,
+ "Stored passwords not supported");
+ callback(agent, connection, NULL, error, callback_data);
+ return;
+ }
+
+ nm_assert(g_str_has_suffix(request_id, setting_name));
+ request_id_setting_name = &request_id[strlen(request_id) - strlen(setting_name)];
+ nm_assert(nm_streq(request_id_setting_name, setting_name));
+
+ request = g_slice_new(RequestData);
+ *request = (RequestData){
+ .self = self,
+ .connection = g_object_ref(connection),
+ .setting_name = request_id_setting_name,
+ .hints = g_strdupv((char **) hints),
+ .callback = callback,
+ .callback_data = callback_data,
+ .request_id = g_steal_pointer(&request_id),
+ .flags = flags,
+ .cancellable = g_cancellable_new(),
+ };
+ g_hash_table_add(priv->requests, request);
+
+ if (priv->enabled)
+ request_secrets_from_ui(request);
+}
+
+/**
+ * nm_secret_agent_simple_response:
+ * @self: the #NMSecretAgentSimple
+ * @request_id: the request ID being responded to
+ * @secrets: (allow-none): the array of secrets, or %NULL
+ *
+ * Response to a #NMSecretAgentSimple::get-secrets signal.
+ *
+ * If the user provided secrets, the caller should set the
+ * corresponding <literal>value</literal> fields in the
+ * #NMSecretAgentSimpleSecrets (freeing any initial values they had), and
+ * pass the array to nm_secret_agent_simple_response(). If the user
+ * cancelled the request, @secrets should be NULL.
+ */
+void
+nm_secret_agent_simple_response(NMSecretAgentSimple *self,
+ const char * request_id,
+ GPtrArray * secrets)
+{
+ NMSecretAgentSimplePrivate *priv;
+ RequestData * request;
+ gs_unref_variant GVariant *secrets_dict = NULL;
+ gs_free_error GError *error = NULL;
+ int i;
+
+ g_return_if_fail(NM_IS_SECRET_AGENT_SIMPLE(self));
+
+ priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self);
+ request = g_hash_table_lookup(priv->requests, &request_id);
+ g_return_if_fail(request != NULL);
+
+ if (secrets) {
+ GVariantBuilder conn_builder, *setting_builder;
+ GVariantBuilder vpn_secrets_builder;
+ GVariantBuilder wg_secrets_builder;
+ GVariantBuilder wg_peer_builder;
+ GHashTable * settings;
+ GHashTableIter iter;
+ const char * name;
+ gboolean has_vpn = FALSE;
+ gboolean has_wg = FALSE;
+
+ settings = g_hash_table_new_full(nm_str_hash,
+ g_str_equal,
+ NULL,
+ (GDestroyNotify) g_variant_builder_unref);
+ for (i = 0; i < secrets->len; i++) {
+ SecretReal *secret = secrets->pdata[i];
+
+ setting_builder = g_hash_table_lookup(settings, nm_setting_get_name(secret->setting));
+ if (!setting_builder) {
+ setting_builder = g_variant_builder_new(NM_VARIANT_TYPE_SETTING);
+ g_hash_table_insert(settings,
+ (char *) nm_setting_get_name(secret->setting),
+ setting_builder);
+ }
+
+ switch (secret->base.secret_type) {
+ case NM_SECRET_AGENT_SECRET_TYPE_PROPERTY:
+ case NM_SECRET_AGENT_SECRET_TYPE_SECRET:
+ g_variant_builder_add(setting_builder,
+ "{sv}",
+ secret->property,
+ g_variant_new_string(secret->base.value));
+ break;
+ case NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET:
+ if (!has_vpn) {
+ g_variant_builder_init(&vpn_secrets_builder, G_VARIANT_TYPE("a{ss}"));
+ has_vpn = TRUE;
+ }
+ g_variant_builder_add(&vpn_secrets_builder,
+ "{ss}",
+ secret->property,
+ secret->base.value);
+ break;
+ case NM_SECRET_AGENT_SECRET_TYPE_WIREGUARD_PEER_PSK:
+ if (!has_wg) {
+ g_variant_builder_init(&wg_secrets_builder, G_VARIANT_TYPE("aa{sv}"));
+ has_wg = TRUE;
+ }
+ g_variant_builder_init(&wg_peer_builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&wg_peer_builder,
+ "{sv}",
+ NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY,
+ g_variant_new_string(secret->property));
+ g_variant_builder_add(&wg_peer_builder,
+ "{sv}",
+ NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY,
+ g_variant_new_string(secret->base.value));
+ g_variant_builder_add(&wg_secrets_builder, "a{sv}", &wg_peer_builder);
+ break;
+ }
+ }
+
+ if (has_vpn) {
+ g_variant_builder_add(setting_builder,
+ "{sv}",
+ "secrets",
+ g_variant_builder_end(&vpn_secrets_builder));
+ }
+
+ if (has_wg) {
+ g_variant_builder_add(setting_builder,
+ "{sv}",
+ NM_SETTING_WIREGUARD_PEERS,
+ g_variant_builder_end(&wg_secrets_builder));
+ }
+
+ g_variant_builder_init(&conn_builder, NM_VARIANT_TYPE_CONNECTION);
+ g_hash_table_iter_init(&iter, settings);
+ while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &setting_builder))
+ g_variant_builder_add(&conn_builder, "{sa{sv}}", name, setting_builder);
+ secrets_dict = g_variant_ref_sink(g_variant_builder_end(&conn_builder));
+ g_hash_table_destroy(settings);
+ } else {
+ error = g_error_new(NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_USER_CANCELED,
+ "User cancelled");
+ }
+
+ _request_data_complete(request, secrets_dict, error, NULL);
+}
+
+static void
+cancel_get_secrets(NMSecretAgentOld *agent, const char *connection_path, const char *setting_name)
+{
+ NMSecretAgentSimple * self = NM_SECRET_AGENT_SIMPLE(agent);
+ NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self);
+ gs_free_error GError *error = NULL;
+ gs_free char * request_id = NULL;
+ RequestData * request;
+
+ request_id = g_strdup_printf("%s/%s", connection_path, setting_name);
+ request = g_hash_table_lookup(priv->requests, &request_id);
+ if (!request) {
+ /* this is really a bug of the caller (or us?). We cannot invoke a callback,
+ * hence the caller cannot cleanup the request. */
+ g_return_if_reached();
+ }
+
+ g_set_error(&error,
+ NM_SECRET_AGENT_ERROR,
+ NM_SECRET_AGENT_ERROR_AGENT_CANCELED,
+ "The secret agent is going away");
+ _request_data_complete(request, NULL, error, NULL);
+}
+
+static void
+save_secrets(NMSecretAgentOld * agent,
+ NMConnection * connection,
+ const char * connection_path,
+ NMSecretAgentOldSaveSecretsFunc callback,
+ gpointer callback_data)
+{
+ /* We don't support secret storage */
+ callback(agent, connection, NULL, callback_data);
+}
+
+static void
+delete_secrets(NMSecretAgentOld * agent,
+ NMConnection * connection,
+ const char * connection_path,
+ NMSecretAgentOldDeleteSecretsFunc callback,
+ gpointer callback_data)
+{
+ /* We don't support secret storage, so there's nothing to delete. */
+ callback(agent, connection, NULL, callback_data);
+}
+
+/**
+ * nm_secret_agent_simple_enable:
+ * @self: the #NMSecretAgentSimple
+ * @path: (allow-none): the path of the connection (if any) to handle secrets
+ * for. If %NULL, secrets for any connection will be handled.
+ *
+ * Enables servicing the requests including the already queued ones. If @path
+ * is given, the agent will only handle requests for connections that match
+ * @path.
+ */
+void
+nm_secret_agent_simple_enable(NMSecretAgentSimple *self, const char *path)
+{
+ NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(self);
+ gs_free RequestData **requests = NULL;
+ gsize i;
+ gs_free char * path_full = NULL;
+
+ /* The path is only used to match a request_id with the current
+ * connection. Since the request_id is "${CONNECTION_PATH}/${SETTING}",
+ * add a trailing '/' to the path to match the full connection path.
+ */
+ path_full = path ? g_strdup_printf("%s/", path) : NULL;
+
+ if (!nm_streq0(path_full, priv->path)) {
+ g_free(priv->path);
+ priv->path = g_steal_pointer(&path_full);
+ }
+
+ if (priv->enabled)
+ return;
+ priv->enabled = TRUE;
+
+ /* Service pending secret requests. */
+ requests = (RequestData **) g_hash_table_get_keys_as_array(priv->requests, NULL);
+ for (i = 0; requests[i]; i++)
+ request_secrets_from_ui(requests[i]);
+}
+
+/*****************************************************************************/
+
+static void
+nm_secret_agent_simple_init(NMSecretAgentSimple *agent)
+{
+ NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(agent);
+
+ G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(RequestData, request_id) == 0);
+ priv->requests = g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, NULL, _request_data_free);
+}
+
+/**
+ * nm_secret_agent_simple_new:
+ * @name: the identifier of secret agent
+ *
+ * Creates a new #NMSecretAgentSimple. It does not serve any requests until
+ * nm_secret_agent_simple_enable() is called.
+ *
+ * Returns: a new #NMSecretAgentSimple if the agent creation is successful
+ * or %NULL in case of a failure.
+ */
+NMSecretAgentSimple *
+nm_secret_agent_simple_new(const char *name)
+{
+ return g_initable_new(NM_TYPE_SECRET_AGENT_SIMPLE,
+ NULL,
+ NULL,
+ NM_SECRET_AGENT_OLD_IDENTIFIER,
+ name,
+ NM_SECRET_AGENT_OLD_CAPABILITIES,
+ NM_SECRET_AGENT_CAPABILITY_VPN_HINTS,
+ NULL);
+}
+
+static void
+dispose(GObject *object)
+{
+ NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(object);
+ gs_free_error GError *error = NULL;
+ GHashTableIter iter;
+ RequestData * request;
+
+ g_hash_table_iter_init(&iter, priv->requests);
+ while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &request)) {
+ if (!error)
+ nm_utils_error_set_cancelled(&error, TRUE, "NMSecretAgentSimple");
+ _request_data_complete(request, NULL, error, &iter);
+ }
+
+ G_OBJECT_CLASS(nm_secret_agent_simple_parent_class)->dispose(object);
+}
+
+static void
+finalize(GObject *object)
+{
+ NMSecretAgentSimplePrivate *priv = NM_SECRET_AGENT_SIMPLE_GET_PRIVATE(object);
+
+ g_hash_table_destroy(priv->requests);
+
+ g_free(priv->path);
+
+ G_OBJECT_CLASS(nm_secret_agent_simple_parent_class)->finalize(object);
+}
+
+void
+nm_secret_agent_simple_class_init(NMSecretAgentSimpleClass *klass)
+{
+ GObjectClass * object_class = G_OBJECT_CLASS(klass);
+ NMSecretAgentOldClass *agent_class = NM_SECRET_AGENT_OLD_CLASS(klass);
+
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ agent_class->get_secrets = get_secrets;
+ agent_class->cancel_get_secrets = cancel_get_secrets;
+ agent_class->save_secrets = save_secrets;
+ agent_class->delete_secrets = delete_secrets;
+
+ /**
+ * NMSecretAgentSimple::request-secrets:
+ * @agent: the #NMSecretAgentSimple
+ * @request_id: request ID, to eventually pass to
+ * nm_secret_agent_simple_response().
+ * @title: a title for the password dialog
+ * @prompt: a prompt message for the password dialog
+ * @secrets: (element-type #NMSecretAgentSimpleSecret): array of secrets
+ * being requested.
+ *
+ * Emitted when the agent requires secrets from the user.
+ *
+ * The application should ask user for the secrets. For example,
+ * nmtui should create a password dialog (#NmtPasswordDialog)
+ * with the given title and prompt, and an entry for each
+ * element of @secrets. If any of the secrets already have a
+ * <literal>value</literal> filled in, the corresponding entry
+ * should be initialized to that value.
+ *
+ * When the dialog is complete, the app must call
+ * nm_secret_agent_simple_response() with the results.
+ */
+ signals[REQUEST_SECRETS] = g_signal_new(NM_SECRET_AGENT_SIMPLE_REQUEST_SECRETS,
+ G_TYPE_FROM_CLASS(klass),
+ 0,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 4,
+ G_TYPE_STRING, /* request_id */
+ G_TYPE_STRING, /* title */
+ G_TYPE_STRING, /* prompt */
+ G_TYPE_PTR_ARRAY);
+}
diff --git a/src/libnmc-base/nm-secret-agent-simple.h b/src/libnmc-base/nm-secret-agent-simple.h
new file mode 100644
index 0000000000..878f9c75c0
--- /dev/null
+++ b/src/libnmc-base/nm-secret-agent-simple.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 - 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_SECRET_AGENT_SIMPLE_H__
+#define __NM_SECRET_AGENT_SIMPLE_H__
+
+#include "nm-secret-agent-old.h"
+
+typedef enum {
+ NM_SECRET_AGENT_SECRET_TYPE_PROPERTY,
+ NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET,
+ NM_SECRET_AGENT_SECRET_TYPE_WIREGUARD_PEER_PSK,
+} NMSecretAgentSecretType;
+
+typedef struct {
+ NMSecretAgentSecretType secret_type;
+ const char * pretty_name;
+ const char * entry_id;
+ char * value;
+ const char * vpn_type;
+ bool is_secret : 1;
+ bool no_prompt_entry_id : 1;
+} NMSecretAgentSimpleSecret;
+
+#define NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "vpn.secrets."
+
+#define NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT NM_DBUS_INTERFACE ".openconnect"
+
+/*****************************************************************************/
+
+#define NM_TYPE_SECRET_AGENT_SIMPLE (nm_secret_agent_simple_get_type())
+#define NM_SECRET_AGENT_SIMPLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SECRET_AGENT_SIMPLE, NMSecretAgentSimple))
+#define NM_SECRET_AGENT_SIMPLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SECRET_AGENT_SIMPLE, NMSecretAgentSimpleClass))
+#define NM_IS_SECRET_AGENT_SIMPLE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SECRET_AGENT_SIMPLE))
+#define NM_IS_SECRET_AGENT_SIMPLE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SECRET_AGENT_SIMPLE))
+#define NM_SECRET_AGENT_SIMPLE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SECRET_AGENT_SIMPLE, NMSecretAgentSimpleClass))
+
+#define NM_SECRET_AGENT_SIMPLE_REQUEST_SECRETS "request-secrets"
+
+typedef struct _NMSecretAgentSimple NMSecretAgentSimple;
+typedef struct _NMSecretAgentSimpleClass NMSecretAgentSimpleClass;
+
+GType nm_secret_agent_simple_get_type(void);
+
+NMSecretAgentSimple *nm_secret_agent_simple_new(const char *name);
+
+void nm_secret_agent_simple_response(NMSecretAgentSimple *self,
+ const char * request_id,
+ GPtrArray * secrets);
+
+void nm_secret_agent_simple_enable(NMSecretAgentSimple *self, const char *path);
+
+#endif /* __NM_SECRET_AGENT_SIMPLE_H__ */
diff --git a/src/libnmc-base/nm-vpn-helpers.c b/src/libnmc-base/nm-vpn-helpers.c
new file mode 100644
index 0000000000..72691e34c2
--- /dev/null
+++ b/src/libnmc-base/nm-vpn-helpers.c
@@ -0,0 +1,821 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 - 2015 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nm-vpn-helpers
+ * @short_description: VPN-related utilities
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nm-vpn-helpers.h"
+
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#include "nm-client-utils.h"
+#include "nm-utils.h"
+#include "libnm-glib-aux/nm-io-utils.h"
+#include "libnm-glib-aux/nm-secret-utils.h"
+
+/*****************************************************************************/
+
+NMVpnEditorPlugin *
+nm_vpn_get_editor_plugin(const char *service_type, GError **error)
+{
+ NMVpnEditorPlugin *plugin = NULL;
+ NMVpnPluginInfo * plugin_info;
+ gs_free_error GError *local = NULL;
+
+ g_return_val_if_fail(service_type, NULL);
+ g_return_val_if_fail(error == NULL || *error == NULL, NULL);
+
+ plugin_info = nm_vpn_plugin_info_list_find_by_service(nm_vpn_get_plugin_infos(), service_type);
+
+ if (!plugin_info) {
+ g_set_error(error,
+ NM_VPN_PLUGIN_ERROR,
+ NM_VPN_PLUGIN_ERROR_FAILED,
+ _("unknown VPN plugin \"%s\""),
+ service_type);
+ return NULL;
+ }
+ plugin = nm_vpn_plugin_info_get_editor_plugin(plugin_info);
+ if (!plugin)
+ plugin = nm_vpn_plugin_info_load_editor_plugin(plugin_info, &local);
+
+ if (!plugin) {
+ if (!nm_vpn_plugin_info_get_plugin(plugin_info)
+ && nm_vpn_plugin_info_lookup_property(plugin_info,
+ NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME,
+ "properties")) {
+ g_set_error(error,
+ NM_VPN_PLUGIN_ERROR,
+ NM_VPN_PLUGIN_ERROR_FAILED,
+ _("cannot load legacy-only VPN plugin \"%s\" for \"%s\""),
+ nm_vpn_plugin_info_get_name(plugin_info),
+ nm_vpn_plugin_info_get_filename(plugin_info));
+ } else if (g_error_matches(local, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
+ g_set_error(
+ error,
+ NM_VPN_PLUGIN_ERROR,
+ NM_VPN_PLUGIN_ERROR_FAILED,
+ _("cannot load VPN plugin \"%s\" due to missing \"%s\". Missing client plugin?"),
+ nm_vpn_plugin_info_get_name(plugin_info),
+ nm_vpn_plugin_info_get_plugin(plugin_info));
+ } else {
+ g_set_error(error,
+ NM_VPN_PLUGIN_ERROR,
+ NM_VPN_PLUGIN_ERROR_FAILED,
+ _("failed to load VPN plugin \"%s\": %s"),
+ nm_vpn_plugin_info_get_name(plugin_info),
+ local->message);
+ }
+ return NULL;
+ }
+
+ return plugin;
+}
+
+GSList *
+nm_vpn_get_plugin_infos(void)
+{
+ static bool plugins_loaded;
+ static GSList *plugins = NULL;
+
+ if (G_LIKELY(plugins_loaded))
+ return plugins;
+ plugins_loaded = TRUE;
+ plugins = nm_vpn_plugin_info_list_load();
+ return plugins;
+}
+
+gboolean
+nm_vpn_supports_ipv6(NMConnection *connection)
+{
+ NMSettingVpn * s_vpn;
+ const char * service_type;
+ NMVpnEditorPlugin *plugin;
+ guint32 capabilities;
+
+ s_vpn = nm_connection_get_setting_vpn(connection);
+ g_return_val_if_fail(s_vpn != NULL, FALSE);
+
+ service_type = nm_setting_vpn_get_service_type(s_vpn);
+ if (!service_type)
+ return FALSE;
+
+ plugin = nm_vpn_get_editor_plugin(service_type, NULL);
+ if (!plugin)
+ return FALSE;
+
+ capabilities = nm_vpn_editor_plugin_get_capabilities(plugin);
+ return NM_FLAGS_HAS(capabilities, NM_VPN_EDITOR_PLUGIN_CAPABILITY_IPV6);
+}
+
+const NmcVpnPasswordName *
+nm_vpn_get_secret_names(const char *service_type)
+{
+ const char *type;
+
+ if (!service_type)
+ return NULL;
+
+ if (!NM_STR_HAS_PREFIX(service_type, NM_DBUS_INTERFACE)
+ || service_type[NM_STRLEN(NM_DBUS_INTERFACE)] != '.') {
+ /* all our well-known, hard-coded vpn-types start with NM_DBUS_INTERFACE. */
+ return NULL;
+ }
+
+ type = service_type + (NM_STRLEN(NM_DBUS_INTERFACE) + 1);
+
+#define _VPN_PASSWORD_LIST(...) \
+ ({ \
+ static const NmcVpnPasswordName _arr[] = { \
+ __VA_ARGS__{0}, \
+ }; \
+ _arr; \
+ })
+
+ if (NM_IN_STRSET(type, "pptp", "iodine", "ssh", "l2tp", "fortisslvpn")) {
+ return _VPN_PASSWORD_LIST({"password", N_("Password")}, );
+ }
+
+ if (NM_IN_STRSET(type, "openvpn")) {
+ return _VPN_PASSWORD_LIST({"password", N_("Password")},
+ {"cert-pass", N_("Certificate password")},
+ {"http-proxy-password", N_("HTTP proxy password")}, );
+ }
+
+ if (NM_IN_STRSET(type, "vpnc")) {
+ return _VPN_PASSWORD_LIST({"Xauth password", N_("Password")},
+ {"IPSec secret", N_("Group password")}, );
+ };
+
+ if (NM_IN_STRSET(type, "openswan", "libreswan", "strongswan")) {
+ return _VPN_PASSWORD_LIST({"xauthpassword", N_("Password")},
+ {"pskvalue", N_("Group password")}, );
+ };
+
+ if (NM_IN_STRSET(type, "openconnect")) {
+ return _VPN_PASSWORD_LIST({"gateway", N_("Gateway")},
+ {"cookie", N_("Cookie")},
+ {"gwcert", N_("Gateway certificate hash")}, );
+ };
+
+ return NULL;
+}
+
+static gboolean
+_extract_variable_value(char *line, const char *tag, char **value)
+{
+ char *p1, *p2;
+
+ if (!g_str_has_prefix(line, tag))
+ return FALSE;
+
+ p1 = line + strlen(tag);
+ p2 = line + strlen(line) - 1;
+ if ((*p1 == '\'' || *p1 == '"') && (*p1 == *p2)) {
+ p1++;
+ *p2 = '\0';
+ }
+ NM_SET_OUT(value, g_strdup(p1));
+ return TRUE;
+}
+
+gboolean
+nm_vpn_openconnect_authenticate_helper(const char *host,
+ char ** cookie,
+ char ** gateway,
+ char ** gwcert,
+ int * status,
+ GError ** error)
+{
+ gs_free char * output = NULL;
+ gs_free const char **output_v = NULL;
+ const char *const * iter;
+ const char * path;
+ const char *const DEFAULT_PATHS[] = {
+ "/sbin/",
+ "/usr/sbin/",
+ "/usr/local/sbin/",
+ "/bin/",
+ "/usr/bin/",
+ "/usr/local/bin/",
+ NULL,
+ };
+
+ path = nm_utils_file_search_in_paths("openconnect",
+ "/usr/sbin/openconnect",
+ DEFAULT_PATHS,
+ G_FILE_TEST_IS_EXECUTABLE,
+ NULL,
+ NULL,
+ error);
+ if (!path)
+ return FALSE;
+
+ if (!g_spawn_sync(NULL,
+ (char **) NM_MAKE_STRV(path, "--authenticate", host),
+ NULL,
+ G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN,
+ NULL,
+ NULL,
+ &output,
+ NULL,
+ status,
+ error))
+ return FALSE;
+
+ /* Parse output and set cookie, gateway and gwcert
+ * output example:
+ * COOKIE='loremipsum'
+ * HOST='1.2.3.4'
+ * FINGERPRINT='sha1:32bac90cf09a722e10ecc1942c67fe2ac8c21e2e'
+ */
+ output_v = nm_utils_strsplit_set_with_empty(output, "\r\n");
+ for (iter = output_v; iter && *iter; iter++) {
+ char *s_mutable = (char *) *iter;
+
+ _extract_variable_value(s_mutable, "COOKIE=", cookie);
+ _extract_variable_value(s_mutable, "HOST=", gateway);
+ _extract_variable_value(s_mutable, "FINGERPRINT=", gwcert);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_wg_complete_peer(GPtrArray ** p_peers,
+ NMWireGuardPeer *peer_take,
+ gsize peer_start_line_nr,
+ const char * filename,
+ GError ** error)
+{
+ nm_auto_unref_wgpeer NMWireGuardPeer *peer = peer_take;
+ gs_free_error GError *local = NULL;
+
+ if (!peer)
+ return TRUE;
+
+ if (!nm_wireguard_peer_is_valid(peer, TRUE, TRUE, &local)) {
+ nm_utils_error_set(error,
+ NM_UTILS_ERROR_UNKNOWN,
+ _("Invalid peer starting at %s:%zu: %s"),
+ filename,
+ peer_start_line_nr,
+ local->message);
+ return FALSE;
+ }
+
+ if (!*p_peers)
+ *p_peers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_wireguard_peer_unref);
+ g_ptr_array_add(*p_peers, g_steal_pointer(&peer));
+ return TRUE;
+}
+
+static gboolean
+_line_match(char *line, const char *key, gsize key_len, const char **out_key, char **out_value)
+{
+ nm_assert(line);
+ nm_assert(key);
+ nm_assert(strlen(key) == key_len);
+ nm_assert(!strchr(key, '='));
+ nm_assert(out_key && !*out_key);
+ nm_assert(out_value && !*out_value);
+
+ /* Note that `wg-quick` (linux.bash) does case-insensitive comparison (shopt -s nocasematch).
+ * `wg setconf` does case-insensitive comparison too (with strncasecmp, which is locale dependent).
+ *
+ * We do a case-insensitive comparison of the key, however in a locale-independent manner. */
+
+ if (g_ascii_strncasecmp(line, key, key_len) != 0)
+ return FALSE;
+
+ if (line[key_len] != '=')
+ return FALSE;
+
+ *out_key = key;
+ *out_value = &line[key_len + 1];
+ return TRUE;
+}
+
+#define line_match(line, key, out_key, out_value) \
+ _line_match((line), "" key "", NM_STRLEN(key), (out_key), (out_value))
+
+static gboolean
+value_split_word(char **line_remainder, char **out_word)
+{
+ char *str;
+
+ if ((*line_remainder)[0] == '\0')
+ return FALSE;
+
+ *out_word = *line_remainder;
+
+ str = strchrnul(*line_remainder, ',');
+ if (str[0] == ',') {
+ str[0] = '\0';
+ *line_remainder = &str[1];
+ } else
+ *line_remainder = str;
+ return TRUE;
+}
+
+NMConnection *
+nm_vpn_wireguard_import(const char *filename, GError **error)
+{
+ nm_auto_clear_secret_ptr NMSecretPtr file_content = NM_SECRET_PTR_INIT();
+ char ifname[IFNAMSIZ];
+ gs_free char * uuid = NULL;
+ gboolean ifname_valid = FALSE;
+ const char * cstr;
+ char * line_remainder;
+ gs_unref_object NMConnection *connection = NULL;
+ NMSettingConnection * s_con;
+ NMSettingIPConfig * s_ip4;
+ NMSettingIPConfig * s_ip6;
+ NMSettingWireGuard * s_wg;
+ gs_free_error GError *local = NULL;
+ enum {
+ LINE_CONTEXT_INIT,
+ LINE_CONTEXT_INTERFACE,
+ LINE_CONTEXT_PEER,
+ } line_context;
+ gsize line_nr;
+ gsize current_peer_start_line_nr = 0;
+ nm_auto_unref_wgpeer NMWireGuardPeer *current_peer = NULL;
+ gs_unref_ptrarray GPtrArray *data_dns_search = NULL;
+ gs_unref_ptrarray GPtrArray *data_dns_v4 = NULL;
+ gs_unref_ptrarray GPtrArray *data_dns_v6 = NULL;
+ gs_unref_ptrarray GPtrArray *data_addr_v4 = NULL;
+ gs_unref_ptrarray GPtrArray *data_addr_v6 = NULL;
+ gs_unref_ptrarray GPtrArray *data_peers = NULL;
+ const char * data_private_key = NULL;
+ gint64 data_table;
+ guint data_listen_port = 0;
+ guint data_fwmark = 0;
+ guint data_mtu = 0;
+ int is_v4;
+ guint i;
+
+ g_return_val_if_fail(filename, NULL);
+ g_return_val_if_fail(!error || !*error, NULL);
+
+ /* contrary to "wg-quick", we never interpret the filename as "/etc/wireguard/$INTERFACE.conf".
+ * If the filename has no '/', it is interpreted as relative to the current working directory.
+ * However, we do require a suitable filename suffix and that the name corresponds to the interface
+ * name. */
+ cstr = strrchr(filename, '/');
+ cstr = cstr ? &cstr[1] : filename;
+ if (NM_STR_HAS_SUFFIX(cstr, ".conf")) {
+ gsize len = strlen(cstr) - NM_STRLEN(".conf");
+
+ if (len > 0 && len < sizeof(ifname)) {
+ memcpy(ifname, cstr, len);
+ ifname[len] = '\0';
+
+ if (nm_utils_ifname_valid(ifname, NMU_IFACE_KERNEL, NULL))
+ ifname_valid = TRUE;
+ }
+ }
+ if (!ifname_valid) {
+ nm_utils_error_set_literal(error,
+ NM_UTILS_ERROR_UNKNOWN,
+ _("The name of the WireGuard config must be a valid interface "
+ "name followed by \".conf\""));
+ return FALSE;
+ }
+
+ if (!nm_utils_file_get_contents(-1,
+ filename,
+ 10 * 1024 * 1024,
+ NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET,
+ &file_content.str,
+ &file_content.len,
+ NULL,
+ error))
+ return NULL;
+
+ /* We interpret the file like `wg-quick up` and `wg setconf` do.
+ *
+ * Of course the WireGuard scripts do something fundamentlly different. They
+ * perform actions to configure the WireGuard link in kernel, add routes and
+ * addresses, and call resolvconf. It all happens at the time when the script
+ * run.
+ *
+ * This code here instead generates a NetworkManager connection profile so that
+ * NetworkManager will apply a similar configuration when later activating the profile. */
+
+#define _TABLE_AUTO ((gint64) -1)
+#define _TABLE_OFF ((gint64) -2)
+
+ data_table = _TABLE_AUTO;
+
+ line_remainder = file_content.str;
+ line_context = LINE_CONTEXT_INIT;
+ line_nr = 0;
+ while (line_remainder[0] != '\0') {
+ const char *matched_key = NULL;
+ char * value = NULL;
+ char * line;
+ char ch;
+ gint64 i64;
+
+ line_nr++;
+
+ line = line_remainder;
+ line_remainder = strchrnul(line, '\n');
+ if (line_remainder[0] != '\0')
+ (line_remainder++)[0] = '\0';
+
+ /* Drop all spaces and truncate at first '#'.
+ * See wg's config_read_line().
+ *
+ * Note that wg-quick doesn't do that.
+ *
+ * Neither `wg setconf` nor `wg-quick` does a strict parsing.
+ * We don't either. Just try to interpret the file (mostly) the same as
+ * they would.
+ */
+ {
+ gsize l, n;
+
+ n = 0;
+ for (l = 0; (ch = line[l]); l++) {
+ if (g_ascii_isspace(ch)) {
+ /* wg-setconf strips all whitespace before parsing the content. That means,
+ * *[I nterface]" will be accepted. We do that too. */
+ continue;
+ }
+ if (ch == '#')
+ break;
+ line[n++] = line[l];
+ }
+ if (n == 0)
+ continue;
+ line[n] = '\0';
+ }
+
+ if (g_ascii_strcasecmp(line, "[Interface]") == 0) {
+ if (!_wg_complete_peer(&data_peers,
+ g_steal_pointer(&current_peer),
+ current_peer_start_line_nr,
+ filename,
+ error))
+ return FALSE;
+ line_context = LINE_CONTEXT_INTERFACE;
+ continue;
+ }
+
+ if (g_ascii_strcasecmp(line, "[Peer]") == 0) {
+ if (!_wg_complete_peer(&data_peers,
+ g_steal_pointer(&current_peer),
+ current_peer_start_line_nr,
+ filename,
+ error))
+ return FALSE;
+ current_peer_start_line_nr = line_nr;
+ current_peer = nm_wireguard_peer_new();
+ line_context = LINE_CONTEXT_PEER;
+ continue;
+ }
+
+ if (line_context == LINE_CONTEXT_INTERFACE) {
+ if (line_match(line, "Address", &matched_key, &value)) {
+ char *value_word;
+
+ while (value_split_word(&value, &value_word)) {
+ GPtrArray **p_data_addr;
+ NMIPAddr addr_bin;
+ int addr_family;
+ int prefix_len;
+
+ if (!nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC,
+ value_word,
+ &addr_family,
+ &addr_bin,
+ &prefix_len))
+ goto fail_invalid_value;
+
+ p_data_addr = (addr_family == AF_INET) ? &data_addr_v4 : &data_addr_v6;
+
+ if (!*p_data_addr)
+ *p_data_addr =
+ g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref);
+
+ g_ptr_array_add(
+ *p_data_addr,
+ nm_ip_address_new_binary(
+ addr_family,
+ &addr_bin,
+ prefix_len == -1 ? ((addr_family == AF_INET) ? 32 : 128) : prefix_len,
+ NULL));
+ }
+ continue;
+ }
+
+ if (line_match(line, "MTU", &matched_key, &value)) {
+ i64 = _nm_utils_ascii_str_to_int64(value, 0, 0, G_MAXUINT32, -1);
+ if (i64 == -1)
+ goto fail_invalid_value;
+
+ /* wg-quick accepts the "MTU" value, but it also fetches routes to
+ * autodetect it. NetworkManager won't do that, we can only configure
+ * an explicit MTU or no autodetection will be performed. */
+ data_mtu = i64;
+ continue;
+ }
+
+ if (line_match(line, "DNS", &matched_key, &value)) {
+ char *value_word;
+
+ while (value_split_word(&value, &value_word)) {
+ GPtrArray **p_data_dns;
+ NMIPAddr addr_bin;
+ int addr_family;
+
+ if (nm_utils_parse_inaddr_bin(AF_UNSPEC, value_word, &addr_family, &addr_bin)) {
+ p_data_dns = (addr_family == AF_INET) ? &data_dns_v4 : &data_dns_v6;
+ if (!*p_data_dns)
+ *p_data_dns = g_ptr_array_new_with_free_func(g_free);
+
+ g_ptr_array_add(*p_data_dns,
+ nm_utils_inet_ntop_dup(addr_family, &addr_bin));
+ continue;
+ }
+
+ if (!data_dns_search)
+ data_dns_search = g_ptr_array_new_with_free_func(g_free);
+ g_ptr_array_add(data_dns_search, g_strdup(value_word));
+ }
+ continue;
+ }
+
+ if (line_match(line, "Table", &matched_key, &value)) {
+ if (nm_streq(value, "auto"))
+ data_table = _TABLE_AUTO;
+ else if (nm_streq(value, "off"))
+ data_table = _TABLE_OFF;
+ else {
+ /* we don't support table names from /etc/iproute2/rt_tables
+ * But we accept hex like `ip route add` would. */
+ i64 = _nm_utils_ascii_str_to_int64(value, 0, 0, G_MAXINT32, -1);
+ if (i64 == -1)
+ goto fail_invalid_value;
+ data_table = i64;
+ }
+ continue;
+ }
+
+ if (line_match(line, "PreUp", &matched_key, &value)
+ || line_match(line, "PreDown", &matched_key, &value)
+ || line_match(line, "PostUp", &matched_key, &value)
+ || line_match(line, "PostDown", &matched_key, &value)) {
+ /* we don't run any scripts. Silently ignore these parameters. */
+ continue;
+ }
+
+ if (line_match(line, "SaveConfig", &matched_key, &value)) {
+ /* we ignore the setting, but enforce that it's either true or false (like
+ * wg-quick. */
+ if (!NM_IN_STRSET(value, "true", "false"))
+ goto fail_invalid_value;
+ continue;
+ }
+
+ if (line_match(line, "ListenPort", &matched_key, &value)) {
+ /* we don't use getaddrinfo(), unlike `wg setconf`. Just interpret
+ * the port as plain decimal number. */
+ i64 = _nm_utils_ascii_str_to_int64(value, 10, 0, 0xFFFF, -1);
+ if (i64 == -1)
+ goto fail_invalid_value;
+ data_listen_port = i64;
+ continue;
+ }
+
+ if (line_match(line, "FwMark", &matched_key, &value)) {
+ if (nm_streq(value, "off"))
+ data_fwmark = 0;
+ else {
+ i64 = _nm_utils_ascii_str_to_int64(value, 0, 0, G_MAXINT32, -1);
+ if (i64 == -1)
+ goto fail_invalid_value;
+ data_fwmark = i64;
+ }
+ continue;
+ }
+
+ if (line_match(line, "PrivateKey", &matched_key, &value)) {
+ if (!nm_utils_base64secret_decode(value, NM_WIREGUARD_PUBLIC_KEY_LEN, NULL))
+ goto fail_invalid_secret;
+ data_private_key = value;
+ continue;
+ }
+
+ goto fail_invalid_line;
+ }
+
+ if (line_context == LINE_CONTEXT_PEER) {
+ if (line_match(line, "Endpoint", &matched_key, &value)) {
+ if (!nm_wireguard_peer_set_endpoint(current_peer, value, FALSE))
+ goto fail_invalid_value;
+ continue;
+ }
+
+ if (line_match(line, "PublicKey", &matched_key, &value)) {
+ if (!nm_wireguard_peer_set_public_key(current_peer, value, FALSE))
+ goto fail_invalid_value;
+ continue;
+ }
+
+ if (line_match(line, "AllowedIPs", &matched_key, &value)) {
+ char *value_word;
+
+ while (value_split_word(&value, &value_word)) {
+ if (!nm_wireguard_peer_append_allowed_ip(current_peer, value_word, FALSE))
+ goto fail_invalid_value;
+ }
+ continue;
+ }
+
+ if (line_match(line, "PersistentKeepalive", &matched_key, &value)) {
+ if (nm_streq(value, "off"))
+ i64 = 0;
+ else {
+ i64 = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXUINT16, -1);
+ if (i64 == -1)
+ goto fail_invalid_value;
+ }
+ nm_wireguard_peer_set_persistent_keepalive(current_peer, i64);
+ continue;
+ }
+
+ if (line_match(line, "PresharedKey", &matched_key, &value)) {
+ if (!nm_wireguard_peer_set_preshared_key(current_peer, value, FALSE))
+ goto fail_invalid_secret;
+ nm_wireguard_peer_set_preshared_key_flags(current_peer,
+ NM_SETTING_SECRET_FLAG_NONE);
+ continue;
+ }
+
+ goto fail_invalid_line;
+ }
+
+fail_invalid_line:
+ nm_utils_error_set(error,
+ NM_UTILS_ERROR_INVALID_ARGUMENT,
+ _("unrecognized line at %s:%zu"),
+ filename,
+ line_nr);
+ return FALSE;
+fail_invalid_value:
+ nm_utils_error_set(error,
+ NM_UTILS_ERROR_INVALID_ARGUMENT,
+ _("invalid value for '%s' at %s:%zu"),
+ matched_key,
+ filename,
+ line_nr);
+ return FALSE;
+fail_invalid_secret:
+ nm_utils_error_set(error,
+ NM_UTILS_ERROR_INVALID_ARGUMENT,
+ _("invalid secret '%s' at %s:%zu"),
+ matched_key,
+ filename,
+ line_nr);
+ return FALSE;
+ }
+
+ if (!_wg_complete_peer(&data_peers,
+ g_steal_pointer(&current_peer),
+ current_peer_start_line_nr,
+ filename,
+ error))
+ return FALSE;
+
+ connection = nm_simple_connection_new();
+ s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+ nm_connection_add_setting(connection, NM_SETTING(s_con));
+ s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+ nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+ s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new());
+ nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+ s_wg = NM_SETTING_WIREGUARD(nm_setting_wireguard_new());
+ nm_connection_add_setting(connection, NM_SETTING(s_wg));
+
+ uuid = nm_utils_uuid_generate();
+
+ g_object_set(s_con,
+ NM_SETTING_CONNECTION_ID,
+ ifname,
+ NM_SETTING_CONNECTION_UUID,
+ uuid,
+ NM_SETTING_CONNECTION_TYPE,
+ NM_SETTING_WIREGUARD_SETTING_NAME,
+ NM_SETTING_CONNECTION_INTERFACE_NAME,
+ ifname,
+ NULL);
+
+ g_object_set(s_wg,
+ NM_SETTING_WIREGUARD_PRIVATE_KEY,
+ data_private_key,
+ NM_SETTING_WIREGUARD_LISTEN_PORT,
+ data_listen_port,
+ NM_SETTING_WIREGUARD_FWMARK,
+ data_fwmark,
+ NM_SETTING_WIREGUARD_MTU,
+ data_mtu,
+ NULL);
+
+ if (data_peers) {
+ for (i = 0; i < data_peers->len; i++)
+ nm_setting_wireguard_append_peer(s_wg, data_peers->pdata[i]);
+ }
+
+ for (is_v4 = 0; is_v4 < 2; is_v4++) {
+ const char *method_disabled =
+ is_v4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED : NM_SETTING_IP6_CONFIG_METHOD_DISABLED;
+ const char *method_manual =
+ is_v4 ? NM_SETTING_IP4_CONFIG_METHOD_MANUAL : NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
+ NMSettingIPConfig *s_ip = is_v4 ? s_ip4 : s_ip6;
+ GPtrArray * data_dns = is_v4 ? data_dns_v4 : data_dns_v6;
+ GPtrArray * data_addr = is_v4 ? data_addr_v4 : data_addr_v6;
+ GPtrArray * data_dns_search2 = data_dns_search;
+
+ if (data_dns && !data_addr) {
+ /* When specifying "DNS", we also require an "Address" for the same address
+ * family. That is because a NMSettingIPConfig cannot have @method_disabled
+ * and DNS settings at the same time.
+ *
+ * We don't have addresses. Silently ignore the DNS setting. */
+ data_dns = NULL;
+ data_dns_search2 = NULL;
+ }
+
+ g_object_set(s_ip,
+ NM_SETTING_IP_CONFIG_METHOD,
+ data_addr ? method_manual : method_disabled,
+ NULL);
+
+ /* For WireGuard profiles, always set dns-priority to a negative value,
+ * so that DNS servers on other profiles get ignored. This is also what
+ * wg-quick does, by calling `resolvconf -x`. */
+ g_object_set(s_ip, NM_SETTING_IP_CONFIG_DNS_PRIORITY, (int) -50, NULL);
+
+ if (data_addr) {
+ for (i = 0; i < data_addr->len; i++)
+ nm_setting_ip_config_add_address(s_ip, data_addr->pdata[i]);
+ }
+ if (data_dns) {
+ for (i = 0; i < data_dns->len; i++)
+ nm_setting_ip_config_add_dns(s_ip, data_dns->pdata[i]);
+
+ /* Of the wg-quick doesn't specify a search domain, assume the user
+ * wants to use the domain server for all searches. */
+ if (!data_dns_search2)
+ nm_setting_ip_config_add_dns_search(s_ip, "~");
+ }
+ if (data_dns_search2) {
+ for (i = 0; i < data_dns_search2->len; i++)
+ nm_setting_ip_config_add_dns_search(s_ip, data_dns_search2->pdata[i]);
+ }
+
+ if (data_table == _TABLE_AUTO) {
+ /* in the "auto" setting, wg-quick adds peer-routes automatically to the main
+ * table. NetworkManager will do that too, but there are differences:
+ *
+ * - NetworkManager (contrary to wg-quick) does not check whether the peer-route is necessary.
+ * It will always add a route for each allowed-ips range, even if there is already another
+ * route that would ensure packets to the endpoint are routed via the WireGuard interface.
+ * If you don't want that, disable "wireguard.peer-routes", and add the necessary routes
+ * yourself to "ipv4.routes" and "ipv6.routes".
+ *
+ * - With "auto", wg-quick also configures policy routing to handle default-routes (/0) to
+ * avoid routing loops.
+ * The imported connection profile will have wireguard.ip4-auto-default-route and
+ * wireguard.ip6-auto-default-route set to "default". It will thus configure wg-quick's
+ * policy routing if the profile has any AllowedIPs ranges with /0.
+ */
+ } else if (data_table == _TABLE_OFF) {
+ if (is_v4) {
+ g_object_set(s_wg, NM_SETTING_WIREGUARD_PEER_ROUTES, FALSE, NULL);
+ }
+ } else {
+ g_object_set(s_ip, NM_SETTING_IP_CONFIG_ROUTE_TABLE, (guint) data_table, NULL);
+ }
+ }
+
+ if (!nm_connection_normalize(connection, NULL, NULL, &local)) {
+ nm_utils_error_set(error,
+ NM_UTILS_ERROR_INVALID_ARGUMENT,
+ _("Failed to create WireGuard connection: %s"),
+ local->message);
+ return FALSE;
+ }
+
+ return g_steal_pointer(&connection);
+}
diff --git a/src/libnmc-base/nm-vpn-helpers.h b/src/libnmc-base/nm-vpn-helpers.h
new file mode 100644
index 0000000000..1cf06743df
--- /dev/null
+++ b/src/libnmc-base/nm-vpn-helpers.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 - 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_VPN_HELPERS_H__
+#define __NM_VPN_HELPERS_H__
+
+typedef struct {
+ const char *name;
+ const char *ui_name;
+} NmcVpnPasswordName;
+
+GSList *nm_vpn_get_plugin_infos(void);
+
+NMVpnEditorPlugin *nm_vpn_get_editor_plugin(const char *service_type, GError **error);
+
+gboolean nm_vpn_supports_ipv6(NMConnection *connection);
+
+const NmcVpnPasswordName *nm_vpn_get_secret_names(const char *service_type);
+
+gboolean nm_vpn_openconnect_authenticate_helper(const char *host,
+ char ** cookie,
+ char ** gateway,
+ char ** gwcert,
+ int * status,
+ GError ** error);
+
+NMConnection *nm_vpn_wireguard_import(const char *filename, GError **error);
+
+#endif /* __NM_VPN_HELPERS_H__ */
diff --git a/src/libnmc-base/qrcodegen.c b/src/libnmc-base/qrcodegen.c
new file mode 100644
index 0000000000..2c40fcb94a
--- /dev/null
+++ b/src/libnmc-base/qrcodegen.c
@@ -0,0 +1,1141 @@
+/*
+ * QR Code generator library (C)
+ *
+ * Copyright (c) Project Nayuki. (MIT License)
+ * https://www.nayuki.io/page/qr-code-generator-library
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ * - The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * - The Software is provided "as is", without warranty of any kind, express or
+ * implied, including but not limited to the warranties of merchantability,
+ * fitness for a particular purpose and noninfringement. In no event shall the
+ * authors or copyright holders be liable for any claim, damages or other
+ * liability, whether in an action of contract, tort or otherwise, arising from,
+ * out of or in connection with the Software or the use or other dealings in the
+ * Software.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include "qrcodegen.h"
+
+#ifndef QRCODEGEN_TEST
+ #define testable static // Keep functions private
+#else
+ #define testable // Expose private functions
+#endif
+
+/*---- Forward declarations for private functions ----*/
+
+// Regarding all public and private functions defined in this source file:
+// - They require all pointer/array arguments to be not null unless the array length is zero.
+// - They only read input scalar/array arguments, write to output pointer/array
+// arguments, and return scalar values; they are "pure" functions.
+// - They don't read mutable global variables or write to any global variables.
+// - They don't perform I/O, read the clock, print to console, etc.
+// - They allocate a small and constant amount of stack memory.
+// - They don't allocate or free any memory on the heap.
+// - They don't recurse or mutually recurse. All the code
+// could be inlined into the top-level public functions.
+// - They run in at most quadratic time with respect to input arguments.
+// Most functions run in linear time, and some in constant time.
+// There are no unbounded loops or non-obvious termination conditions.
+// - They are completely thread-safe if the caller does not give the
+// same writable buffer to concurrent calls to these functions.
+
+testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen);
+
+testable void
+addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]);
+testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl);
+testable int getNumRawDataModules(int ver);
+
+testable void calcReedSolomonGenerator(int degree, uint8_t result[]);
+testable void calcReedSolomonRemainder(const uint8_t data[],
+ int dataLen,
+ const uint8_t generator[],
+ int degree,
+ uint8_t result[]);
+testable uint8_t finiteFieldMultiply(uint8_t x, uint8_t y);
+
+testable void initializeFunctionModules(int version, uint8_t qrcode[]);
+static void drawWhiteFunctionModules(uint8_t qrcode[], int version);
+static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]);
+testable int getAlignmentPatternPositions(int version, uint8_t result[7]);
+static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]);
+
+static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]);
+static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask);
+static long getPenaltyScore(const uint8_t qrcode[]);
+static void addRunToHistory(unsigned char run, unsigned char history[7]);
+static bool hasFinderLikePattern(unsigned char runHistory[7]);
+
+testable bool getModule(const uint8_t qrcode[], int x, int y);
+testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack);
+testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack);
+static bool getBit(int x, int i);
+
+testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars);
+testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version);
+static int numCharCountBits(enum qrcodegen_Mode mode, int version);
+
+/*---- Private tables of constants ----*/
+
+// The set of all legal characters in alphanumeric mode, where each character
+// value maps to the index in the string. For checking text and encoding segments.
+static const char *ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
+
+// For generating error correction codes.
+testable const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = {
+ // Version: (note that index 0 is for padding, and is set to an illegal value)
+ //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
+ {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28,
+ 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low
+ {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26,
+ 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium
+ {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30,
+ 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile
+ {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28,
+ 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High
+};
+
+#define qrcodegen_REED_SOLOMON_DEGREE_MAX 30 // Based on the table above
+
+// For generating error correction codes.
+testable const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41] = {
+ // Version: (note that index 0 is for padding, and is set to an illegal value)
+ //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
+ {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8,
+ 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
+ {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16,
+ 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
+ {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20,
+ 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
+ {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25,
+ 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
+};
+
+// For automatic mask pattern selection.
+static const int PENALTY_N1 = 3;
+static const int PENALTY_N2 = 3;
+static const int PENALTY_N3 = 40;
+static const int PENALTY_N4 = 10;
+
+/*---- High-level QR Code encoding functions ----*/
+
+// Public function - see documentation comment in header file.
+bool
+qrcodegen_encodeText(const char * text,
+ uint8_t tempBuffer[],
+ uint8_t qrcode[],
+ enum qrcodegen_Ecc ecl,
+ int minVersion,
+ int maxVersion,
+ enum qrcodegen_Mask mask,
+ bool boostEcl)
+{
+ size_t textLen = strlen(text);
+ if (textLen == 0)
+ return qrcodegen_encodeSegmentsAdvanced(NULL,
+ 0,
+ ecl,
+ minVersion,
+ maxVersion,
+ mask,
+ boostEcl,
+ tempBuffer,
+ qrcode);
+ size_t bufLen = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion);
+
+ struct qrcodegen_Segment seg;
+ if (qrcodegen_isNumeric(text)) {
+ if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen)
+ goto fail;
+ seg = qrcodegen_makeNumeric(text, tempBuffer);
+ } else if (qrcodegen_isAlphanumeric(text)) {
+ if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_ALPHANUMERIC, textLen) > bufLen)
+ goto fail;
+ seg = qrcodegen_makeAlphanumeric(text, tempBuffer);
+ } else {
+ if (textLen > bufLen)
+ goto fail;
+ for (size_t i = 0; i < textLen; i++)
+ tempBuffer[i] = (uint8_t) text[i];
+ seg.mode = qrcodegen_Mode_BYTE;
+ seg.bitLength = calcSegmentBitLength(seg.mode, textLen);
+ if (seg.bitLength == -1)
+ goto fail;
+ seg.numChars = (int) textLen;
+ seg.data = tempBuffer;
+ }
+ return qrcodegen_encodeSegmentsAdvanced(&seg,
+ 1,
+ ecl,
+ minVersion,
+ maxVersion,
+ mask,
+ boostEcl,
+ tempBuffer,
+ qrcode);
+
+fail:
+ qrcode[0] = 0; // Set size to invalid value for safety
+ return false;
+}
+
+// Public function - see documentation comment in header file.
+bool
+qrcodegen_encodeBinary(uint8_t dataAndTemp[],
+ size_t dataLen,
+ uint8_t qrcode[],
+ enum qrcodegen_Ecc ecl,
+ int minVersion,
+ int maxVersion,
+ enum qrcodegen_Mask mask,
+ bool boostEcl)
+{
+ struct qrcodegen_Segment seg;
+ seg.mode = qrcodegen_Mode_BYTE;
+ seg.bitLength = calcSegmentBitLength(seg.mode, dataLen);
+ if (seg.bitLength == -1) {
+ qrcode[0] = 0; // Set size to invalid value for safety
+ return false;
+ }
+ seg.numChars = (int) dataLen;
+ seg.data = dataAndTemp;
+ return qrcodegen_encodeSegmentsAdvanced(&seg,
+ 1,
+ ecl,
+ minVersion,
+ maxVersion,
+ mask,
+ boostEcl,
+ dataAndTemp,
+ qrcode);
+}
+
+// Appends the given number of low-order bits of the given value to the given byte-based
+// bit buffer, increasing the bit length. Requires 0 <= numBits <= 16 and val < 2^numBits.
+testable void
+appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen)
+{
+ assert(0 <= numBits && numBits <= 16 && (unsigned long) val >> numBits == 0);
+ for (int i = numBits - 1; i >= 0; i--, (*bitLen)++)
+ buffer[*bitLen >> 3] |= ((val >> i) & 1) << (7 - (*bitLen & 7));
+}
+
+/*---- Low-level QR Code encoding functions ----*/
+
+// Public function - see documentation comment in header file.
+bool
+qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[],
+ size_t len,
+ enum qrcodegen_Ecc ecl,
+ uint8_t tempBuffer[],
+ uint8_t qrcode[])
+{
+ return qrcodegen_encodeSegmentsAdvanced(segs,
+ len,
+ ecl,
+ qrcodegen_VERSION_MIN,
+ qrcodegen_VERSION_MAX,
+ -1,
+ true,
+ tempBuffer,
+ qrcode);
+}
+
+// Public function - see documentation comment in header file.
+bool
+qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[],
+ size_t len,
+ enum qrcodegen_Ecc ecl,
+ int minVersion,
+ int maxVersion,
+ int mask,
+ bool boostEcl,
+ uint8_t tempBuffer[],
+ uint8_t qrcode[])
+{
+ assert(segs != NULL || len == 0);
+ assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion
+ && maxVersion <= qrcodegen_VERSION_MAX);
+ assert(0 <= (int) ecl && (int) ecl <= 3 && -1 <= (int) mask && (int) mask <= 7);
+
+ // Find the minimal version number to use
+ int version, dataUsedBits;
+ for (version = minVersion;; version++) {
+ int dataCapacityBits =
+ getNumDataCodewords(version, ecl) * 8; // Number of data bits available
+ dataUsedBits = getTotalBits(segs, len, version);
+ if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
+ break; // This version number is found to be suitable
+ if (version >= maxVersion) { // All versions in the range could not fit the given data
+ qrcode[0] = 0; // Set size to invalid value for safety
+ return false;
+ }
+ }
+ assert(dataUsedBits != -1);
+
+ // Increase the error correction level while the data still fits in the current version number
+ for (int i = (int) qrcodegen_Ecc_MEDIUM; i <= (int) qrcodegen_Ecc_HIGH;
+ i++) { // From low to high
+ if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc) i) * 8)
+ ecl = (enum qrcodegen_Ecc) i;
+ }
+
+ // Concatenate all segments to create the data bit string
+ memset(qrcode, 0, qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0]));
+ int bitLen = 0;
+ for (size_t i = 0; i < len; i++) {
+ const struct qrcodegen_Segment *seg = &segs[i];
+ appendBitsToBuffer((int) seg->mode, 4, qrcode, &bitLen);
+ appendBitsToBuffer(seg->numChars, numCharCountBits(seg->mode, version), qrcode, &bitLen);
+ for (int j = 0; j < seg->bitLength; j++)
+ appendBitsToBuffer((seg->data[j >> 3] >> (7 - (j & 7))) & 1, 1, qrcode, &bitLen);
+ }
+ assert(bitLen == dataUsedBits);
+
+ // Add terminator and pad up to a byte if applicable
+ int dataCapacityBits = getNumDataCodewords(version, ecl) * 8;
+ assert(bitLen <= dataCapacityBits);
+ int terminatorBits = dataCapacityBits - bitLen;
+ if (terminatorBits > 4)
+ terminatorBits = 4;
+ appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen);
+ appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen);
+ assert(bitLen % 8 == 0);
+
+ // Pad with alternating bytes until data capacity is reached
+ for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
+ appendBitsToBuffer(padByte, 8, qrcode, &bitLen);
+
+ // Draw function and data codeword modules
+ addEccAndInterleave(qrcode, version, ecl, tempBuffer);
+ initializeFunctionModules(version, qrcode);
+ drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode);
+ drawWhiteFunctionModules(qrcode, version);
+ initializeFunctionModules(version, tempBuffer);
+
+ // Handle masking
+ if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask
+ long minPenalty = LONG_MAX;
+ for (int i = 0; i < 8; i++) {
+ enum qrcodegen_Mask msk = (enum qrcodegen_Mask) i;
+ drawFormatBits(ecl, msk, qrcode);
+ applyMask(tempBuffer, qrcode, msk);
+ long penalty = getPenaltyScore(qrcode);
+ if (penalty < minPenalty) {
+ mask = msk;
+ minPenalty = penalty;
+ }
+ applyMask(tempBuffer, qrcode, msk); // Undoes the mask due to XOR
+ }
+ }
+ assert(0 <= (int) mask && (int) mask <= 7);
+ drawFormatBits(ecl, mask, qrcode);
+ applyMask(tempBuffer, qrcode, mask);
+ return true;
+}
+
+/*---- Error correction code generation functions ----*/
+
+// Appends error correction bytes to each block of the given data array, then interleaves
+// bytes from the blocks and stores them in the result array. data[0 : dataLen] contains
+// the input data. data[dataLen : rawCodewords] is used as a temporary work area and will
+// be clobbered by this function. The final answer is stored in result[0 : rawCodewords].
+testable void
+addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[])
+{
+ // Calculate parameter numbers
+ assert(0 <= (int) ecl && (int) ecl < 4 && qrcodegen_VERSION_MIN <= version
+ && version <= qrcodegen_VERSION_MAX);
+ int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int) ecl][version];
+ int blockEccLen = ECC_CODEWORDS_PER_BLOCK[(int) ecl][version];
+ int rawCodewords = getNumRawDataModules(version) / 8;
+ int dataLen = getNumDataCodewords(version, ecl);
+ int numShortBlocks = numBlocks - rawCodewords % numBlocks;
+ int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen;
+
+ // Split data into blocks, calculate ECC, and interleave
+ // (not concatenate) the bytes into a single sequence
+ uint8_t generator[qrcodegen_REED_SOLOMON_DEGREE_MAX];
+ calcReedSolomonGenerator(blockEccLen, generator);
+ const uint8_t *dat = data;
+ for (int i = 0; i < numBlocks; i++) {
+ int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1);
+ uint8_t *ecc = &data[dataLen]; // Temporary storage
+ calcReedSolomonRemainder(dat, datLen, generator, blockEccLen, ecc);
+ for (int j = 0, k = i; j < datLen; j++, k += numBlocks) { // Copy data
+ if (j == shortBlockDataLen)
+ k -= numShortBlocks;
+ result[k] = dat[j];
+ }
+ for (int j = 0, k = dataLen + i; j < blockEccLen; j++, k += numBlocks) // Copy ECC
+ result[k] = ecc[j];
+ dat += datLen;
+ }
+}
+
+// Returns the number of 8-bit codewords that can be used for storing data (not ECC),
+// for the given version number and error correction level. The result is in the range [9, 2956].
+testable int
+getNumDataCodewords(int version, enum qrcodegen_Ecc ecl)
+{
+ int v = version, e = (int) ecl;
+ assert(0 <= e && e < 4);
+ return getNumRawDataModules(v) / 8
+ - ECC_CODEWORDS_PER_BLOCK[e][v] * NUM_ERROR_CORRECTION_BLOCKS[e][v];
+}
+
+// Returns the number of data bits that can be stored in a QR Code of the given version number, after
+// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
+// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
+testable int
+getNumRawDataModules(int ver)
+{
+ assert(qrcodegen_VERSION_MIN <= ver && ver <= qrcodegen_VERSION_MAX);
+ int result = (16 * ver + 128) * ver + 64;
+ if (ver >= 2) {
+ int numAlign = ver / 7 + 2;
+ result -= (25 * numAlign - 10) * numAlign - 55;
+ if (ver >= 7)
+ result -= 36;
+ }
+ return result;
+}
+
+/*---- Reed-Solomon ECC generator functions ----*/
+
+// Calculates the Reed-Solomon generator polynomial of the given degree, storing in result[0 : degree].
+testable void
+calcReedSolomonGenerator(int degree, uint8_t result[])
+{
+ // Start with the monomial x^0
+ assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX);
+ memset(result, 0, degree * sizeof(result[0]));
+ result[degree - 1] = 1;
+
+ // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
+ // drop the highest term, and store the rest of the coefficients in order of descending powers.
+ // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
+ uint8_t root = 1;
+ for (int i = 0; i < degree; i++) {
+ // Multiply the current product by (x - r^i)
+ for (int j = 0; j < degree; j++) {
+ result[j] = finiteFieldMultiply(result[j], root);
+ if (j + 1 < degree)
+ result[j] ^= result[j + 1];
+ }
+ root = finiteFieldMultiply(root, 0x02);
+ }
+}
+
+// Calculates the remainder of the polynomial data[0 : dataLen] when divided by the generator[0 : degree], where all
+// polynomials are in big endian and the generator has an implicit leading 1 term, storing the result in result[0 : degree].
+testable void
+calcReedSolomonRemainder(const uint8_t data[],
+ int dataLen,
+ const uint8_t generator[],
+ int degree,
+ uint8_t result[])
+{
+ // Perform polynomial division
+ assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX);
+ memset(result, 0, degree * sizeof(result[0]));
+ for (int i = 0; i < dataLen; i++) {
+ uint8_t factor = data[i] ^ result[0];
+ memmove(&result[0], &result[1], (degree - 1) * sizeof(result[0]));
+ result[degree - 1] = 0;
+ for (int j = 0; j < degree; j++)
+ result[j] ^= finiteFieldMultiply(generator[j], factor);
+ }
+}
+
+#undef qrcodegen_REED_SOLOMON_DEGREE_MAX
+
+// Returns the product of the two given field elements modulo GF(2^8/0x11D).
+// All inputs are valid. This could be implemented as a 256*256 lookup table.
+testable uint8_t
+finiteFieldMultiply(uint8_t x, uint8_t y)
+{
+ // Russian peasant multiplication
+ uint8_t z = 0;
+ for (int i = 7; i >= 0; i--) {
+ z = (z << 1) ^ ((z >> 7) * 0x11D);
+ z ^= ((y >> i) & 1) * x;
+ }
+ return z;
+}
+
+/*---- Drawing function modules ----*/
+
+// Clears the given QR Code grid with white modules for the given
+// version's size, then marks every function module as black.
+testable void
+initializeFunctionModules(int version, uint8_t qrcode[])
+{
+ // Initialize QR Code
+ int qrsize = version * 4 + 17;
+ memset(qrcode, 0, ((qrsize * qrsize + 7) / 8 + 1) * sizeof(qrcode[0]));
+ qrcode[0] = (uint8_t) qrsize;
+
+ // Fill horizontal and vertical timing patterns
+ fillRectangle(6, 0, 1, qrsize, qrcode);
+ fillRectangle(0, 6, qrsize, 1, qrcode);
+
+ // Fill 3 finder patterns (all corners except bottom right) and format bits
+ fillRectangle(0, 0, 9, 9, qrcode);
+ fillRectangle(qrsize - 8, 0, 8, 9, qrcode);
+ fillRectangle(0, qrsize - 8, 9, 8, qrcode);
+
+ // Fill numerous alignment patterns
+ uint8_t alignPatPos[7];
+ int numAlign = getAlignmentPatternPositions(version, alignPatPos);
+ for (int i = 0; i < numAlign; i++) {
+ for (int j = 0; j < numAlign; j++) {
+ // Don't draw on the three finder corners
+ if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1)
+ || (i == numAlign - 1 && j == 0)))
+ fillRectangle(alignPatPos[i] - 2, alignPatPos[j] - 2, 5, 5, qrcode);
+ }
+ }
+
+ // Fill version blocks
+ if (version >= 7) {
+ fillRectangle(qrsize - 11, 0, 3, 6, qrcode);
+ fillRectangle(0, qrsize - 11, 6, 3, qrcode);
+ }
+}
+
+// Draws white function modules and possibly some black modules onto the given QR Code, without changing
+// non-function modules. This does not draw the format bits. This requires all function modules to be previously
+// marked black (namely by initializeFunctionModules()), because this may skip redrawing black function modules.
+static void
+drawWhiteFunctionModules(uint8_t qrcode[], int version)
+{
+ // Draw horizontal and vertical timing patterns
+ int qrsize = qrcodegen_getSize(qrcode);
+ for (int i = 7; i < qrsize - 7; i += 2) {
+ setModule(qrcode, 6, i, false);
+ setModule(qrcode, i, 6, false);
+ }
+
+ // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
+ for (int dy = -4; dy <= 4; dy++) {
+ for (int dx = -4; dx <= 4; dx++) {
+ int dist = abs(dx);
+ if (abs(dy) > dist)
+ dist = abs(dy);
+ if (dist == 2 || dist == 4) {
+ setModuleBounded(qrcode, 3 + dx, 3 + dy, false);
+ setModuleBounded(qrcode, qrsize - 4 + dx, 3 + dy, false);
+ setModuleBounded(qrcode, 3 + dx, qrsize - 4 + dy, false);
+ }
+ }
+ }
+
+ // Draw numerous alignment patterns
+ uint8_t alignPatPos[7];
+ int numAlign = getAlignmentPatternPositions(version, alignPatPos);
+ for (int i = 0; i < numAlign; i++) {
+ for (int j = 0; j < numAlign; j++) {
+ if ((i == 0 && j == 0) || (i == 0 && j == numAlign - 1)
+ || (i == numAlign - 1 && j == 0))
+ continue; // Don't draw on the three finder corners
+ for (int dy = -1; dy <= 1; dy++) {
+ for (int dx = -1; dx <= 1; dx++)
+ setModule(qrcode, alignPatPos[i] + dx, alignPatPos[j] + dy, dx == 0 && dy == 0);
+ }
+ }
+ }
+
+ // Draw version blocks
+ if (version >= 7) {
+ // Calculate error correction code and pack bits
+ int rem = version; // version is uint6, in the range [7, 40]
+ for (int i = 0; i < 12; i++)
+ rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
+ long bits = (long) version << 12 | rem; // uint18
+ assert(bits >> 18 == 0);
+
+ // Draw two copies
+ for (int i = 0; i < 6; i++) {
+ for (int j = 0; j < 3; j++) {
+ int k = qrsize - 11 + j;
+ setModule(qrcode, k, i, (bits & 1) != 0);
+ setModule(qrcode, i, k, (bits & 1) != 0);
+ bits >>= 1;
+ }
+ }
+ }
+}
+
+// Draws two copies of the format bits (with its own error correction code) based
+// on the given mask and error correction level. This always draws all modules of
+// the format bits, unlike drawWhiteFunctionModules() which might skip black modules.
+static void
+drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[])
+{
+ // Calculate error correction code and pack bits
+ assert(0 <= (int) mask && (int) mask <= 7);
+ static const int table[] = {1, 0, 3, 2};
+ int data = table[(int) ecl] << 3 | (int) mask; // errCorrLvl is uint2, mask is uint3
+ int rem = data;
+ for (int i = 0; i < 10; i++)
+ rem = (rem << 1) ^ ((rem >> 9) * 0x537);
+ int bits = (data << 10 | rem) ^ 0x5412; // uint15
+ assert(bits >> 15 == 0);
+
+ // Draw first copy
+ for (int i = 0; i <= 5; i++)
+ setModule(qrcode, 8, i, getBit(bits, i));
+ setModule(qrcode, 8, 7, getBit(bits, 6));
+ setModule(qrcode, 8, 8, getBit(bits, 7));
+ setModule(qrcode, 7, 8, getBit(bits, 8));
+ for (int i = 9; i < 15; i++)
+ setModule(qrcode, 14 - i, 8, getBit(bits, i));
+
+ // Draw second copy
+ int qrsize = qrcodegen_getSize(qrcode);
+ for (int i = 0; i < 8; i++)
+ setModule(qrcode, qrsize - 1 - i, 8, getBit(bits, i));
+ for (int i = 8; i < 15; i++)
+ setModule(qrcode, 8, qrsize - 15 + i, getBit(bits, i));
+ setModule(qrcode, 8, qrsize - 8, true); // Always black
+}
+
+// Calculates and stores an ascending list of positions of alignment patterns
+// for this version number, returning the length of the list (in the range [0,7]).
+// Each position is in the range [0,177), and are used on both the x and y axes.
+// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes.
+testable int
+getAlignmentPatternPositions(int version, uint8_t result[7])
+{
+ if (version == 1)
+ return 0;
+ int numAlign = version / 7 + 2;
+ int step = (version == 32) ? 26 : (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2;
+ for (int i = numAlign - 1, pos = version * 4 + 10; i >= 1; i--, pos -= step)
+ result[i] = pos;
+ result[0] = 6;
+ return numAlign;
+}
+
+// Sets every pixel in the range [left : left + width] * [top : top + height] to black.
+static void
+fillRectangle(int left, int top, int width, int height, uint8_t qrcode[])
+{
+ for (int dy = 0; dy < height; dy++) {
+ for (int dx = 0; dx < width; dx++)
+ setModule(qrcode, left + dx, top + dy, true);
+ }
+}
+
+/*---- Drawing data modules and masking ----*/
+
+// Draws the raw codewords (including data and ECC) onto the given QR Code. This requires the initial state of
+// the QR Code to be black at function modules and white at codeword modules (including unused remainder bits).
+static void
+drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[])
+{
+ int qrsize = qrcodegen_getSize(qrcode);
+ int i = 0; // Bit index into the data
+ // Do the funny zigzag scan
+ for (int right = qrsize - 1; right >= 1;
+ right -= 2) { // Index of right column in each column pair
+ if (right == 6)
+ right = 5;
+ for (int vert = 0; vert < qrsize; vert++) { // Vertical counter
+ for (int j = 0; j < 2; j++) {
+ int x = right - j; // Actual x coordinate
+ bool upward = ((right + 1) & 2) == 0;
+ int y = upward ? qrsize - 1 - vert : vert; // Actual y coordinate
+ if (!getModule(qrcode, x, y) && i < dataLen * 8) {
+ bool black = getBit(data[i >> 3], 7 - (i & 7));
+ setModule(qrcode, x, y, black);
+ i++;
+ }
+ // If this QR Code has any remainder bits (0 to 7), they were assigned as
+ // 0/false/white by the constructor and are left unchanged by this method
+ }
+ }
+ }
+ assert(i == dataLen * 8);
+}
+
+// XORs the codeword modules in this QR Code with the given mask pattern.
+// The function modules must be marked and the codeword bits must be drawn
+// before masking. Due to the arithmetic of XOR, calling applyMask() with
+// the same mask value a second time will undo the mask. A final well-formed
+// QR Code needs exactly one (not zero, two, etc.) mask applied.
+static void
+applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask)
+{
+ assert(0 <= (int) mask && (int) mask <= 7); // Disallows qrcodegen_Mask_AUTO
+ int qrsize = qrcodegen_getSize(qrcode);
+ for (int y = 0; y < qrsize; y++) {
+ for (int x = 0; x < qrsize; x++) {
+ if (getModule(functionModules, x, y))
+ continue;
+ bool invert;
+ switch ((int) mask) {
+ case 0:
+ invert = (x + y) % 2 == 0;
+ break;
+ case 1:
+ invert = y % 2 == 0;
+ break;
+ case 2:
+ invert = x % 3 == 0;
+ break;
+ case 3:
+ invert = (x + y) % 3 == 0;
+ break;
+ case 4:
+ invert = (x / 3 + y / 2) % 2 == 0;
+ break;
+ case 5:
+ invert = x * y % 2 + x * y % 3 == 0;
+ break;
+ case 6:
+ invert = (x * y % 2 + x * y % 3) % 2 == 0;
+ break;
+ case 7:
+ invert = ((x + y) % 2 + x * y % 3) % 2 == 0;
+ break;
+ default:
+ assert(false);
+ return;
+ }
+ bool val = getModule(qrcode, x, y);
+ setModule(qrcode, x, y, val ^ invert);
+ }
+ }
+}
+
+// Calculates and returns the penalty score based on state of the given QR Code's current modules.
+// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
+static long
+getPenaltyScore(const uint8_t qrcode[])
+{
+ int qrsize = qrcodegen_getSize(qrcode);
+ long result = 0;
+
+ // Adjacent modules in row having same color, and finder-like patterns
+ for (int y = 0; y < qrsize; y++) {
+ unsigned char runHistory[7] = {0};
+ bool color = false;
+ unsigned char runX = 0;
+ for (int x = 0; x < qrsize; x++) {
+ if (getModule(qrcode, x, y) == color) {
+ runX++;
+ if (runX == 5)
+ result += PENALTY_N1;
+ else if (runX > 5)
+ result++;
+ } else {
+ addRunToHistory(runX, runHistory);
+ if (!color && hasFinderLikePattern(runHistory))
+ result += PENALTY_N3;
+ color = getModule(qrcode, x, y);
+ runX = 1;
+ }
+ }
+ addRunToHistory(runX, runHistory);
+ if (color)
+ addRunToHistory(0, runHistory); // Dummy run of white
+ if (hasFinderLikePattern(runHistory))
+ result += PENALTY_N3;
+ }
+ // Adjacent modules in column having same color, and finder-like patterns
+ for (int x = 0; x < qrsize; x++) {
+ unsigned char runHistory[7] = {0};
+ bool color = false;
+ unsigned char runY = 0;
+ for (int y = 0; y < qrsize; y++) {
+ if (getModule(qrcode, x, y) == color) {
+ runY++;
+ if (runY == 5)
+ result += PENALTY_N1;
+ else if (runY > 5)
+ result++;
+ } else {
+ addRunToHistory(runY, runHistory);
+ if (!color && hasFinderLikePattern(runHistory))
+ result += PENALTY_N3;
+ color = getModule(qrcode, x, y);
+ runY = 1;
+ }
+ }
+ addRunToHistory(runY, runHistory);
+ if (color)
+ addRunToHistory(0, runHistory); // Dummy run of white
+ if (hasFinderLikePattern(runHistory))
+ result += PENALTY_N3;
+ }
+
+ // 2*2 blocks of modules having same color
+ for (int y = 0; y < qrsize - 1; y++) {
+ for (int x = 0; x < qrsize - 1; x++) {
+ bool color = getModule(qrcode, x, y);
+ if (color == getModule(qrcode, x + 1, y) && color == getModule(qrcode, x, y + 1)
+ && color == getModule(qrcode, x + 1, y + 1))
+ result += PENALTY_N2;
+ }
+ }
+
+ // Balance of black and white modules
+ int black = 0;
+ for (int y = 0; y < qrsize; y++) {
+ for (int x = 0; x < qrsize; x++) {
+ if (getModule(qrcode, x, y))
+ black++;
+ }
+ }
+ int total = qrsize * qrsize; // Note that size is odd, so black/total != 1/2
+ // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)%
+ int k = (int) ((labs(black * 20L - total * 10L) + total - 1) / total) - 1;
+ result += k * PENALTY_N4;
+ return result;
+}
+
+// Inserts the given value to the front of the given array, which shifts over the
+// existing values and deletes the last value. A helper function for getPenaltyScore().
+static void
+addRunToHistory(unsigned char run, unsigned char history[7])
+{
+ memmove(&history[1], &history[0], 6 * sizeof(history[0]));
+ history[0] = run;
+}
+
+// Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and
+// surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore().
+// Must only be called immediately after a run of white modules has ended.
+static bool
+hasFinderLikePattern(unsigned char runHistory[7])
+{
+ unsigned char n = runHistory[1];
+ // The maximum QR Code size is 177, hence the run length n <= 177.
+ // Arithmetic is promoted to int, so n*4 will not overflow.
+ return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n
+ && runHistory[3] == n * 3 && (runHistory[0] >= n * 4 || runHistory[6] >= n * 4);
+}
+
+/*---- Basic QR Code information ----*/
+
+// Public function - see documentation comment in header file.
+int
+qrcodegen_getSize(const uint8_t qrcode[])
+{
+ assert(qrcode != NULL);
+ int result = qrcode[0];
+ assert((qrcodegen_VERSION_MIN * 4 + 17) <= result
+ && result <= (qrcodegen_VERSION_MAX * 4 + 17));
+ return result;
+}
+
+// Public function - see documentation comment in header file.
+bool
+qrcodegen_getModule(const uint8_t qrcode[], int x, int y)
+{
+ assert(qrcode != NULL);
+ int qrsize = qrcode[0];
+ return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && getModule(qrcode, x, y);
+}
+
+// Gets the module at the given coordinates, which must be in bounds.
+testable bool
+getModule(const uint8_t qrcode[], int x, int y)
+{
+ int qrsize = qrcode[0];
+ assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize);
+ int index = y * qrsize + x;
+ return getBit(qrcode[(index >> 3) + 1], index & 7);
+}
+
+// Sets the module at the given coordinates, which must be in bounds.
+testable void
+setModule(uint8_t qrcode[], int x, int y, bool isBlack)
+{
+ int qrsize = qrcode[0];
+ assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize);
+ int index = y * qrsize + x;
+ int bitIndex = index & 7;
+ int byteIndex = (index >> 3) + 1;
+ if (isBlack)
+ qrcode[byteIndex] |= 1 << bitIndex;
+ else
+ qrcode[byteIndex] &= (1 << bitIndex) ^ 0xFF;
+}
+
+// Sets the module at the given coordinates, doing nothing if out of bounds.
+testable void
+setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack)
+{
+ int qrsize = qrcode[0];
+ if (0 <= x && x < qrsize && 0 <= y && y < qrsize)
+ setModule(qrcode, x, y, isBlack);
+}
+
+// Returns true iff the i'th bit of x is set to 1. Requires x >= 0 and 0 <= i <= 14.
+static bool
+getBit(int x, int i)
+{
+ return ((x >> i) & 1) != 0;
+}
+
+/*---- Segment handling ----*/
+
+// Public function - see documentation comment in header file.
+bool
+qrcodegen_isAlphanumeric(const char *text)
+{
+ assert(text != NULL);
+ for (; *text != '\0'; text++) {
+ if (strchr(ALPHANUMERIC_CHARSET, *text) == NULL)
+ return false;
+ }
+ return true;
+}
+
+// Public function - see documentation comment in header file.
+bool
+qrcodegen_isNumeric(const char *text)
+{
+ assert(text != NULL);
+ for (; *text != '\0'; text++) {
+ if (*text < '0' || *text > '9')
+ return false;
+ }
+ return true;
+}
+
+// Public function - see documentation comment in header file.
+size_t
+qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars)
+{
+ int temp = calcSegmentBitLength(mode, numChars);
+ if (temp == -1)
+ return SIZE_MAX;
+ assert(0 <= temp && temp <= INT16_MAX);
+ return ((size_t) temp + 7) / 8;
+}
+
+// Returns the number of data bits needed to represent a segment
+// containing the given number of characters using the given mode. Notes:
+// - Returns -1 on failure, i.e. numChars > INT16_MAX or
+// the number of needed bits exceeds INT16_MAX (i.e. 32767).
+// - Otherwise, all valid results are in the range [0, INT16_MAX].
+// - For byte mode, numChars measures the number of bytes, not Unicode code points.
+// - For ECI mode, numChars must be 0, and the worst-case number of bits is returned.
+// An actual ECI segment can have shorter data. For non-ECI modes, the result is exact.
+testable int
+calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars)
+{
+ // All calculations are designed to avoid overflow on all platforms
+ if (numChars > (unsigned int) INT16_MAX)
+ return -1;
+ long result = (long) numChars;
+ if (mode == qrcodegen_Mode_NUMERIC)
+ result = (result * 10 + 2) / 3; // ceil(10/3 * n)
+ else if (mode == qrcodegen_Mode_ALPHANUMERIC)
+ result = (result * 11 + 1) / 2; // ceil(11/2 * n)
+ else if (mode == qrcodegen_Mode_BYTE)
+ result *= 8;
+ else if (mode == qrcodegen_Mode_KANJI)
+ result *= 13;
+ else if (mode == qrcodegen_Mode_ECI && numChars == 0)
+ result = 3 * 8;
+ else { // Invalid argument
+ assert(false);
+ return -1;
+ }
+ assert(result >= 0);
+ if (result > (unsigned int) INT16_MAX)
+ return -1;
+ return (int) result;
+}
+
+// Public function - see documentation comment in header file.
+struct qrcodegen_Segment
+qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[])
+{
+ assert(data != NULL || len == 0);
+ struct qrcodegen_Segment result;
+ result.mode = qrcodegen_Mode_BYTE;
+ result.bitLength = calcSegmentBitLength(result.mode, len);
+ assert(result.bitLength != -1);
+ result.numChars = (int) len;
+ if (len > 0)
+ memcpy(buf, data, len * sizeof(buf[0]));
+ result.data = buf;
+ return result;
+}
+
+// Public function - see documentation comment in header file.
+struct qrcodegen_Segment
+qrcodegen_makeNumeric(const char *digits, uint8_t buf[])
+{
+ assert(digits != NULL);
+ struct qrcodegen_Segment result;
+ size_t len = strlen(digits);
+ result.mode = qrcodegen_Mode_NUMERIC;
+ int bitLen = calcSegmentBitLength(result.mode, len);
+ assert(bitLen != -1);
+ result.numChars = (int) len;
+ if (bitLen > 0)
+ memset(buf, 0, ((size_t) bitLen + 7) / 8 * sizeof(buf[0]));
+ result.bitLength = 0;
+
+ unsigned int accumData = 0;
+ int accumCount = 0;
+ for (; *digits != '\0'; digits++) {
+ char c = *digits;
+ assert('0' <= c && c <= '9');
+ accumData = accumData * 10 + (unsigned int) (c - '0');
+ accumCount++;
+ if (accumCount == 3) {
+ appendBitsToBuffer(accumData, 10, buf, &result.bitLength);
+ accumData = 0;
+ accumCount = 0;
+ }
+ }
+ if (accumCount > 0) // 1 or 2 digits remaining
+ appendBitsToBuffer(accumData, accumCount * 3 + 1, buf, &result.bitLength);
+ assert(result.bitLength == bitLen);
+ result.data = buf;
+ return result;
+}
+
+// Public function - see documentation comment in header file.
+struct qrcodegen_Segment
+qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[])
+{
+ assert(text != NULL);
+ struct qrcodegen_Segment result;
+ size_t len = strlen(text);
+ result.mode = qrcodegen_Mode_ALPHANUMERIC;
+ int bitLen = calcSegmentBitLength(result.mode, len);
+ assert(bitLen != -1);
+ result.numChars = (int) len;
+ if (bitLen > 0)
+ memset(buf, 0, ((size_t) bitLen + 7) / 8 * sizeof(buf[0]));
+ result.bitLength = 0;
+
+ unsigned int accumData = 0;
+ int accumCount = 0;
+ for (; *text != '\0'; text++) {
+ const char *temp = strchr(ALPHANUMERIC_CHARSET, *text);
+ assert(temp != NULL);
+ accumData = accumData * 45 + (unsigned int) (temp - ALPHANUMERIC_CHARSET);
+ accumCount++;
+ if (accumCount == 2) {
+ appendBitsToBuffer(accumData, 11, buf, &result.bitLength);
+ accumData = 0;
+ accumCount = 0;
+ }
+ }
+ if (accumCount > 0) // 1 character remaining
+ appendBitsToBuffer(accumData, 6, buf, &result.bitLength);
+ assert(result.bitLength == bitLen);
+ result.data = buf;
+ return result;
+}
+
+// Public function - see documentation comment in header file.
+struct qrcodegen_Segment
+qrcodegen_makeEci(long assignVal, uint8_t buf[])
+{
+ struct qrcodegen_Segment result;
+ result.mode = qrcodegen_Mode_ECI;
+ result.numChars = 0;
+ result.bitLength = 0;
+ if (assignVal < 0)
+ assert(false);
+ else if (assignVal < (1 << 7)) {
+ memset(buf, 0, 1 * sizeof(buf[0]));
+ appendBitsToBuffer(assignVal, 8, buf, &result.bitLength);
+ } else if (assignVal < (1 << 14)) {
+ memset(buf, 0, 2 * sizeof(buf[0]));
+ appendBitsToBuffer(2, 2, buf, &result.bitLength);
+ appendBitsToBuffer(assignVal, 14, buf, &result.bitLength);
+ } else if (assignVal < 1000000L) {
+ memset(buf, 0, 3 * sizeof(buf[0]));
+ appendBitsToBuffer(6, 3, buf, &result.bitLength);
+ appendBitsToBuffer(assignVal >> 10, 11, buf, &result.bitLength);
+ appendBitsToBuffer(assignVal & 0x3FF, 10, buf, &result.bitLength);
+ } else
+ assert(false);
+ result.data = buf;
+ return result;
+}
+
+// Calculates the number of bits needed to encode the given segments at the given version.
+// Returns a non-negative number if successful. Otherwise, returns -1 if a segment has too
+// many characters to fit its length field, or the total bits exceeds INT16_MAX.
+testable int
+getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version)
+{
+ assert(segs != NULL || len == 0);
+ long result = 0;
+ for (size_t i = 0; i < len; i++) {
+ int numChars = segs[i].numChars;
+ int bitLength = segs[i].bitLength;
+ assert(0 <= numChars && numChars <= INT16_MAX);
+ assert(0 <= bitLength && bitLength <= INT16_MAX);
+ int ccbits = numCharCountBits(segs[i].mode, version);
+ assert(0 <= ccbits && ccbits <= 16);
+ if (numChars >= (1L << ccbits))
+ return -1; // The segment's length doesn't fit the field's bit width
+ result += 4L + ccbits + bitLength;
+ if (result > INT16_MAX)
+ return -1; // The sum might overflow an int type
+ }
+ assert(0 <= result && result <= INT16_MAX);
+ return (int) result;
+}
+
+// Returns the bit width of the character count field for a segment in the given mode
+// in a QR Code at the given version number. The result is in the range [0, 16].
+static int
+numCharCountBits(enum qrcodegen_Mode mode, int version)
+{
+ assert(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX);
+ int i = (version + 7) / 17;
+ switch (mode) {
+ case qrcodegen_Mode_NUMERIC:
+ {
+ static const int temp[] = {10, 12, 14};
+ return temp[i];
+ }
+ case qrcodegen_Mode_ALPHANUMERIC:
+ {
+ static const int temp[] = {9, 11, 13};
+ return temp[i];
+ }
+ case qrcodegen_Mode_BYTE:
+ {
+ static const int temp[] = {8, 16, 16};
+ return temp[i];
+ }
+ case qrcodegen_Mode_KANJI:
+ {
+ static const int temp[] = {8, 10, 12};
+ return temp[i];
+ }
+ case qrcodegen_Mode_ECI:
+ return 0;
+ default:
+ assert(false);
+ return -1; // Dummy value
+ }
+}
diff --git a/src/libnmc-base/qrcodegen.h b/src/libnmc-base/qrcodegen.h
new file mode 100644
index 0000000000..b91ae49ab6
--- /dev/null
+++ b/src/libnmc-base/qrcodegen.h
@@ -0,0 +1,312 @@
+/*
+ * QR Code generator library (C)
+ *
+ * Copyright (c) Project Nayuki. (MIT License)
+ * https://www.nayuki.io/page/qr-code-generator-library
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ * - The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * - The Software is provided "as is", without warranty of any kind, express or
+ * implied, including but not limited to the warranties of merchantability,
+ * fitness for a particular purpose and noninfringement. In no event shall the
+ * authors or copyright holders be liable for any claim, damages or other
+ * liability, whether in an action of contract, tort or otherwise, arising from,
+ * out of or in connection with the Software or the use or other dealings in the
+ * Software.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * This library creates QR Code symbols, which is a type of two-dimension barcode.
+ * Invented by Denso Wave and described in the ISO/IEC 18004 standard.
+ * A QR Code structure is an immutable square grid of black and white cells.
+ * The library provides functions to create a QR Code from text or binary data.
+ * The library covers the QR Code Model 2 specification, supporting all versions (sizes)
+ * from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
+ *
+ * Ways to create a QR Code object:
+ * - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary().
+ * - Low level: Custom-make the list of segments and call
+ * qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced().
+ * (Note that all ways require supplying the desired error correction level and various byte buffers.)
+ */
+
+/*---- Enum and struct types----*/
+
+/*
+ * The error correction level in a QR Code symbol.
+ */
+enum qrcodegen_Ecc {
+ // Must be declared in ascending order of error protection
+ // so that an internal qrcodegen function works properly
+ qrcodegen_Ecc_LOW = 0, // The QR Code can tolerate about 7% erroneous codewords
+ qrcodegen_Ecc_MEDIUM, // The QR Code can tolerate about 15% erroneous codewords
+ qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords
+ qrcodegen_Ecc_HIGH, // The QR Code can tolerate about 30% erroneous codewords
+};
+
+/*
+ * The mask pattern used in a QR Code symbol.
+ */
+enum qrcodegen_Mask {
+ // A special value to tell the QR Code encoder to
+ // automatically select an appropriate mask pattern
+ qrcodegen_Mask_AUTO = -1,
+ // The eight actual mask patterns
+ qrcodegen_Mask_0 = 0,
+ qrcodegen_Mask_1,
+ qrcodegen_Mask_2,
+ qrcodegen_Mask_3,
+ qrcodegen_Mask_4,
+ qrcodegen_Mask_5,
+ qrcodegen_Mask_6,
+ qrcodegen_Mask_7,
+};
+
+/*
+ * Describes how a segment's data bits are interpreted.
+ */
+enum qrcodegen_Mode {
+ qrcodegen_Mode_NUMERIC = 0x1,
+ qrcodegen_Mode_ALPHANUMERIC = 0x2,
+ qrcodegen_Mode_BYTE = 0x4,
+ qrcodegen_Mode_KANJI = 0x8,
+ qrcodegen_Mode_ECI = 0x7,
+};
+
+/*
+ * A segment of character/binary/control data in a QR Code symbol.
+ * The mid-level way to create a segment is to take the payload data
+ * and call a factory function such as qrcodegen_makeNumeric().
+ * The low-level way to create a segment is to custom-make the bit buffer
+ * and initialize a qrcodegen_Segment struct with appropriate values.
+ * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
+ * Any segment longer than this is meaningless for the purpose of generating QR Codes.
+ * Moreover, the maximum allowed bit length is 32767 because
+ * the largest QR Code (version 40) has 31329 modules.
+ */
+struct qrcodegen_Segment {
+ // The mode indicator of this segment.
+ enum qrcodegen_Mode mode;
+
+ // The length of this segment's unencoded data. Measured in characters for
+ // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
+ // Always zero or positive. Not the same as the data's bit length.
+ int numChars;
+
+ // The data bits of this segment, packed in bitwise big endian.
+ // Can be null if the bit length is zero.
+ uint8_t *data;
+
+ // The number of valid data bits used in the buffer. Requires
+ // 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8.
+ // The character count (numChars) must agree with the mode and the bit buffer length.
+ int bitLength;
+};
+
+/*---- Macro constants and functions ----*/
+
+#define qrcodegen_VERSION_MIN \
+ 1 // The minimum version number supported in the QR Code Model 2 standard
+#define qrcodegen_VERSION_MAX \
+ 40 // The maximum version number supported in the QR Code Model 2 standard
+
+// Calculates the number of bytes needed to store any QR Code up to and including the given version number,
+// as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];'
+// can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16).
+// Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX.
+#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) *4 + 17) * ((n) *4 + 17) + 7) / 8 + 1)
+
+// The worst-case number of bytes needed to store one QR Code, up to and including
+// version 40. This value equals 3918, which is just under 4 kilobytes.
+// Use this more convenient value to avoid calculating tighter memory bounds for buffers.
+#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX)
+
+/*---- Functions (high level) to generate QR Codes ----*/
+
+/*
+ * Encodes the given text string to a QR Code, returning true if encoding succeeded.
+ * If the data is too long to fit in any version in the given range
+ * at the given ECC level, then false is returned.
+ * - The input text must be encoded in UTF-8 and contain no NULs.
+ * - The variables ecl and mask must correspond to enum constant values.
+ * - Requires 1 <= minVersion <= maxVersion <= 40.
+ * - The arrays tempBuffer and qrcode must each have a length
+ * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion).
+ * - After the function returns, tempBuffer contains no useful data.
+ * - If successful, the resulting QR Code may use numeric,
+ * alphanumeric, or byte mode to encode the text.
+ * - In the most optimistic case, a QR Code at version 40 with low ECC
+ * can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string
+ * up to 4296 characters, or any digit string up to 7089 characters.
+ * These numbers represent the hard upper limit of the QR Code standard.
+ * - Please consult the QR Code specification for information on
+ * data capacities per version, ECC level, and text encoding mode.
+ */
+bool qrcodegen_encodeText(const char * text,
+ uint8_t tempBuffer[],
+ uint8_t qrcode[],
+ enum qrcodegen_Ecc ecl,
+ int minVersion,
+ int maxVersion,
+ enum qrcodegen_Mask mask,
+ bool boostEcl);
+
+/*
+ * Encodes the given binary data to a QR Code, returning true if encoding succeeded.
+ * If the data is too long to fit in any version in the given range
+ * at the given ECC level, then false is returned.
+ * - The input array range dataAndTemp[0 : dataLen] should normally be
+ * valid UTF-8 text, but is not required by the QR Code standard.
+ * - The variables ecl and mask must correspond to enum constant values.
+ * - Requires 1 <= minVersion <= maxVersion <= 40.
+ * - The arrays dataAndTemp and qrcode must each have a length
+ * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion).
+ * - After the function returns, the contents of dataAndTemp may have changed,
+ * and does not represent useful data anymore.
+ * - If successful, the resulting QR Code will use byte mode to encode the data.
+ * - In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte
+ * sequence up to length 2953. This is the hard upper limit of the QR Code standard.
+ * - Please consult the QR Code specification for information on
+ * data capacities per version, ECC level, and text encoding mode.
+ */
+bool qrcodegen_encodeBinary(uint8_t dataAndTemp[],
+ size_t dataLen,
+ uint8_t qrcode[],
+ enum qrcodegen_Ecc ecl,
+ int minVersion,
+ int maxVersion,
+ enum qrcodegen_Mask mask,
+ bool boostEcl);
+
+/*---- Functions (low level) to generate QR Codes ----*/
+
+/*
+ * Renders a QR Code representing the given segments at the given error correction level.
+ * The smallest possible QR Code version is automatically chosen for the output. Returns true if
+ * QR Code creation succeeded, or false if the data is too long to fit in any version. The ECC level
+ * of the result may be higher than the ecl argument if it can be done without increasing the version.
+ * This function allows the user to create a custom sequence of segments that switches
+ * between modes (such as alphanumeric and byte) to encode text in less space.
+ * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary().
+ * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will
+ * result in them being clobbered, but the QR Code output will still be correct.
+ * But the qrcode array must not overlap tempBuffer or any segment's data buffer.
+ */
+bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[],
+ size_t len,
+ enum qrcodegen_Ecc ecl,
+ uint8_t tempBuffer[],
+ uint8_t qrcode[]);
+
+/*
+ * Renders a QR Code representing the given segments with the given encoding parameters.
+ * Returns true if QR Code creation succeeded, or false if the data is too long to fit in the range of versions.
+ * The smallest possible QR Code version within the given range is automatically
+ * chosen for the output. Iff boostEcl is true, then the ECC level of the result
+ * may be higher than the ecl argument if it can be done without increasing the
+ * version. The mask number is either between 0 to 7 (inclusive) to force that
+ * mask, or -1 to automatically choose an appropriate mask (which may be slow).
+ * This function allows the user to create a custom sequence of segments that switches
+ * between modes (such as alphanumeric and byte) to encode text in less space.
+ * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary().
+ * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will
+ * result in them being clobbered, but the QR Code output will still be correct.
+ * But the qrcode array must not overlap tempBuffer or any segment's data buffer.
+ */
+bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[],
+ size_t len,
+ enum qrcodegen_Ecc ecl,
+ int minVersion,
+ int maxVersion,
+ int mask,
+ bool boostEcl,
+ uint8_t tempBuffer[],
+ uint8_t qrcode[]);
+
+/*
+ * Tests whether the given string can be encoded as a segment in alphanumeric mode.
+ * A string is encodable iff each character is in the following set: 0 to 9, A to Z
+ * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
+ */
+bool qrcodegen_isAlphanumeric(const char *text);
+
+/*
+ * Tests whether the given string can be encoded as a segment in numeric mode.
+ * A string is encodable iff each character is in the range 0 to 9.
+ */
+bool qrcodegen_isNumeric(const char *text);
+
+/*
+ * Returns the number of bytes (uint8_t) needed for the data buffer of a segment
+ * containing the given number of characters using the given mode. Notes:
+ * - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or
+ * the number of needed bits exceeds INT16_MAX (i.e. 32767).
+ * - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096.
+ * - It is okay for the user to allocate more bytes for the buffer than needed.
+ * - For byte mode, numChars measures the number of bytes, not Unicode code points.
+ * - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned.
+ * An actual ECI segment can have shorter data. For non-ECI modes, the result is exact.
+ */
+size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars);
+
+/*
+ * Returns a segment representing the given binary data encoded in
+ * byte mode. All input byte arrays are acceptable. Any text string
+ * can be converted to UTF-8 bytes and encoded as a byte mode segment.
+ */
+struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]);
+
+/*
+ * Returns a segment representing the given string of decimal digits encoded in numeric mode.
+ */
+struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]);
+
+/*
+ * Returns a segment representing the given text string encoded in alphanumeric mode.
+ * The characters allowed are: 0 to 9, A to Z (uppercase only), space,
+ * dollar, percent, asterisk, plus, hyphen, period, slash, colon.
+ */
+struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]);
+
+/*
+ * Returns a segment representing an Extended Channel Interpretation
+ * (ECI) designator with the given assignment value.
+ */
+struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]);
+
+/*---- Functions to extract raw data from QR Codes ----*/
+
+/*
+ * Returns the side length of the given QR Code, assuming that encoding succeeded.
+ * The result is in the range [21, 177]. Note that the length of the array buffer
+ * is related to the side length - every 'uint8_t qrcode[]' must have length at least
+ * qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1).
+ */
+int qrcodegen_getSize(const uint8_t qrcode[]);
+
+/*
+ * Returns the color of the module (pixel) at the given coordinates, which is false
+ * for white or true for black. The top left corner has the coordinates (x=0, y=0).
+ * If the given coordinates are out of bounds, then false (white) is returned.
+ */
+bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y);
+
+#ifdef __cplusplus
+}
+#endif